Skip to content

[25.0] [WIP] DatasetView and Card Polish #20342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: release_25.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions client/src/api/datatypes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
import axios from "axios";

import type { components } from "@/api";
import { withPrefix } from "@/utils/redirect";
import { rethrowSimple } from "@/utils/simple-error";

export type CompositeFileInfo = components["schemas"]["CompositeFileInfo"];
export type DatatypeDetails = components["schemas"]["DatatypeDetails"];

/**
* Get details about a specific datatype
*/
export async function fetchDatatypeDetails(extension: string) {
try {
const { data } = await axios.get(withPrefix(`/api/datatypes/${extension}`));
return data;
} catch (error) {
rethrowSimple(error);
}
}
5 changes: 5 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9301,6 +9301,11 @@ export interface components {
* @description The URL to a detailed description for this datatype
*/
description_url: string | null;
/**
* Display behavior
* @description How this datatype behaves when displayed with preview=True: 'inline' (can be displayed in browser) or 'download' (triggers download)
*/
display_behavior?: string | null;
/**
* Display in upload
* @description If True, the associated file extension will be displayed in the `File Format` select list in the `Upload File from your computer` tool in the `Get Data` tool section of the tool panel
Expand Down
151 changes: 112 additions & 39 deletions client/src/components/Dataset/DatasetView.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<script setup lang="ts">
import { BNav, BNavItem } from "bootstrap-vue";
import { faBug, faChartBar, faEye, faFileAlt, faInfoCircle, faPen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BLink, BNav, BNavItem } from "bootstrap-vue";
import { computed, ref, watch } from "vue";

import { usePersistentToggle } from "@/composables/persistentToggle";
import { useDatasetStore } from "@/stores/datasetStore";
import { useDatatypeVisualizationsStore } from "@/stores/datatypeVisualizationsStore";
import { useDatatypeStore } from "@/stores/datatypeStore";
import { bytesToString } from "@/utils/utils";

import DatasetError from "../DatasetInformation/DatasetError.vue";
import LoadingSpan from "../LoadingSpan.vue";
Expand All @@ -17,50 +20,43 @@ import VisualizationFrame from "@/components/Visualizations/VisualizationFrame.v
import CenterFrame from "@/entry/analysis/modules/CenterFrame.vue";

const datasetStore = useDatasetStore();
const datatypeVisualizationsStore = useDatatypeVisualizationsStore();
const datatypeStore = useDatatypeStore();
const { toggled: headerCollapsed, toggle: toggleHeaderCollapse } = usePersistentToggle("dataset-header-collapsed");

interface Props {
datasetId: string;
tab?: "details" | "edit" | "error" | "preview" | "visualize";
tab?: "details" | "edit" | "error" | "preview" | "raw" | "visualize";
}

const props = withDefaults(defineProps<Props>(), {
tab: "preview",
});

const iframeLoading = ref(true);
const preferredVisualization = ref<string>();

const dataset = computed(() => datasetStore.getDataset(props.datasetId));
const headerState = computed(() => (headerCollapsed.value ? "closed" : "open"));
const isLoading = computed(() => datasetStore.isLoadingDataset(props.datasetId));
const showError = computed(
() => dataset.value && (dataset.value.state === "error" || dataset.value.state === "failed_metadata")
);
const isAutoDownloadType = computed(
() => dataset.value && datatypeStore.isDatatypeAutoDownload(dataset.value.file_ext)
);
const preferredVisualization = computed(
() => dataset.value && datatypeStore.getPreferredVisualization(dataset.value.file_ext)
);

// Check if the dataset has a preferred visualization by datatype
async function checkPreferredVisualization() {
if (dataset.value && dataset.value.file_ext) {
try {
const mapping = await datatypeVisualizationsStore.getPreferredVisualizationForDatatype(
dataset.value.file_ext
);
if (mapping) {
preferredVisualization.value = mapping.visualization;
} else {
preferredVisualization.value = undefined;
}
} catch (error) {
preferredVisualization.value = undefined;
// Watch for changes to the dataset to fetch datatype info
watch(
() => dataset.value?.file_ext,
async () => {
if (dataset.value && dataset.value.file_ext) {
await datatypeStore.fetchDatatypeDetails(dataset.value.file_ext);
}
} else {
preferredVisualization.value = undefined;
}
}

// Watch for changes to the dataset to check for preferred visualizations
watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: true });
},
{ immediate: true }
);
</script>

<template>
Expand Down Expand Up @@ -100,29 +96,52 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
{{ dataset.genome_build }}
</BLink>
</span>
<div v-if="dataset.misc_info" class="info">
<span class="value">{{ dataset.misc_info }}</span>
</div>
<span v-if="dataset.file_size" class="filesize">
<span v-localize class="prompt">size</span>
<span class="value font-weight-bold" v-html="bytesToString(dataset.file_size, false)" />
</span>
</div>
</transition>
</header>
<BNav pills class="my-2 p-2 bg-light border-bottom">
<BNavItem title="Preview" :active="tab === 'preview'" :to="`/datasets/${datasetId}/preview`">
Preview
<BNavItem
title="View a preview of the dataset contents"
:active="tab === 'preview'"
:to="`/datasets/${datasetId}/preview`">
<FontAwesomeIcon :icon="faEye" class="mr-1" /> Preview
</BNavItem>
<BNavItem
v-if="preferredVisualization"
title="View raw dataset contents"
:active="tab === 'raw'"
:to="`/datasets/${datasetId}/raw`">
<FontAwesomeIcon :icon="faFileAlt" class="mr-1" /> Raw
</BNavItem>
<BNavItem
v-if="!showError"
title="Visualize"
title="Explore available visualizations for this dataset"
:active="tab === 'visualize'"
:to="`/datasets/${datasetId}/visualize`">
Visualize
<FontAwesomeIcon :icon="faChartBar" class="mr-1" /> Visualize
</BNavItem>
<BNavItem title="Details" :active="tab === 'details'" :to="`/datasets/${datasetId}/details`">
Details
<BNavItem
title="View detailed information about this dataset"
:active="tab === 'details'"
:to="`/datasets/${datasetId}/details`">
<FontAwesomeIcon :icon="faInfoCircle" class="mr-1" /> Details
</BNavItem>
<BNavItem title="Edit" :active="tab === 'edit'" :to="`/datasets/${datasetId}/edit`">Edit</BNavItem>
<BNavItem v-if="showError" title="Error" :active="tab === 'error'" :to="`/datasets/${datasetId}/error`">
Error
<BNavItem
title="Edit dataset attributes and metadata"
:active="tab === 'edit'"
:to="`/datasets/${datasetId}/edit`">
<FontAwesomeIcon :icon="faPen" class="mr-1" /> Edit
</BNavItem>
<BNavItem
v-if="showError"
title="View error information for this dataset"
:active="tab === 'error'"
:to="`/datasets/${datasetId}/error`">
<FontAwesomeIcon :icon="faBug" class="mr-1" /> Error
</BNavItem>
</BNav>
<div v-if="tab === 'preview'" class="h-100">
Expand All @@ -131,10 +150,37 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
:dataset-id="datasetId"
:visualization="preferredVisualization"
@load="iframeLoading = false" />
<div v-else-if="isAutoDownloadType" 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>
<p>File size: <strong v-html="bytesToString(dataset.file_size || 0, false)" /></p>
<a :href="`/datasets/${datasetId}/display`" class="btn btn-primary mt-2" download>
<FontAwesomeIcon :icon="faFileAlt" class="mr-1" /> Download File
</a>
</div>
</div>
<CenterFrame
v-else
:src="`/datasets/${datasetId}/display/?preview=True`"
:is_preview="true"
:is-preview="true"
@load="iframeLoading = false" />
</div>
<div v-else-if="tab === 'raw'" class="h-100">
<div v-if="isAutoDownloadType" 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>
<p>File size: <strong v-html="bytesToString(dataset.file_size || 0, false)" /></p>
<a :href="`/datasets/${datasetId}/display`" class="btn btn-primary mt-2" download>
<FontAwesomeIcon :icon="faFileAlt" class="mr-1" /> Download File
</a>
</div>
</div>
<CenterFrame
v-else
:src="`/datasets/${datasetId}/display/?preview=True`"
:is-preview="true"
@load="iframeLoading = false" />
</div>
<div v-else-if="tab === 'visualize'" class="d-flex flex-column overflow-hidden overflow-y">
Expand All @@ -161,6 +207,21 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
opacity: 1;
transition: all 0.25s ease;
overflow: hidden;

.datatype,
.dbkey,
.filesize {
margin-right: 1rem;
}

.prompt {
color: $text-muted;
margin-right: 0.25rem;
}

.blurb {
margin-bottom: 0.25rem;
}
}

.header-enter, /* change to header-enter-from with Vue 3 */
Expand All @@ -176,4 +237,16 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
font-size: $h5-font-size;
vertical-align: middle;
}

