Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
752483a
Create DatasetDisplay component
guerler Oct 18, 2025
ece6d1a
Switch from iframe to div
guerler Oct 18, 2025
c17a5ac
Move template content to display component
guerler Oct 18, 2025
4495478
Switch over to only parsing the chunk
guerler Oct 18, 2025
579161f
Remove tabular_chunked.mako
guerler Oct 19, 2025
ded75ca
Remove large_file and binary_file makos
guerler Oct 19, 2025
ef47433
Begin to handle dataset details in dataset display component
guerler Oct 19, 2025
c9d5b94
Add links to download and display complete datasets
guerler Oct 19, 2025
1cc49a6
Collect dataset details from store
guerler Oct 19, 2025
9132c9d
Only show download option for large datasets
guerler Oct 19, 2025
3441907
Remove Tabular Chunked View from bundle entries
guerler Oct 19, 2025
1f9c78e
Prepare checking datatype for warning message
guerler Oct 19, 2025
9cf7795
Adjust truncation messages
guerler Oct 19, 2025
64555c4
Remove unused constant and default props
guerler Oct 19, 2025
23e0ef8
Add truncated peek size to header
guerler Oct 19, 2025
3a5e5aa
Update dataset display
guerler Oct 19, 2025
8d95300
Add overflow
guerler Oct 19, 2025
a5c04fc
Adjust overflow behavior
guerler Oct 19, 2025
687e1d2
Refactor nested props in Tabular Chunked View
guerler Oct 19, 2025
5786f9e
Allow overflow-auto
guerler Oct 19, 2025
de10160
Add error message
guerler Oct 20, 2025
42c8948
Extract actual error message
guerler Oct 20, 2025
23d7e2f
Move sanitization message to dataset display
guerler Oct 20, 2025
335fc4d
Add watcher for dataset id
guerler Oct 20, 2025
b13d646
Disable frame switching
guerler Oct 20, 2025
2f2bc1b
Refactor sanatize message handling
guerler Oct 20, 2025
e76c1ff
Sanitize content
guerler Oct 20, 2025
7a1b889
Rely on content-type, use consistent header variable names
guerler Oct 20, 2025
34ed1ab
Move tabular chunked view component to datasets, its not a visualization
guerler Oct 20, 2025
b17d4a6
Restore iframe, inject content
guerler Oct 20, 2025
26628e8
Restore iframe switching
guerler Oct 20, 2025
d3b31b0
Avoid injection, use src to load html pages
guerler Oct 20, 2025
0e49f66
Use preview url
guerler Oct 20, 2025
2253bd6
Remove unused content type ref
guerler Oct 20, 2025
eaa6c6c
Emit load
guerler Oct 20, 2025
f28a13b
Add content chunked to header
guerler Oct 20, 2025
7f68d2e
Load first chunk in tabular chunked view
guerler Oct 20, 2025
e5edfbc
Determine overflow on tab not parent level
guerler Oct 20, 2025
dea3e65
Update client/src/components/Dataset/DatasetDisplay.vue
guerler Oct 28, 2025
e16d750
Update client/src/components/Dataset/DatasetDisplay.vue
guerler Oct 28, 2025
d1d4ae3
Explicitly declare emit in data display component
guerler Oct 28, 2025
506bb3d
Improve typing
guerler Oct 28, 2025
21d90a0
Add selenium test to check display component
guerler Oct 28, 2025
d6eb465
Switch back to default content
guerler Oct 28, 2025
aa0cba2
Use consistent selenium test filename
guerler Oct 28, 2025
9048212
Remove unused import from selenium test
guerler Oct 28, 2025
851407e
Fix formatting of imports
guerler Oct 28, 2025
db9937f
Add selenium only to decorator
guerler Oct 28, 2025
60dd9ce
Use default frame id
guerler Oct 28, 2025
81000dc
CenterFrame already calls withPrefix
guerler Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions client/src/bundleEntries.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
/**
* The list of horrible globals we expose on window.bundleEntries.
*
* Everything that is exposed on this global variable is something that the python templates
* require for their hardcoded initializations. These objects are going to have to continue
* to exist until such time as we replace the overall application with a Vue component which
* will handle initializations for components individually.
*
* The list of globals we expose on window.bundleEntries.
*/
import { replaceChildrenWithComponent } from "utils/mountVueComponent";

import TabularChunkedView from "components/Visualizations/Tabular/TabularChunkedView.vue";

