mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Refactor export wizards for more shared code
This commit is contained in:
@@ -2,6 +2,10 @@ import { GalaxyApi, type ObjectExportTaskResponse } from "@/api";
|
||||
import { ExportRecordModel } from "@/components/Common/models/exportRecordModel";
|
||||
import { rethrowSimple } from "@/utils/simple-error";
|
||||
|
||||
import type { components } from "./schema";
|
||||
|
||||
export type WriteStoreToPayload = components["schemas"]["WriteStoreToPayload"];
|
||||
|
||||
/**
|
||||
* Gets a list of recent export records for the current user.
|
||||
* This includes exports to remote file sources (not short-term storage downloads).
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { BCard, BCardGroup, BCardImg, BCardTitle } from "bootstrap-vue";
|
||||
|
||||
import { borderVariant } from "@/components/Common/Wizard/utils";
|
||||
import type { ExportPlugin } from "@/components/Workflow/Invocation/Export/Plugins";
|
||||
import { useMarkdown } from "@/composables/markdown";
|
||||
|
||||
import ExternalLink from "@/components/ExternalLink.vue";
|
||||
|
||||
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
|
||||
|
||||
interface Props {
|
||||
plugins: ExportPlugin[];
|
||||
modelValue: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:model-value", value: string): void;
|
||||
}>();
|
||||
|
||||
function selectPlugin(plugin: ExportPlugin) {
|
||||
emit("update:model-value", plugin.exportParams.modelStoreFormat);
|
||||
}
|
||||
|
||||
function isSelected(plugin: ExportPlugin): boolean {
|
||||
return props.modelValue === plugin.exportParams.modelStoreFormat;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BCardGroup deck>
|
||||
<BCard
|
||||
v-for="plugin in plugins"
|
||||
:key="plugin.id"
|
||||
:data-export-format="plugin.exportParams.modelStoreFormat"
|
||||
class="export-format-card"
|
||||
:border-variant="borderVariant(isSelected(plugin))"
|
||||
@click="selectPlugin(plugin)">
|
||||
<BCardTitle>
|
||||
<b>{{ plugin.title }}</b>
|
||||
</BCardTitle>
|
||||
<div v-if="plugin.img">
|
||||
<BCardImg :src="plugin.img" :alt="plugin.title" class="export-format-img" />
|
||||
<br />
|
||||
<ExternalLink v-if="plugin.url" :href="plugin.url">
|
||||
<b>Learn more</b>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div v-else v-html="renderMarkdown(plugin.markdownDescription)" />
|
||||
</BCard>
|
||||
</BCardGroup>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.export-format-card {
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.export-format-img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-height: 100px;
|
||||
max-width: 100%;
|
||||
max-inline-size: -webkit-fill-available;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { BFormCheckbox, BFormGroup } from "bootstrap-vue";
|
||||
|
||||
interface Props {
|
||||
includeFiles: boolean;
|
||||
includeDeleted: boolean;
|
||||
includeHidden: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:includeFiles", value: boolean): void;
|
||||
(e: "update:includeDeleted", value: boolean): void;
|
||||
(e: "update:includeHidden", value: boolean): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BFormGroup label="Dataset files included in the export:">
|
||||
<BFormCheckbox
|
||||
:checked="props.includeFiles"
|
||||
:disabled="props.disabled"
|
||||
switch
|
||||
data-test-id="include-files-checkbox"
|
||||
@change="emit('update:includeFiles', $event)">
|
||||
Include Active Files
|
||||
</BFormCheckbox>
|
||||
|
||||
<BFormCheckbox
|
||||
:checked="props.includeDeleted"
|
||||
:disabled="props.disabled"
|
||||
switch
|
||||
data-test-id="include-deleted-checkbox"
|
||||
@change="emit('update:includeDeleted', $event)">
|
||||
Include Deleted (not purged)
|
||||
</BFormCheckbox>
|
||||
|
||||
<BFormCheckbox
|
||||
:checked="props.includeHidden"
|
||||
:disabled="props.disabled"
|
||||
switch
|
||||
data-test-id="include-hidden-checkbox"
|
||||
@change="emit('update:includeHidden', $event)">
|
||||
Include Hidden
|
||||
</BFormCheckbox>
|
||||
</BFormGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
|
||||
import type { FilterFileSourcesOptions } from "@/api/remoteFiles";
|
||||
import { useFileSources } from "@/composables/fileSources";
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
import FilesInput from "@/components/FilesDialog/FilesInput.vue";
|
||||
|
||||
interface Props {
|
||||
directory: string;
|
||||
fileName?: string;
|
||||
fileExtension?: string;
|
||||
resourceName?: string;
|
||||
filterOptions?: FilterFileSourcesOptions;
|
||||
showFileName?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fileName: "",
|
||||
fileExtension: "",
|
||||
resourceName: "data",
|
||||
filterOptions: () => ({ exclude: ["rdm"] }),
|
||||
showFileName: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:directory", value: string): void;
|
||||
(e: "update:fileName", value: string): void;
|
||||
}>();
|
||||
|
||||
const { hasWritable: hasWritableFileSources } = useFileSources(props.filterOptions);
|
||||
|
||||
const directoryDescription = localize(`Select a 'repository' to export ${props.resourceName} to.`);
|
||||
const fileNameDescription = localize("Give the exported file a name.");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="!hasWritableFileSources" class="text-warning">
|
||||
No writable file sources are configured. Please contact your administrator.
|
||||
</p>
|
||||
<template v-else>
|
||||
<BFormGroup id="fieldset-directory" label-for="directory" :description="directoryDescription" class="mt-3">
|
||||
<FilesInput
|
||||
id="directory"
|
||||
:value="props.directory"
|
||||
mode="directory"
|
||||
:require-writable="true"
|
||||
:filter-options="props.filterOptions"
|
||||
data-test-id="export-destination-input"
|
||||
@input="emit('update:directory', $event)" />
|
||||
</BFormGroup>
|
||||
|
||||
<BFormGroup
|
||||
v-if="props.showFileName"
|
||||
id="fieldset-name"
|
||||
label-for="name"
|
||||
:description="fileNameDescription"
|
||||
class="mt-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<BFormInput
|
||||
id="name"
|
||||
:value="props.fileName"
|
||||
placeholder="Name"
|
||||
required
|
||||
data-test-id="export-file-name-input"
|
||||
@input="emit('update:fileName', $event)" />
|
||||
<span v-if="props.fileExtension" class="ml-2 text-muted">.{{ props.fileExtension }}</span>
|
||||
</div>
|
||||
</BFormGroup>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,21 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { BAlert, BCard, BCardGroup, BCardTitle, BFormCheckbox, BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
import { BAlert, BCard, BCardGroup, BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
|
||||
import { AVAILABLE_EXPORT_FORMATS } from "@/api/histories.export";
|
||||
import type { ModelStoreFormat } from "@/api";
|
||||
import { useWizard } from "@/components/Common/Wizard/useWizard";
|
||||
import { borderVariant } from "@/components/Common/Wizard/utils";
|
||||
import type { ExportPlugin } from "@/components/Workflow/Invocation/Export/Plugins";
|
||||
import { useFileSources } from "@/composables/fileSources";
|
||||
import { useMarkdown } from "@/composables/markdown";
|
||||
import { DEFAULT_EXPORT_PARAMS } from "@/composables/shortTermStorage";
|
||||
|
||||
import type { HistoryExportData, HistoryExportDestination } from "./types";
|
||||
|
||||
import ExportFormatSelector from "@/components/Common/ExportFormatSelector.vue";
|
||||
import ExportIncludeOptions from "@/components/Common/ExportIncludeOptions.vue";
|
||||
import ExportRemoteSourceSelector from "@/components/Common/ExportRemoteSourceSelector.vue";
|
||||
import RDMCredentialsInfo from "@/components/Common/RDMCredentialsInfo.vue";
|
||||
import RDMDestinationSelector from "@/components/Common/RDMDestinationSelector.vue";
|
||||
import GenericWizard from "@/components/Common/Wizard/GenericWizard.vue";
|
||||
import ExternalLink from "@/components/ExternalLink.vue";
|
||||
import FilesInput from "@/components/FilesDialog/FilesInput.vue";
|
||||
import FileSourceNameSpan from "@/components/FileSources/FileSourceNameSpan.vue";
|
||||
|
||||
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
|
||||
@@ -30,6 +32,34 @@ const {
|
||||
|
||||
const resource = "history";
|
||||
|
||||
// History-specific export plugins
|
||||
const historyExportPlugins: ExportPlugin[] = [
|
||||
{
|
||||
id: "ro-crate",
|
||||
title: "RO-Crate",
|
||||
img: "https://www.researchobject.org/ro-crate/assets/img/ro-crate-wide.svg",
|
||||
url: "https://www.researchobject.org/ro-crate/",
|
||||
markdownDescription: `RO-Crate is a community effort to establish a lightweight approach to packaging research data with their metadata. It makes use of schema.org annotations in JSON-LD format.`,
|
||||
exportParams: {
|
||||
modelStoreFormat: "rocrate.zip",
|
||||
includeFiles: true,
|
||||
includeDeleted: false,
|
||||
includeHidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tar-gz",
|
||||
title: "Compressed TGZ",
|
||||
markdownDescription: `A compressed tar.gz archive containing the history and its metadata.`,
|
||||
exportParams: {
|
||||
modelStoreFormat: "tar.gz" as ModelStoreFormat,
|
||||
includeFiles: true,
|
||||
includeDeleted: false,
|
||||
includeHidden: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface ExportDestinationInfo {
|
||||
destination: HistoryExportDestination;
|
||||
label: string;
|
||||
@@ -84,12 +114,13 @@ const exportDestinationSummary = computed(() => {
|
||||
|
||||
const exportDestinationTargets = computed(initializeExportDestinations);
|
||||
|
||||
const exportFormats = computed(() => AVAILABLE_EXPORT_FORMATS);
|
||||
const selectedPlugin = computed(
|
||||
() =>
|
||||
historyExportPlugins.find((p) => p.exportParams.modelStoreFormat === exportData.modelStoreFormat) ||
|
||||
historyExportPlugins[0],
|
||||
);
|
||||
|
||||
const exportFormatName = computed(() => {
|
||||
const format = exportFormats.value.find((f) => f.id === exportData.modelStoreFormat);
|
||||
return format ? format.name : "";
|
||||
});
|
||||
const exportFormatName = computed(() => selectedPlugin.value?.title ?? "");
|
||||
|
||||
const zenodoSource = computed(() => getZenodoSource());
|
||||
|
||||
@@ -155,6 +186,13 @@ function onRecordSelected(recordUri: string) {
|
||||
exportData.remoteUri = recordUri;
|
||||
}
|
||||
|
||||
function onFormatSelected(formatId: string) {
|
||||
const plugin = historyExportPlugins.find((p) => p.exportParams.modelStoreFormat === formatId);
|
||||
if (plugin) {
|
||||
exportData.modelStoreFormat = plugin.exportParams.modelStoreFormat;
|
||||
}
|
||||
}
|
||||
|
||||
async function exportHistory() {
|
||||
emit("onExport", exportData);
|
||||
}
|
||||
@@ -232,36 +270,10 @@ function resetWizard() {
|
||||
:is-busy="isBusy"
|
||||
@submit="exportHistory">
|
||||
<div v-if="wizard.isCurrent('select-format')">
|
||||
<BCardGroup deck>
|
||||
<BCard
|
||||
v-for="format in exportFormats"
|
||||
:key="format.id"
|
||||
:data-history-export-format="format.id"
|
||||
class="wizard-selection-card"
|
||||
:border-variant="borderVariant(exportData.modelStoreFormat == format.id)"
|
||||
@click="exportData.modelStoreFormat = format.id">
|
||||
<BCardTitle>
|
||||
<b>{{ format.name }}</b>
|
||||
</BCardTitle>
|
||||
<div v-if="format.id === 'rocrate.zip'">
|
||||
<p>
|
||||
RO-Crate is a community effort to establish a lightweight approach to packaging research
|
||||
data with their metadata. It makes use of schema.org annotations in JSON-LD format.
|
||||
</p>
|
||||
<img
|
||||
class="card-img"
|
||||
src="https://www.researchobject.org/ro-crate/assets/img/ro-crate-wide.svg"
|
||||
alt="RO-Crate Logo" />
|
||||
<br />
|
||||
<ExternalLink href="https://www.researchobject.org/ro-crate/">
|
||||
<b>Learn more</b>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>A compressed tar.gz archive containing the history and its metadata.</p>
|
||||
</div>
|
||||
</BCard>
|
||||
</BCardGroup>
|
||||
<ExportFormatSelector
|
||||
:model-value="exportData.modelStoreFormat"
|
||||
:plugins="historyExportPlugins"
|
||||
@update:model-value="onFormatSelected" />
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('select-destination')">
|
||||
@@ -283,18 +295,10 @@ function resetWizard() {
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('setup-remote')">
|
||||
<BFormGroup
|
||||
id="fieldset-directory"
|
||||
label-for="directory"
|
||||
:description="`Select a 'repository' to export ${resource} to.`"
|
||||
class="mt-3">
|
||||
<FilesInput
|
||||
id="directory"
|
||||
v-model="exportData.remoteUri"
|
||||
mode="directory"
|
||||
:require-writable="true"
|
||||
:filter-options="{ exclude: ['rdm'] }" />
|
||||
</BFormGroup>
|
||||
<ExportRemoteSourceSelector
|
||||
:directory="exportData.remoteUri"
|
||||
:resource-name="resource"
|
||||
@update:directory="exportData.remoteUri = $event" />
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('setup-rdm')">
|
||||
@@ -337,15 +341,13 @@ function resetWizard() {
|
||||
required />
|
||||
</BFormGroup>
|
||||
|
||||
<BFormGroup label="Dataset files included in the package:">
|
||||
<BFormCheckbox v-model="exportData.includeFiles" switch> Include Active Files </BFormCheckbox>
|
||||
|
||||
<BFormCheckbox v-model="exportData.includeDeleted" switch>
|
||||
Include Deleted (not purged)
|
||||
</BFormCheckbox>
|
||||
|
||||
<BFormCheckbox v-model="exportData.includeHidden" switch> Include Hidden </BFormCheckbox>
|
||||
</BFormGroup>
|
||||
<ExportIncludeOptions
|
||||
:include-files="exportData.includeFiles"
|
||||
:include-deleted="exportData.includeDeleted"
|
||||
:include-hidden="exportData.includeHidden"
|
||||
@update:include-files="exportData.includeFiles = $event"
|
||||
@update:include-deleted="exportData.includeDeleted = $event"
|
||||
@update:include-hidden="exportData.includeHidden = $event" />
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { BAlert, BCard, BCardGroup, BCardImg, BCardTitle, BFormCheckbox, BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
import { BAlert, BCard, BCardGroup, BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
import { computed, onUnmounted, reactive, ref, watch } from "vue";
|
||||
|
||||
import { GalaxyApi } from "@/api";
|
||||
@@ -21,11 +21,13 @@ import { useShortTermStorageMonitor } from "@/composables/shortTermStorageMonito
|
||||
import { useTaskMonitor } from "@/composables/taskMonitor";
|
||||
import { errorMessageAsString } from "@/utils/simple-error";
|
||||
|
||||
import ExportFormatSelector from "@/components/Common/ExportFormatSelector.vue";
|
||||
import ExportIncludeOptions from "@/components/Common/ExportIncludeOptions.vue";
|
||||
import ExportRemoteSourceSelector from "@/components/Common/ExportRemoteSourceSelector.vue";
|
||||
import RDMCredentialsInfo from "@/components/Common/RDMCredentialsInfo.vue";
|
||||
import RDMDestinationSelector from "@/components/Common/RDMDestinationSelector.vue";
|
||||
import GenericWizard from "@/components/Common/Wizard/GenericWizard.vue";
|
||||
import ExternalLink from "@/components/ExternalLink.vue";
|
||||
import FilesInput from "@/components/FilesDialog/FilesInput.vue";
|
||||
import FileSourceNameSpan from "@/components/FileSources/FileSourceNameSpan.vue";
|
||||
import ExistingInvocationExportProgressCard from "@/components/Workflow/Invocation/Export/ExistingInvocationExportProgressCard.vue";
|
||||
|
||||
@@ -323,6 +325,14 @@ function initializeExportData(): InvocationExportData {
|
||||
};
|
||||
}
|
||||
|
||||
function onFormatSelected(formatId: string) {
|
||||
// Find the plugin that uses this format
|
||||
const plugin = exportPlugins.value.find((p) => p.exportParams.modelStoreFormat === formatId);
|
||||
if (plugin) {
|
||||
exportData.exportPluginFormat = plugin.id;
|
||||
}
|
||||
}
|
||||
|
||||
function resetWizard() {
|
||||
const initialExportData = initializeExportData();
|
||||
Object.assign(exportData, initialExportData);
|
||||
@@ -374,27 +384,10 @@ onUnmounted(() => {
|
||||
:is-busy="isWizardBusy"
|
||||
@submit="exportInvocation">
|
||||
<div v-if="wizard.isCurrent('select-format')">
|
||||
<BCardGroup deck>
|
||||
<BCard
|
||||
v-for="plugin in exportPlugins"
|
||||
:key="plugin.id"
|
||||
:data-invocation-export-type="plugin.id"
|
||||
class="wizard-selection-card"
|
||||
:border-variant="borderVariant(exportData.exportPluginFormat == plugin.id)"
|
||||
@click="exportData.exportPluginFormat = plugin.id">
|
||||
<BCardTitle>
|
||||
<b>{{ plugin.title }}</b>
|
||||
</BCardTitle>
|
||||
<div v-if="plugin.img">
|
||||
<BCardImg :src="plugin.img" :alt="plugin.title" />
|
||||
<br />
|
||||
<ExternalLink v-if="plugin.url" :href="plugin.url">
|
||||
<b>Learn more</b>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div v-else v-html="renderMarkdown(plugin.markdownDescription)" />
|
||||
</BCard>
|
||||
</BCardGroup>
|
||||
<ExportFormatSelector
|
||||
:plugins="exportPlugins"
|
||||
:model-value="selectedExportPlugin.exportParams.modelStoreFormat"
|
||||
@update:model-value="onFormatSelected" />
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('select-destination')">
|
||||
@@ -415,18 +408,11 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('setup-remote')">
|
||||
<BFormGroup
|
||||
id="fieldset-directory"
|
||||
label-for="directory"
|
||||
:description="`Select a 'repository' to export ${resource} to.`"
|
||||
class="mt-3">
|
||||
<FilesInput
|
||||
id="directory"
|
||||
v-model="exportData.remoteUri"
|
||||
mode="directory"
|
||||
:require-writable="true"
|
||||
:filter-options="{ exclude: ['rdm'] }" />
|
||||
</BFormGroup>
|
||||
<ExportRemoteSourceSelector
|
||||
:directory="exportData.remoteUri"
|
||||
:resource-name="resource"
|
||||
:filter-options="{ exclude: ['rdm'] }"
|
||||
@update:directory="exportData.remoteUri = $event" />
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('setup-rdm')">
|
||||
@@ -495,15 +481,14 @@ onUnmounted(() => {
|
||||
required />
|
||||
</BFormGroup>
|
||||
|
||||
<BFormGroup v-if="canIncludeData" label="Dataset files included in the package:">
|
||||
<BFormCheckbox v-model="exportData.includeFiles" switch> Include Active Files </BFormCheckbox>
|
||||
|
||||
<BFormCheckbox v-model="exportData.includeDeleted" switch>
|
||||
Include Deleted (not purged)
|
||||
</BFormCheckbox>
|
||||
|
||||
<BFormCheckbox v-model="exportData.includeHidden" switch> Include Hidden </BFormCheckbox>
|
||||
</BFormGroup>
|
||||
<ExportIncludeOptions
|
||||
v-if="canIncludeData"
|
||||
:include-files="exportData.includeFiles"
|
||||
:include-deleted="exportData.includeDeleted"
|
||||
:include-hidden="exportData.includeHidden"
|
||||
@update:include-files="exportData.includeFiles = $event"
|
||||
@update:include-deleted="exportData.includeDeleted = $event"
|
||||
@update:include-hidden="exportData.includeHidden = $event" />
|
||||
|
||||
<br />
|
||||
|
||||
@@ -531,13 +516,3 @@ onUnmounted(() => {
|
||||
</BAlert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-height: 100px;
|
||||
max-width: 100%;
|
||||
max-inline-size: -webkit-fill-available;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,11 +23,11 @@ export function getInvocationExportPluginByType(pluginType: InvocationExportPlug
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a UI plugin that can export a workflow invocation to a particular format.
|
||||
* Generic base interface for export plugins that can be used across different export wizards.
|
||||
*/
|
||||
export interface InvocationExportPlugin {
|
||||
export interface ExportPlugin {
|
||||
/** The unique identifier for the plugin. */
|
||||
id: InvocationExportPluginType;
|
||||
id: string;
|
||||
/** The title of the plugin. */
|
||||
title: string;
|
||||
/** The image URL to display for the plugin. Usually a logo. */
|
||||
@@ -36,10 +36,18 @@ export interface InvocationExportPlugin {
|
||||
url?: string;
|
||||
/** A markdown description of the plugin. */
|
||||
markdownDescription: string;
|
||||
/** The parameters to use when exporting the invocation. */
|
||||
/** The parameters to use when exporting. */
|
||||
exportParams: ExportParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a UI plugin that can export a workflow invocation to a particular format.
|
||||
*/
|
||||
export interface InvocationExportPlugin extends ExportPlugin {
|
||||
/** The unique identifier for the plugin. */
|
||||
id: InvocationExportPluginType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an action that can be taken in addition to the main export action.
|
||||
*/
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { BButton, BCard, BCardGroup, BCardTitle, BFormCheckbox, BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
import { BButton } from "bootstrap-vue";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import type { FilterFileSourcesOptions } from "@/api/remoteFiles";
|
||||
import type { components } from "@/api/schema";
|
||||
import type { ModelStoreFormat } from "@/api";
|
||||
import type { WriteStoreToPayload } from "@/api/exports";
|
||||
import { useWizard } from "@/components/Common/Wizard/useWizard";
|
||||
import { borderVariant } from "@/components/Common/Wizard/utils";
|
||||
import {
|
||||
AVAILABLE_INVOCATION_EXPORT_PLUGINS,
|
||||
type InvocationExportPlugin,
|
||||
} from "@/components/Workflow/Invocation/Export/Plugins";
|
||||
import { useFileSources } from "@/composables/fileSources";
|
||||
import { useMarkdown } from "@/composables/markdown";
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
import ExportFormatSelector from "@/components/Common/ExportFormatSelector.vue";
|
||||
import ExportIncludeOptions from "@/components/Common/ExportIncludeOptions.vue";
|
||||
import ExportRemoteSourceSelector from "@/components/Common/ExportRemoteSourceSelector.vue";
|
||||
import GenericWizard from "@/components/Common/Wizard/GenericWizard.vue";
|
||||
import FilesInput from "@/components/FilesDialog/FilesInput.vue";
|
||||
import FileSourceNameSpan from "@/components/FileSources/FileSourceNameSpan.vue";
|
||||
|
||||
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
|
||||
|
||||
export type WriteStoreToPayload = components["schemas"]["WriteStoreToPayload"];
|
||||
type ModelStoreFormat = components["schemas"]["ModelStoreFormat"];
|
||||
|
||||
interface Props {
|
||||
initialConfig?: Partial<WriteStoreToPayload> & { fileName?: string; directory?: string };
|
||||
}
|
||||
@@ -36,9 +29,6 @@ const emit = defineEmits<{
|
||||
(e: "cancel"): void;
|
||||
}>();
|
||||
|
||||
const defaultExportFilterOptions: FilterFileSourcesOptions = { exclude: ["rdm"] };
|
||||
const { hasWritable: hasWritableFileSources } = useFileSources(defaultExportFilterOptions);
|
||||
|
||||
// Internal state for the wizard UI
|
||||
const directory = ref(props.initialConfig.directory || "");
|
||||
const fileName = ref(props.initialConfig.fileName || "galaxy-workflow-export");
|
||||
@@ -57,9 +47,6 @@ const selectedPlugin = computed(
|
||||
() => exportPlugins.find((p) => p.exportParams.modelStoreFormat === modelStoreFormat.value) || exportPlugins[0],
|
||||
);
|
||||
|
||||
const directoryDescription = computed(() => localize("Select a 'repository' to export the workflow results to."));
|
||||
const nameDescription = computed(() => localize("Give the exported file a name."));
|
||||
|
||||
const wizard = useWizard({
|
||||
"select-format": {
|
||||
label: "Select format",
|
||||
@@ -98,6 +85,10 @@ function onSubmit() {
|
||||
function onCancel() {
|
||||
emit("cancel");
|
||||
}
|
||||
|
||||
function onFormatChange(format: string) {
|
||||
modelStoreFormat.value = format as ModelStoreFormat;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -109,69 +100,31 @@ function onCancel() {
|
||||
container-component="div"
|
||||
@submit="onSubmit">
|
||||
<div v-if="wizard.isCurrent('select-format')">
|
||||
<BCardGroup deck>
|
||||
<BCard
|
||||
v-for="plugin in exportPlugins"
|
||||
:key="plugin.id"
|
||||
:data-export-format="plugin.exportParams.modelStoreFormat"
|
||||
class="wizard-selection-card"
|
||||
:border-variant="borderVariant(modelStoreFormat === plugin.exportParams.modelStoreFormat)"
|
||||
@click="modelStoreFormat = plugin.exportParams.modelStoreFormat">
|
||||
<BCardTitle>
|
||||
<b>{{ plugin.title }}</b>
|
||||
</BCardTitle>
|
||||
<div v-html="renderMarkdown(plugin.markdownDescription)" />
|
||||
</BCard>
|
||||
</BCardGroup>
|
||||
<ExportFormatSelector
|
||||
:model-value="modelStoreFormat"
|
||||
:plugins="exportPlugins"
|
||||
@update:model-value="onFormatChange" />
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('select-destination')">
|
||||
<p v-if="!hasWritableFileSources" class="text-warning">
|
||||
No writable file sources are configured. Please contact your administrator.
|
||||
</p>
|
||||
<template v-else>
|
||||
<BFormGroup
|
||||
id="fieldset-directory"
|
||||
label-for="directory"
|
||||
:description="directoryDescription"
|
||||
class="mt-3">
|
||||
<FilesInput
|
||||
id="directory"
|
||||
v-model="directory"
|
||||
mode="directory"
|
||||
:require-writable="true"
|
||||
:filter-options="defaultExportFilterOptions"
|
||||
data-test-id="export-destination-input" />
|
||||
</BFormGroup>
|
||||
|
||||
<BFormGroup id="fieldset-name" label-for="name" :description="nameDescription" class="mt-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<BFormInput
|
||||
id="name"
|
||||
v-model="fileName"
|
||||
placeholder="Name"
|
||||
required
|
||||
data-test-id="export-file-name-input" />
|
||||
<span class="ml-2 text-muted">.{{ modelStoreFormat }}</span>
|
||||
</div>
|
||||
</BFormGroup>
|
||||
</template>
|
||||
<ExportRemoteSourceSelector
|
||||
:directory="directory"
|
||||
:file-name="fileName"
|
||||
:file-extension="modelStoreFormat"
|
||||
resource-name="workflow results"
|
||||
:show-file-name="true"
|
||||
@update:directory="directory = $event"
|
||||
@update:file-name="fileName = $event" />
|
||||
</div>
|
||||
|
||||
<div v-if="wizard.isCurrent('configure-options')">
|
||||
<BFormGroup label="Dataset files included in the export:">
|
||||
<BFormCheckbox v-model="includeFiles" switch data-test-id="include-files-checkbox">
|
||||
Include Active Files
|
||||
</BFormCheckbox>
|
||||
|
||||
<BFormCheckbox v-model="includeDeleted" switch data-test-id="include-deleted-checkbox">
|
||||
Include Deleted (not purged)
|
||||
</BFormCheckbox>
|
||||
|
||||
<BFormCheckbox v-model="includeHidden" switch data-test-id="include-hidden-checkbox">
|
||||
Include Hidden
|
||||
</BFormCheckbox>
|
||||
</BFormGroup>
|
||||
<ExportIncludeOptions
|
||||
:include-files="includeFiles"
|
||||
:include-deleted="includeDeleted"
|
||||
:include-hidden="includeHidden"
|
||||
@update:include-files="includeFiles = $event"
|
||||
@update:include-deleted="includeDeleted = $event"
|
||||
@update:include-hidden="includeHidden = $event" />
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -200,15 +153,6 @@ function onCancel() {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wizard-selection-card {
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
background-color: var(--bs-light);
|
||||
padding: 1rem;
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
import { BButton, BFormCheckbox, BFormGroup, BModal } from "bootstrap-vue";
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
|
||||
import type { WriteStoreToPayload } from "@/api/exports";
|
||||
import { useConfig } from "@/composables/config";
|
||||
import { useFileSources } from "@/composables/fileSources";
|
||||
|
||||
import ExportOnCompleteWizard, { type WriteStoreToPayload } from "./ExportOnCompleteWizard.vue";
|
||||
import ExportOnCompleteWizard from "./ExportOnCompleteWizard.vue";
|
||||
import FileSourceNameSpan from "@/components/FileSources/FileSourceNameSpan.vue";
|
||||
import FormCard from "@/components/Form/FormCard.vue";
|
||||
|
||||
@@ -14,6 +15,12 @@ interface OnCompleteAction {
|
||||
export_to_file_source?: WriteStoreToPayload;
|
||||
}
|
||||
|
||||
interface ComponentState {
|
||||
sendNotification: boolean;
|
||||
exportEnabled: boolean;
|
||||
exportConfig: WriteStoreToPayload;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: OnCompleteAction[];
|
||||
}
|
||||
@@ -32,16 +39,18 @@ const notificationSystemEnabled = computed(
|
||||
|
||||
const showExportWizard = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
const defaultExportConfig: WriteStoreToPayload = {
|
||||
target_uri: "",
|
||||
model_store_format: "rocrate.zip",
|
||||
include_files: true,
|
||||
include_hidden: false,
|
||||
include_deleted: false,
|
||||
};
|
||||
|
||||
const state = reactive<ComponentState>({
|
||||
sendNotification: false,
|
||||
exportEnabled: false,
|
||||
exportConfig: {
|
||||
target_uri: "",
|
||||
model_store_format: "rocrate.zip",
|
||||
include_files: true,
|
||||
include_hidden: false,
|
||||
include_deleted: false,
|
||||
} as WriteStoreToPayload,
|
||||
exportConfig: { ...defaultExportConfig },
|
||||
});
|
||||
|
||||
// Initialize state from value prop (Vue 2 style v-model)
|
||||
@@ -92,13 +101,7 @@ function openExportWizard() {
|
||||
|
||||
function clearExport() {
|
||||
state.exportEnabled = false;
|
||||
state.exportConfig = {
|
||||
target_uri: "",
|
||||
model_store_format: "rocrate.zip",
|
||||
include_files: true,
|
||||
include_hidden: false,
|
||||
include_deleted: false,
|
||||
};
|
||||
state.exportConfig = { ...defaultExportConfig };
|
||||
}
|
||||
|
||||
const exportSummary = computed(() => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { BAlert, BFormInput, BModal, BOverlay } from "bootstrap-vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onBeforeMount, ref, watch } from "vue";
|
||||
|
||||
import type { WriteStoreToPayload } from "@/api/exports";
|
||||
import type { WorkflowInvocationRequestInputs } from "@/api/invocations";
|
||||
import type { ToolIdentifier } from "@/api/tools";
|
||||
import type { DataOption } from "@/components/Form/Elements/FormData/types";
|
||||
@@ -27,7 +28,7 @@ import { invokeWorkflow } from "./services";
|
||||
|
||||
import WorkflowAnnotation from "../WorkflowAnnotation.vue";
|
||||
import WorkflowNavigationTitle from "../WorkflowNavigationTitle.vue";
|
||||
import ExportOnCompleteWizard, { type WriteStoreToPayload } from "./ExportOnCompleteWizard.vue";
|
||||
import ExportOnCompleteWizard from "./ExportOnCompleteWizard.vue";
|
||||
import WorkflowHelpDisplay from "./WorkflowHelpDisplay.vue";
|
||||
import WorkflowRunGraph from "./WorkflowRunGraph.vue";
|
||||
import WorkflowStorageConfiguration from "./WorkflowStorageConfiguration.vue";
|
||||
|
||||
Reference in New Issue
Block a user