.auto-download-message {
display: flex;
align-items: flex-start;
justify-content: center;
height: 100%;

.alert {
max-width: 600px;
width: 100%;
}
}
</style>
8 changes: 5 additions & 3 deletions client/src/components/History/Content/ContentOptions.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script setup lang="ts">
import { faStop } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import axios from "axios";
import { BButton, BDropdown } from "bootstrap-vue";
import { faStop } from "font-awesome-6";
//@ts-ignore deprecated package without types (vue 2, remove this comment on vue 3 migration)
import { ScanEye } from "lucide-vue";
import { computed, type Ref, ref } from "vue";

import { getAppRoot } from "@/onload/loadConfig";
Expand Down Expand Up @@ -112,14 +114,14 @@ function onDisplay($event: MouseEvent) {
<BButton
v-if="isDataset"
v-b-tooltip.hover
title="Display"
title="View"
tabindex="0"
class="display-btn px-1"
size="sm"
variant="link"
:href="displayUrl"
@click.prevent.stop="onDisplay($event)">
<icon icon="eye" />
<ScanEye absolute-stroke-width :size="16" />
</BButton>
<BButton
v-if="writable && isHistoryItem"
Expand Down
44 changes: 44 additions & 0 deletions client/src/components/History/Content/Dataset/DatasetActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,24 @@ const showDownloads = computed(() => {
const showError = computed(() => {
return props.item.state === "error" || props.item.state === "failed_metadata";
});
const showInfo = computed(() => {
return props.item.state !== "noPermission";
});
const showVisualizations = computed(() => {
return !props.item.purged && ["ok", "failed_metadata", "error"].includes(props.item.state);
});
const showRerun = computed(() => {
return props.item.accessible && props.item.rerunnable && props.item.creating_job && props.item.state != "upload";
});
const reportErrorUrl = computed(() => {
return prependPath(props.itemUrls.reportError!);
});
const showDetailsUrl = computed(() => {
return prependPath(props.itemUrls.showDetails!);
});
const visualizeUrl = computed(() => {
return prependPath(props.itemUrls.visualize!);
});
const rerunUrl = computed(() => {
return prependPath(props.itemUrls.rerun!);
});
Expand All @@ -69,6 +81,14 @@ function onError() {
window.location.href = reportErrorUrl.value;
}

function onInfo() {
router.push(`/datasets/${props.item.id}/details`);
}

function onVisualize() {
router.push(`/visualizations?dataset_id=${props.item.id}`);
}

function onRerun() {
router.push(`/root?job_id=${props.item.creating_job}`);
}
Expand Down Expand Up @@ -103,6 +123,30 @@ function onRerun() {
<FontAwesomeIcon :icon="faLink" />
</BButton>

<BButton
v-if="showInfo"
v-b-tooltip.hover
class="info-btn px-1"
title="Dataset Details"
size="sm"
variant="link"
:href="showDetailsUrl"
@click.prevent.stop="onInfo">
<FontAwesomeIcon :icon="faInfoCircle" />
</BButton>

<BButton
v-if="showVisualizations"
v-b-tooltip.hover
class="visualize-btn px-1"
title="Visualize"
size="sm"
variant="link"
:href="visualizeUrl"
@click.prevent.stop="onVisualize">
<FontAwesomeIcon :icon="faChartBar" />
</BButton>

<BButton
v-if="showHighlight"
v-b-tooltip.hover
Expand Down
Loading
Loading