// legacy/grid_base.mako
export { default as LegacyGridView } from "legacy/grid/grid-view";

// webapps/reports/run_stats.mako
export { create_chart, create_histogram } from "reports/run_stats";

// webapps/galaxy/dataset/{ display | tabular_chunked }.mako
export const createTabularDatasetChunkedView = (options) => {
return replaceChildrenWithComponent(options.parent_elt, TabularChunkedView, { options });
};
105 changes: 105 additions & 0 deletions client/src/components/Dataset/DatasetDisplay.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
import { BAlert } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, ref, watch } from "vue";

import { useDatasetStore } from "@/stores/datasetStore";
import { useUserStore } from "@/stores/userStore";
import { withPrefix } from "@/utils/redirect";
import { errorMessageAsString } from "@/utils/simple-error";
import { bytesToString } from "@/utils/utils";

import Alert from "@/components/Alert.vue";
import TabularChunkedView from "@/components/Dataset/Tabular/TabularChunkedView.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
import CenterFrame from "@/entry/analysis/modules/CenterFrame.vue";

interface Props {
datasetId: string;
isBinary: boolean;
}

const { getDataset, isLoadingDataset } = useDatasetStore();

const emit = defineEmits(["load"]);

const props = defineProps<Props>();

const contentTruncated = ref<number | null>(null);
const contentChunked = ref<boolean>(false);
const errorMessage = ref<string>("");
const sanitizedJobImported = ref<boolean>(false);
const sanitizedToolId = ref<string | null>(null);

const { isAdmin } = storeToRefs(useUserStore());

const dataset = computed(() => getDataset(props.datasetId));
const datasetUrl = computed(() => `/datasets/${props.datasetId}/display`);
const downloadUrl = computed(() => withPrefix(`${datasetUrl.value}?to_ext=${dataset.value?.file_ext}`));
const isLoading = computed(() => isLoadingDataset(props.datasetId));
const previewUrl = computed(() => `${datasetUrl.value}?preview=True`);

const sanitizedMessage = computed(() => {
const plainText = "Contents are shown as plain text.";
if (sanitizedJobImported.value) {
return `Dataset has been imported. ${plainText}`;
} else if (sanitizedToolId.value) {
return `Dataset created by a tool that is not known to create safe HTML. ${plainText}`;
}
return undefined;
});

watch(
() => props.datasetId,
async () => {
try {
const { headers } = await fetch(withPrefix(previewUrl.value), { method: "HEAD" });
contentChunked.value = !!headers.get("x-content-chunked");
contentTruncated.value = headers.get("x-content-truncated")
? Number(headers.get("x-content-truncated"))
: null;
sanitizedJobImported.value = !!headers.get("x-sanitized-job-imported");
sanitizedToolId.value = headers.get("x-sanitized-tool-id");
errorMessage.value = "";
} catch (e) {
errorMessage.value = errorMessageAsString(e);
console.error(e);
}
},
{ immediate: true },
);
</script>

<template>
<BAlert v-if="errorMessage" variant="danger" show>
{{ errorMessage }}
</BAlert>
<LoadingSpan v-else-if="isLoading || !dataset" message="Loading dataset content" />
<div v-else class="dataset-display h-100">
<Alert v-if="sanitizedMessage" :dismissible="true" variant="warning" data-description="sanitization warning">
{{ sanitizedMessage }}
<span v-if="isAdmin && sanitizedToolId">
<br />
<router-link data-description="allowlist link" to="/admin/sanitize_allow">Review Allowlist</router-link>
if outputs of {{ sanitizedToolId }} are trusted and should be shown as HTML.
</span>
</Alert>
<div v-if="dataset.deleted" id="deleted-data-message" class="errormessagelarge">
You are viewing a deleted dataset.
</div>
<TabularChunkedView v-if="contentChunked" :options="dataset" />
<div v-else class="h-100">
<div v-if="isBinary">
This is a binary (or unknown to Galaxy) dataset of size {{ bytesToString(dataset.file_size) }}. Preview
is not implemented for this filetype. Displaying as ASCII text.
</div>
<div v-if="contentTruncated" class="warningmessagelarge">
<div>
This dataset is large and only the first {{ bytesToString(contentTruncated) }} is shown below.
</div>
<a :href="downloadUrl">Download</a>
</div>
<CenterFrame :src="previewUrl" @load="emit('load')" />
</div>
</div>
</template>
46 changes: 17 additions & 29 deletions client/src/components/Dataset/DatasetView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { bytesToString } from "@/utils/utils";
import DatasetError from "../DatasetInformation/DatasetError.vue";
import LoadingSpan from "../LoadingSpan.vue";
import DatasetAsImage from "./DatasetAsImage/DatasetAsImage.vue";
import DatasetDisplay from "./DatasetDisplay.vue";
import DatasetState from "./DatasetState.vue";
import Heading from "@/components/Common/Heading.vue";
import DatasetAttributes from "@/components/DatasetInformation/DatasetAttributes.vue";
import DatasetDetails from "@/components/DatasetInformation/DatasetDetails.vue";
import VisualizationsList from "@/components/Visualizations/Index.vue";
import VisualizationFrame from "@/components/Visualizations/VisualizationFrame.vue";
import CenterFrame from "@/entry/analysis/modules/CenterFrame.vue";

const datasetStore = useDatasetStore();
const datatypeStore = useDatatypeStore();
Expand Down Expand Up @@ -61,6 +61,13 @@ const downloadUrl = computed(() => withPrefix(`/datasets/${props.datasetId}/disp
const preferredVisualization = computed(
() => dataset.value && datatypeStore.getPreferredVisualization(dataset.value.file_ext),
);
const isBinaryDataset = computed(() => {
if (!dataset.value?.file_ext || !datatypesMapperStore.datatypesMapper) {
return false;
}
return datatypesMapperStore.datatypesMapper.isSubTypeOfAny(dataset.value.file_ext, ["galaxy.datatypes.binary"]);
});

const isImageDataset = computed(() => {
if (!dataset.value?.file_ext || !datatypesMapperStore.datatypesMapper) {
return false;
Expand Down Expand Up @@ -93,7 +100,7 @@ watch(

<template>
<LoadingSpan v-if="isLoading || !dataset" message="Loading dataset details" />
<div v-else class="dataset-view d-flex flex-column h-100">
<div v-else class="dataset-view d-flex flex-column">
<header v-if="!displayOnly" :key="`dataset-header-${dataset.id}`" class="dataset-header flex-shrink-0">
<div class="d-flex">
<Heading
Expand Down Expand Up @@ -181,18 +188,13 @@ watch(
<FontAwesomeIcon :icon="faBug" class="mr-1" /> Error
</BNavItem>
</BNav>
<div v-if="tab === 'preview'" class="h-100">
<div v-if="tab === 'preview'" class="tab-content-panel">
<VisualizationFrame
v-if="preferredVisualization"
:dataset-id="datasetId"
:visualization="preferredVisualization"
@load="iframeLoading = false" />
<CenterFrame
v-else-if="isPdfDataset"
:src="`/datasets/${datasetId}/display/?preview=True`"
:is-preview="true"
@load="iframeLoading = false" />
<div v-else-if="isAutoDownloadType" class="auto-download-message p-4">
<div v-else-if="isAutoDownloadType && !isPdfDataset" class="auto-download-message p-4">
<div class="alert alert-info">
<h4>Download Required</h4>
<p>This file type ({{ dataset.file_ext }}) will download automatically when accessed directly.</p>
Expand All @@ -203,23 +205,14 @@ watch(
</div>
</div>
<DatasetAsImage
v-else-if="isImageDataset"
v-else-if="isImageDataset && !isPdfDataset"
:history-dataset-id="datasetId"
:allow-size-toggle="true"
class="p-3" />
<CenterFrame
v-else
:src="`/datasets/${datasetId}/display/?preview=True`"
:is-preview="true"
@load="iframeLoading = false" />
<DatasetDisplay v-else :dataset-id="datasetId" :is-binary="isBinaryDataset" @load="iframeLoading = false" />
</div>
<div v-else-if="tab === 'raw'" class="h-100">
<CenterFrame
v-if="isPdfDataset"
:src="`/datasets/${datasetId}/display/?preview=True`"
:is-preview="true"
@load="iframeLoading = false" />
<div v-else-if="isAutoDownloadType" class="auto-download-message p-4">
<div v-else-if="tab === 'raw'" class="tab-content-panel">
<div v-if="isAutoDownloadType && !isPdfDataset" class="auto-download-message p-4">
<div class="alert alert-info">
<h4>Download Required</h4>
<p>This file type ({{ dataset.file_ext }}) will download automatically when accessed directly.</p>
Expand All @@ -229,11 +222,7 @@ watch(
</a>
</div>
</div>
<CenterFrame
v-else
:src="`/datasets/${datasetId}/display/?preview=True`"
:is-preview="true"
@load="iframeLoading = false" />
<DatasetDisplay v-else :dataset-id="datasetId" :is-binary="isBinaryDataset" @load="iframeLoading = false" />
</div>
<div v-else-if="tab === 'visualize'" class="tab-content-panel">
<VisualizationsList :dataset-id="datasetId" />
Expand Down Expand Up @@ -318,8 +307,7 @@ watch(
.tab-content-panel {
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
overflow: auto;
height: 100%;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import axios from "axios";
import { parse } from "csv-parse/sync";
import { computed, onMounted, reactive, ref, watch } from "vue";

import type { HDADetailed } from "@/api";
import { getAppRoot } from "@/onload/loadConfig";

interface TabularChunk {
Expand All @@ -12,17 +13,14 @@ interface TabularChunk {
data_line_offset: number;
}

interface TabularDataset extends HDADetailed {
metadata_columns?: number;
metadata_column_types?: string[];
metadata_column_names?: string[];
}

interface TabularChunkedViewProps {
options: {
dataset_config: {
id: string;
file_ext: string;
first_data_chunk: TabularChunk;
metadata_columns: number;
metadata_column_types: string[];
metadata_column_names: string[];
};
};
options: TabularDataset;
}

const props = defineProps<TabularChunkedViewProps>();
Expand All @@ -37,32 +35,32 @@ const tabularData = reactive<{ rows: string[][] }>({
});

const columns = computed(() => {
const columns = Array(props.options.dataset_config.metadata_columns);
const columns = Array(props.options.metadata_columns);
// for each column_name, inject header
if (props.options.dataset_config.metadata_column_names?.length > 0) {
props.options.dataset_config.metadata_column_names.forEach((column_name, index) => {
if (props.options.metadata_column_names && props.options.metadata_column_names?.length > 0) {
props.options.metadata_column_names.forEach((column_name, index) => {
columns[index] = column_name;
});
}
return columns;
});

const columnStyle = computed(() => {
const columnStyle = Array(props.options.dataset_config.metadata_columns);
if (props.options.dataset_config.metadata_column_types?.length > 0) {
props.options.dataset_config.metadata_column_types.forEach((column_type, index) => {
const columnStyle = Array(props.options.metadata_columns);
if (props.options.metadata_column_types && props.options.metadata_column_types?.length > 0) {
props.options.metadata_column_types.forEach((column_type, index) => {
columnStyle[index] = column_type === "str" || column_type === "list" ? "string-align" : "number-align";
});
}
return columnStyle;
});

const delimiter = computed(() => {
return props.options.dataset_config.file_ext === "csv" ? "," : "\t";
return props.options.file_ext === "csv" ? "," : "\t";
});

const chunkUrl = computed(() => {
return `${getAppRoot()}dataset/display?dataset_id=${props.options.dataset_config.id}`;
return `${getAppRoot()}dataset/display?dataset_id=${props.options.id}`;
});

// Loading more data on user scroll to (near) bottom.
Expand Down Expand Up @@ -163,11 +161,8 @@ function nextChunk() {
}

onMounted(() => {
// Render first chunk if available.
if (props.options.dataset_config.first_data_chunk) {
processChunk(props.options.dataset_config.first_data_chunk);
loading.value = false;
}
// Fetch and render first chunk
nextChunk();
});
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ onMounted(async () => {
</script>

<template>
<div aria-labelledby="dataset-attributes-heading">
<div class="dataset-attributes" aria-labelledby="dataset-attributes-heading">
<Heading id="dataset-attributes-heading" h1 separator inline size="md">
{{ localize("Edit Dataset Attributes") }}
</Heading>
Expand Down Expand Up @@ -230,3 +230,10 @@ onMounted(async () => {
</div>
</div>
</template>

<style>
.dataset-attributes {
overflow-x: hidden;
overflow-y: auto;
}
</style>
3 changes: 2 additions & 1 deletion client/src/components/DatasetInformation/DatasetDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ onUnmounted(() => {
display: flex;
flex-direction: column;
gap: 1rem;

overflow-x: hidden;
overflow-y: auto;
.dataset-peek {
word-break: break-all;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ onMounted(() => {
</script>

<template>
<div class="position-relative h-100">
<div class="position-relative h-100 overflow-hidden">
<div v-if="isLoading" class="iframe-loading bg-light">
<LoadingSpan message="Loading preview" />
</div>
Expand Down
Loading
Loading