mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'release_24.2' into dev
This commit is contained in:
@@ -414,6 +414,7 @@ function renameElement(element: any, name: string) {
|
||||
:extensions="extensions"
|
||||
collection-type="list"
|
||||
:no-items="props.initialElements.length == 0 && !props.fromSelection"
|
||||
:show-upload="!fromSelection"
|
||||
@add-uploaded-files="addUploadedFiles"
|
||||
@on-update-datatype-toggle="changeDatatypeFilter"
|
||||
@onUpdateHideSourceItems="onUpdateHideSourceItems"
|
||||
|
||||
@@ -376,6 +376,7 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
|
||||
:extensions-toggle="removeExtensions"
|
||||
collection-type="paired"
|
||||
:no-items="props.initialElements.length == 0 && !props.fromSelection"
|
||||
:show-upload="!fromSelection"
|
||||
@add-uploaded-files="addUploadedFiles"
|
||||
@onUpdateHideSourceItems="onUpdateHideSourceItems"
|
||||
@clicked-create="clickedCreate"
|
||||
|
||||
@@ -881,6 +881,7 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
|
||||
:extensions="extensions"
|
||||
collection-type="list:paired"
|
||||
:no-items="props.initialElements.length == 0 && !props.fromSelection"
|
||||
:show-upload="!fromSelection"
|
||||
@add-uploaded-files="addUploadedFiles"
|
||||
@onUpdateHideSourceItems="hideSourceItems = $event"
|
||||
@clicked-create="clickedCreate"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { faChevronDown, faChevronUp, faPlus, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPlus, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BAlert, BButton, BFormCheckbox, BFormGroup, BFormInput, BLink, BTab, BTabs } from "bootstrap-vue";
|
||||
import { BAlert, BTab, BTabs } from "bootstrap-vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
@@ -13,6 +13,12 @@ import { useUserStore } from "@/stores/userStore";
|
||||
import localize from "@/utils/localization";
|
||||
import { orList } from "@/utils/strings";
|
||||
|
||||
import CollectionCreatorFooterButtons from "./CollectionCreatorFooterButtons.vue";
|
||||
import CollectionCreatorHelpHeader from "./CollectionCreatorHelpHeader.vue";
|
||||
import CollectionCreatorNoItemsMessage from "./CollectionCreatorNoItemsMessage.vue";
|
||||
import CollectionCreatorShowExtensions from "./CollectionCreatorShowExtensions.vue";
|
||||
import CollectionCreatorSourceOptions from "./CollectionCreatorSourceOptions.vue";
|
||||
import CollectionNameInput from "./CollectionNameInput.vue";
|
||||
import HelpText from "@/components/Help/HelpText.vue";
|
||||
import DefaultBox from "@/components/Upload/DefaultBox.vue";
|
||||
|
||||
@@ -40,12 +46,14 @@ interface Props {
|
||||
extensionsToggle?: boolean;
|
||||
noItems?: boolean;
|
||||
collectionType?: string;
|
||||
showUpload: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
suggestedName: "",
|
||||
extensions: undefined,
|
||||
extensionsToggle: false,
|
||||
showUpload: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -56,7 +64,6 @@ const emit = defineEmits<{
|
||||
(e: "add-uploaded-files", value: HDASummary[]): void;
|
||||
}>();
|
||||
|
||||
const isExpanded = ref(false);
|
||||
const currentTab = ref(Tabs.create);
|
||||
const collectionName = ref(props.suggestedName);
|
||||
const localHideSourceItems = ref(props.hideSourceItems);
|
||||
@@ -122,15 +129,14 @@ function addUploadedFiles(value: HDASummary[]) {
|
||||
emit("add-uploaded-files", value);
|
||||
}
|
||||
|
||||
function clickForHelp() {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
return isExpanded.value;
|
||||
}
|
||||
|
||||
function cancelCreate() {
|
||||
props.oncancel();
|
||||
}
|
||||
|
||||
function removeExtensionsToggle() {
|
||||
emit("remove-extensions-toggle");
|
||||
}
|
||||
|
||||
async function loadExtensions() {
|
||||
listExtensions.value = await getUploadDatatypes(false, AUTO_EXTENSION);
|
||||
extensionsSet.value = true;
|
||||
@@ -147,51 +153,15 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BTabs v-model="currentTab" fill justified>
|
||||
<BTab class="collection-creator" :title="localize('Create Collection')">
|
||||
<span>
|
||||
<span v-if="!showUpload" class="collection-creator">
|
||||
<div v-if="props.noItems">
|
||||
<BAlert variant="info" show>
|
||||
{{ localize("No items available to create a collection.") }}
|
||||
{{ localize("Exit and change your current history, or") }}
|
||||
<BLink class="text-decoration-none" @click.stop.prevent="currentTab = Tabs.upload">
|
||||
{{ localize("Upload some datasets.") }}
|
||||
</BLink>
|
||||
</BAlert>
|
||||
<CollectionCreatorNoItemsMessage @click-upload="currentTab = Tabs.upload" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="header flex-row no-flex">
|
||||
<div class="main-help well clear" :class="{ expanded: isExpanded }">
|
||||
<a
|
||||
class="more-help"
|
||||
href="javascript:void(0);"
|
||||
role="button"
|
||||
:title="localize('Expand or Close Help')"
|
||||
@click="clickForHelp">
|
||||
<div v-if="!isExpanded">
|
||||
<FontAwesomeIcon :icon="faChevronDown" />
|
||||
<span class="sr-only">{{ localize("Expand Help") }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<FontAwesomeIcon :icon="faChevronUp" />
|
||||
<span class="sr-only">{{ localize("Close Help") }}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="help-content">
|
||||
<!-- each collection that extends this will add their own help content -->
|
||||
<slot name="help-content"></slot>
|
||||
|
||||
<a
|
||||
class="more-help"
|
||||
href="javascript:void(0);"
|
||||
role="button"
|
||||
:title="localize('Expand or Close Help')"
|
||||
@click="clickForHelp">
|
||||
<span class="sr-only">{{ localize("Expand Help") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CollectionCreatorHelpHeader>
|
||||
<slot name="help-content"></slot>
|
||||
</CollectionCreatorHelpHeader>
|
||||
|
||||
<div class="middle flex-row flex-row-container">
|
||||
<slot name="middle-content"></slot>
|
||||
@@ -199,105 +169,103 @@ watch(
|
||||
|
||||
<div class="footer flex-row">
|
||||
<div class="vertically-spaced">
|
||||
<CollectionCreatorShowExtensions :extensions="extensions" />
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<CollectionCreatorSourceOptions
|
||||
v-model="localHideSourceItems"
|
||||
:render-extensions-toggle="renderExtensionsToggle"
|
||||
:extensions-toggle="extensionsToggle"
|
||||
@remove-extensions-toggle="removeExtensionsToggle" />
|
||||
<CollectionNameInput
|
||||
v-model="collectionName"
|
||||
:short-what-is-being-created="shortWhatIsBeingCreated" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionCreatorFooterButtons
|
||||
:short-what-is-being-created="shortWhatIsBeingCreated"
|
||||
:valid-input="validInput"
|
||||
@clicked-cancel="cancelCreate"
|
||||
@clicked-create="emit('clicked-create', collectionName)" />
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<BTabs v-else v-model="currentTab" fill justified>
|
||||
<BTab class="collection-creator" :title="localize('Create Collection')">
|
||||
<div v-if="props.noItems">
|
||||
<CollectionCreatorNoItemsMessage @click-upload="currentTab = Tabs.upload" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<CollectionCreatorHelpHeader>
|
||||
<slot name="help-content"></slot>
|
||||
</CollectionCreatorHelpHeader>
|
||||
|
||||
<div class="middle flex-row flex-row-container">
|
||||
<slot name="middle-content"></slot>
|
||||
</div>
|
||||
|
||||
<div class="footer flex-row">
|
||||
<div class="vertically-spaced">
|
||||
<CollectionCreatorShowExtensions :extensions="extensions" />
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<CollectionCreatorSourceOptions
|
||||
v-model="localHideSourceItems"
|
||||
:render-extensions-toggle="renderExtensionsToggle"
|
||||
:extensions-toggle="extensionsToggle" />
|
||||
<CollectionNameInput
|
||||
v-model="collectionName"
|
||||
:short-what-is-being-created="shortWhatIsBeingCreated" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionCreatorFooterButtons
|
||||
:short-what-is-being-created="shortWhatIsBeingCreated"
|
||||
:valid-input="validInput"
|
||||
@clicked-cancel="cancelCreate"
|
||||
@clicked-create="emit('clicked-create', collectionName)" />
|
||||
</div>
|
||||
</div>
|
||||
</BTab>
|
||||
<BTab>
|
||||
<template v-slot:title>
|
||||
<FontAwesomeIcon :icon="faUpload" fixed-width />
|
||||
<span>{{ localize("Upload Files to Add to Collection") }}</span>
|
||||
</template>
|
||||
<DefaultBox
|
||||
v-if="configOptions && extensionsSet"
|
||||
:chunk-upload-size="configOptions.chunkUploadSize"
|
||||
:default-db-key="configOptions.defaultDbKey"
|
||||
:default-extension="defaultExtension"
|
||||
:effective-extensions="props.extensions?.length ? validExtensions : listExtensions"
|
||||
:file-sources-configured="configOptions.fileSourcesConfigured"
|
||||
:ftp-upload-site="ftpUploadSite"
|
||||
:has-callback="false"
|
||||
:history-id="historyId"
|
||||
:list-db-keys="[]"
|
||||
disable-footer
|
||||
emit-uploaded
|
||||
@uploaded="addUploadedFiles"
|
||||
@dismiss="currentTab = Tabs.create">
|
||||
<template v-slot:footer>
|
||||
<div class="d-flex align-items-center justify-content-between mt-2">
|
||||
<BAlert v-if="extensions?.length" class="w-100 py-0" variant="secondary" show>
|
||||
<HelpText
|
||||
uri="galaxy.collections.collectionBuilder.filteredExtensions"
|
||||
:text="localize('Filtered extensions: ')" />
|
||||
uri="galaxy.collections.collectionBuilder.requiredUploadExtensions"
|
||||
:text="localize('Required extensions: ')" />
|
||||
<strong>{{ orList(extensions) }}</strong>
|
||||
</BAlert>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<BFormGroup class="inputs-form-group">
|
||||
<BFormCheckbox
|
||||
v-if="renderExtensionsToggle"
|
||||
name="remove-extensions"
|
||||
switch
|
||||
:checked="extensionsToggle"
|
||||
@input="emit('remove-extensions-toggle')">
|
||||
{{ localize("Remove file extensions?") }}
|
||||
</BFormCheckbox>
|
||||
|
||||
<div data-description="hide original elements">
|
||||
<BFormCheckbox v-model="localHideSourceItems" name="hide-originals" switch>
|
||||
<HelpText
|
||||
uri="galaxy.collections.collectionBuilder.hideOriginalElements"
|
||||
:text="localize('Hide original elements')" />
|
||||
</BFormCheckbox>
|
||||
</div>
|
||||
</BFormGroup>
|
||||
|
||||
<BFormGroup
|
||||
class="flex-gapx-1 d-flex align-items-center w-50 inputs-form-group"
|
||||
:label="localize('Name:')"
|
||||
label-for="collection-name">
|
||||
<BFormInput
|
||||
id="collection-name"
|
||||
v-model="collectionName"
|
||||
class="collection-name"
|
||||
:placeholder="localize('Enter a name for your new ' + shortWhatIsBeingCreated)"
|
||||
size="sm"
|
||||
required
|
||||
:state="!collectionName ? false : null" />
|
||||
</BFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions vertically-spaced d-flex justify-content-between">
|
||||
<BButton tabindex="-1" @click="cancelCreate">
|
||||
{{ localize("Cancel") }}
|
||||
</BButton>
|
||||
|
||||
<BButton
|
||||
class="create-collection"
|
||||
variant="primary"
|
||||
:disabled="!validInput"
|
||||
@click="emit('clicked-create', collectionName)">
|
||||
{{ localize("Create " + shortWhatIsBeingCreated) }}
|
||||
</BButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BTab>
|
||||
<BTab>
|
||||
<template v-slot:title>
|
||||
<FontAwesomeIcon :icon="faUpload" fixed-width />
|
||||
<span>{{ localize("Upload Files to Add to Collection") }}</span>
|
||||
</template>
|
||||
<!-- TODO: This is incomplete; need to return uploadValues to parent -->
|
||||
<DefaultBox
|
||||
v-if="configOptions && extensionsSet"
|
||||
:chunk-upload-size="configOptions.chunkUploadSize"
|
||||
:default-db-key="configOptions.defaultDbKey"
|
||||
:default-extension="defaultExtension"
|
||||
:effective-extensions="props.extensions?.length ? validExtensions : listExtensions"
|
||||
:file-sources-configured="configOptions.fileSourcesConfigured"
|
||||
:ftp-upload-site="ftpUploadSite"
|
||||
:has-callback="false"
|
||||
:history-id="historyId"
|
||||
:list-db-keys="[]"
|
||||
disable-footer
|
||||
emit-uploaded
|
||||
@uploaded="addUploadedFiles"
|
||||
@dismiss="currentTab = Tabs.create">
|
||||
<template v-slot:footer>
|
||||
<div class="d-flex align-items-center justify-content-between mt-2">
|
||||
<BAlert v-if="extensions?.length" class="w-100 py-0" variant="secondary" show>
|
||||
<HelpText
|
||||
uri="galaxy.collections.collectionBuilder.requiredUploadExtensions"
|
||||
:text="localize('Required extensions: ')" />
|
||||
<strong>{{ orList(extensions) }}</strong>
|
||||
</BAlert>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:emit-btn-txt>
|
||||
<FontAwesomeIcon :icon="faPlus" fixed-width />
|
||||
{{ localize("Add Uploaded") }}
|
||||
</template>
|
||||
</DefaultBox>
|
||||
</BTab>
|
||||
</BTabs>
|
||||
</template>
|
||||
<template v-slot:emit-btn-txt>
|
||||
<FontAwesomeIcon :icon="faPlus" fixed-width />
|
||||
{{ localize("Add Uploaded") }}
|
||||
</template>
|
||||
</DefaultBox>
|
||||
</BTab>
|
||||
</BTabs>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
interface Props {
|
||||
validInput: boolean;
|
||||
shortWhatIsBeingCreated: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "clicked-create"): void;
|
||||
(e: "clicked-cancel"): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="actions vertically-spaced d-flex justify-content-between">
|
||||
<BButton tabindex="-1" @click="emit('clicked-cancel')">
|
||||
{{ localize("Cancel") }}
|
||||
</BButton>
|
||||
|
||||
<BButton class="create-collection" variant="primary" :disabled="!validInput" @click="emit('clicked-create')">
|
||||
{{ localize("Create " + shortWhatIsBeingCreated) }}
|
||||
</BButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { ref } from "vue";
|
||||
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
const isExpanded = ref(false);
|
||||
|
||||
function clickForHelp() {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
return isExpanded.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header flex-row no-flex">
|
||||
<div class="main-help well clear" :class="{ expanded: isExpanded }">
|
||||
<a
|
||||
class="more-help"
|
||||
href="javascript:void(0);"
|
||||
role="button"
|
||||
:title="localize('Expand or Close Help')"
|
||||
@click="clickForHelp">
|
||||
<div v-if="!isExpanded">
|
||||
<FontAwesomeIcon :icon="faChevronDown" />
|
||||
<span class="sr-only">{{ localize("Expand Help") }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<FontAwesomeIcon :icon="faChevronUp" />
|
||||
<span class="sr-only">{{ localize("Close Help") }}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="help-content">
|
||||
<!-- each collection that extends this will add their own help content -->
|
||||
<slot></slot>
|
||||
|
||||
<a
|
||||
class="more-help"
|
||||
href="javascript:void(0);"
|
||||
role="button"
|
||||
:title="localize('Expand or Close Help')"
|
||||
@click="clickForHelp">
|
||||
<span class="sr-only">{{ localize("Expand Help") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { BAlert, BLink } from "bootstrap-vue";
|
||||
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "clicked-upload"): void;
|
||||
}>();
|
||||
</script>
|
||||
<template>
|
||||
<BAlert variant="info" show>
|
||||
{{ localize("No items available to create a collection.") }}
|
||||
{{ localize("Exit and change your current history, or") }}
|
||||
<BLink class="text-decoration-none" @click.stop.prevent="emit('clicked-upload')">
|
||||
{{ localize("Upload some datasets.") }}
|
||||
</BLink>
|
||||
</BAlert>
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { BAlert } from "bootstrap-vue";
|
||||
|
||||
import localize from "@/utils/localization";
|
||||
import { orList } from "@/utils/strings";
|
||||
|
||||
import HelpText from "@/components/Help/HelpText.vue";
|
||||
|
||||
interface Props {
|
||||
extensions?: string[];
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<BAlert v-if="extensions?.length" class="w-100 py-0" variant="secondary" show>
|
||||
<HelpText
|
||||
uri="galaxy.collections.collectionBuilder.filteredExtensions"
|
||||
:text="localize('Filtered extensions: ')" />
|
||||
<strong>{{ orList(extensions) }}</strong>
|
||||
</BAlert>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { BFormCheckbox, BFormGroup } from "bootstrap-vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
import HelpText from "@/components/Help/HelpText.vue";
|
||||
|
||||
interface Props {
|
||||
value: boolean;
|
||||
extensionsToggle?: boolean;
|
||||
renderExtensionsToggle?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const localHideSourceItems = ref(props.value);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: boolean): void;
|
||||
(e: "remove-extensions-toggle"): void;
|
||||
}>();
|
||||
|
||||
// Watch for external updates to value and sync with innerValue
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
localHideSourceItems.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
watch(localHideSourceItems, (newValue) => {
|
||||
emit("input", newValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BFormGroup class="inputs-form-group">
|
||||
<BFormCheckbox
|
||||
v-if="renderExtensionsToggle"
|
||||
name="remove-extensions"
|
||||
switch
|
||||
:checked="extensionsToggle"
|
||||
@input="emit('remove-extensions-toggle')">
|
||||
{{ localize("Remove file extensions?") }}
|
||||
</BFormCheckbox>
|
||||
|
||||
<div data-description="hide original elements">
|
||||
<BFormCheckbox v-model="localHideSourceItems" name="hide-originals" switch>
|
||||
<HelpText
|
||||
uri="galaxy.collections.collectionBuilder.hideOriginalElements"
|
||||
:text="localize('Hide original elements')" />
|
||||
</BFormCheckbox>
|
||||
</div>
|
||||
</BFormGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { BFormGroup, BFormInput } from "bootstrap-vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
shortWhatIsBeingCreated: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const name = ref(props.value);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: string): void;
|
||||
}>();
|
||||
|
||||
// Watch for external updates to value and sync with innerValue
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
name.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
watch(name, (newValue) => {
|
||||
emit("input", newValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BFormGroup
|
||||
class="flex-gapx-1 d-flex align-items-center w-50 inputs-form-group"
|
||||
:label="localize('Name:')"
|
||||
label-for="collection-name">
|
||||
<BFormInput
|
||||
id="collection-name"
|
||||
v-model="name"
|
||||
class="collection-name"
|
||||
:placeholder="localize('Enter a name for your new ' + shortWhatIsBeingCreated)"
|
||||
size="sm"
|
||||
required
|
||||
:state="!name ? false : null" />
|
||||
</BFormGroup>
|
||||
</template>
|
||||
@@ -9,6 +9,7 @@ import { computed, onMounted, ref } from "vue";
|
||||
import { GalaxyApi, type HDADetailed } from "@/api";
|
||||
import { fetchDatasetDetails } from "@/api/datasets";
|
||||
import { type JobDetails, type JobInputSummary } from "@/api/jobs";
|
||||
import { useConfig } from "@/composables/config";
|
||||
import { useMarkdown } from "@/composables/markdown";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import localize from "@/utils/localization";
|
||||
@@ -30,6 +31,7 @@ const userStore = useUserStore();
|
||||
const { currentUser } = storeToRefs(userStore);
|
||||
|
||||
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
|
||||
const { config, isConfigLoaded } = useConfig();
|
||||
|
||||
const message = ref("");
|
||||
const jobLoading = ref(true);
|
||||
@@ -47,6 +49,8 @@ const showForm = computed(() => {
|
||||
return noResult || hasError;
|
||||
});
|
||||
|
||||
const showWizard = computed(() => isConfigLoaded && config.value?.llm_api_configured);
|
||||
|
||||
async function getDatasetDetails() {
|
||||
datasetLoading.value = true;
|
||||
try {
|
||||
@@ -154,21 +158,22 @@ onMounted(async () => {
|
||||
<b id="dataset-error-tool-id" class="text-break">{{ jobDetails.tool_id }}</b
|
||||
>.
|
||||
</p>
|
||||
|
||||
<h4 class="mb-3 h-md">Possible Causes</h4>
|
||||
<p>
|
||||
<span>
|
||||
We can use AI to analyze the issue and suggest possible fixes. Please note that the diagnosis may
|
||||
not always be accurate.
|
||||
</span>
|
||||
</p>
|
||||
<BCard class="mb-2">
|
||||
<GalaxyWizard
|
||||
view="error"
|
||||
:query="jobDetails.tool_stderr"
|
||||
context="tool_error"
|
||||
:job-id="jobDetails.id" />
|
||||
</BCard>
|
||||
<template v-if="showWizard">
|
||||
<h4 class="mb-3 h-md">Possible Causes</h4>
|
||||
<p>
|
||||
<span>
|
||||
We can use AI to analyze the issue and suggest possible fixes. Please note that the diagnosis
|
||||
may not always be accurate.
|
||||
</span>
|
||||
</p>
|
||||
<BCard class="mb-2">
|
||||
<GalaxyWizard
|
||||
view="error"
|
||||
:query="jobDetails.tool_stderr"
|
||||
context="tool_error"
|
||||
:job-id="jobDetails.id" />
|
||||
</BCard>
|
||||
</template>
|
||||
|
||||
<DatasetErrorDetails
|
||||
:tool-stderr="jobDetails.tool_stderr"
|
||||
|
||||
@@ -69,8 +69,8 @@ describe("JobInformation/JobInformation.vue", () => {
|
||||
// table should exist
|
||||
expect(jobInfoTable).toBeTruthy();
|
||||
const rows = jobInfoTable.findAll("tr");
|
||||
// should contain 9 rows
|
||||
expect(rows.length).toBe(9);
|
||||
// should contain 10 rows
|
||||
expect(rows.length).toBe(10);
|
||||
});
|
||||
|
||||
it("stdout and stderr should be rendered", async () => {
|
||||
@@ -98,5 +98,6 @@ describe("JobInformation/JobInformation.vue", () => {
|
||||
{ id: "encoded-copied-from-job-id", backend_key: "copied_from_job_id" },
|
||||
];
|
||||
verifyValues(rendered_entries, jobInfoTable, jobResponse);
|
||||
expect(wrapper.find('td[data-description="galaxy-job-state"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,11 +39,15 @@ const stderr_position = computed(() => stderr_text.value.length);
|
||||
const runTime = computed(() => getJobDuration(job.value));
|
||||
|
||||
function jobStateIsTerminal(jobState) {
|
||||
return jobState && !NON_TERMINAL_STATES.includes(job.value.state);
|
||||
return jobState && !NON_TERMINAL_STATES.includes(jobState);
|
||||
}
|
||||
|
||||
function jobStateIsRunning(jobState) {
|
||||
return jobState == "running";
|
||||
}
|
||||
|
||||
const jobIsTerminal = computed(() => jobStateIsTerminal(job?.value?.state));
|
||||
|
||||
const jobIsRunning = computed(() => jobStateIsRunning(job?.value?.state));
|
||||
const routeToInvocation = computed(() => `/workflows/invocations/${invocationId.value}`);
|
||||
|
||||
const metadataDetail = ref({
|
||||
@@ -127,6 +131,7 @@ watch(
|
||||
<div>
|
||||
<JobDetailsProvider auto-refresh :job-id="props.job_id" @update:result="updateJob" />
|
||||
<JobConsoleOutputProvider
|
||||
v-if="jobIsRunning"
|
||||
auto-refresh
|
||||
:job-id="props.job_id"
|
||||
:stdout_position="stdout_position"
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"command_line": "commandline",
|
||||
"job_messages": ["test-msg1", "test-msg2", "test-msg3", "test-msg4"],
|
||||
"id": "test_id",
|
||||
"copied_from_job_id": "test_copied_from_job_id"
|
||||
"copied_from_job_id": "test_copied_from_job_id",
|
||||
"state": "running"
|
||||
}
|
||||
|
||||
@@ -1102,8 +1102,9 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin):
|
||||
|
||||
self._process_celery_config()
|
||||
|
||||
# load in the chat_prompts if openai is enabled
|
||||
self._load_chat_prompts()
|
||||
# load in the chat_prompts if openai api key is configured
|
||||
if self.openai_api_key:
|
||||
self._load_chat_prompts()
|
||||
|
||||
self.pretty_datetime_format = expand_pretty_datetime_format(self.pretty_datetime_format)
|
||||
try:
|
||||
@@ -1266,21 +1267,20 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin):
|
||||
self.file_source_temp_dir = os.path.abspath(self.file_source_temp_dir)
|
||||
|
||||
def _load_chat_prompts(self):
|
||||
if self.openai_api_key:
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
chat_prompts_path = os.path.join(current_dir, "chat_prompts.json")
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
chat_prompts_path = os.path.join(current_dir, "chat_prompts.json")
|
||||
|
||||
if os.path.exists(chat_prompts_path):
|
||||
try:
|
||||
with open(chat_prompts_path, encoding="utf-8") as file:
|
||||
data = json.load(file)
|
||||
self.chat_prompts = data.get("prompts", {})
|
||||
except json.JSONDecodeError as e:
|
||||
log.error(f"JSON decoding error in chat prompts file: {e}")
|
||||
except Exception as e:
|
||||
log.error(f"An error occurred while reading chat prompts file: {e}")
|
||||
else:
|
||||
log.warning(f"Chat prompts file not found at {chat_prompts_path}")
|
||||
if os.path.exists(chat_prompts_path):
|
||||
try:
|
||||
with open(chat_prompts_path, encoding="utf-8") as file:
|
||||
data = json.load(file)
|
||||
self.chat_prompts = data.get("prompts", {})
|
||||
except json.JSONDecodeError as e:
|
||||
log.error(f"JSON decoding error in chat prompts file: {e}")
|
||||
except Exception as e:
|
||||
log.error(f"An error occurred while reading chat prompts file: {e}")
|
||||
else:
|
||||
log.warning(f"Chat prompts file not found at {chat_prompts_path}")
|
||||
|
||||
def _process_celery_config(self):
|
||||
if self.celery_conf and self.celery_conf.get("result_backend") is None:
|
||||
|
||||
@@ -849,7 +849,11 @@ class Data(metaclass=DataMeta):
|
||||
job, converted_datasets, *_ = converter.execute(
|
||||
trans, incoming=params, set_output_hid=visible, history=history, flush_job=False
|
||||
)
|
||||
# We should only have a single converted output, but let's be defensive here
|
||||
n_converted_datasets = len(converted_datasets)
|
||||
for converted_dataset in converted_datasets.values():
|
||||
if converted_dataset.extension == "auto" and n_converted_datasets == 1:
|
||||
converted_dataset.extension = target_type
|
||||
original_dataset.attach_implicitly_converted_dataset(trans.sa_session, converted_dataset, target_type)
|
||||
trans.app.job_manager.enqueue(job, tool=converter)
|
||||
if len(params) > 0:
|
||||
|
||||
@@ -229,6 +229,7 @@ class ConfigSerializer(base.ModelSerializer):
|
||||
"fixed_delegated_auth": _defaults_to(False),
|
||||
"help_forum_api_url": _use_config,
|
||||
"enable_help_forum_tool_panel_integration": _use_config,
|
||||
"llm_api_configured": lambda item, key, **context: bool(item.openai_api_key),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -83,9 +83,9 @@ class OutputsFilterExpression(Linter):
|
||||
tool_xml = getattr(tool_source, "xml_tree", None)
|
||||
if not tool_xml:
|
||||
return
|
||||
for filter in tool_xml.findall("./outputs//filter"):
|
||||
for filter in tool_xml.findall("./outputs/*/filter"):
|
||||
try:
|
||||
ast.parse(filter.text, mode="eval")
|
||||
ast.parse(filter.text.strip(), mode="eval")
|
||||
except Exception as e:
|
||||
lint_ctx.warn(
|
||||
f"Filter '{filter.text}' is no valid expression: {str(e)}",
|
||||
|
||||
@@ -668,9 +668,9 @@ def raise_error_if_valiation_fails(
|
||||
raise AssertionError("Validator logic problem - computed validation value must be boolean")
|
||||
if message is None:
|
||||
message = validator.message
|
||||
if message is None:
|
||||
if not message:
|
||||
message = DEFAULT_VALIDATOR_MESSAGE
|
||||
assert message
|
||||
assert message is not None
|
||||
if value_to_show and "%s" in message:
|
||||
message = message % value_to_show
|
||||
negate = validator.negate
|
||||
|
||||
@@ -1688,7 +1688,7 @@ def adapt_tool_source_dict(processed_dict: ToolTestDict) -> ToolTestDescriptionD
|
||||
|
||||
if not error_in_test_definition:
|
||||
processed_test_dict = cast(ValidToolTestDict, processed_dict)
|
||||
maxseconds = _get_maxseconds(processed_test_dict)
|
||||
maxseconds = processed_test_dict.get("maxseconds")
|
||||
output_collections = processed_test_dict.get("output_collections", [])
|
||||
if "num_outputs" in processed_test_dict and processed_test_dict["num_outputs"]:
|
||||
num_outputs = int(processed_test_dict["num_outputs"])
|
||||
@@ -1750,10 +1750,6 @@ def _get_test_name(test_dict: Union[ToolTestDict, ToolTestDescriptionDict], test
|
||||
return name
|
||||
|
||||
|
||||
def _get_maxseconds(test_dict: Union[ToolTestDict, ToolTestDescriptionDict]) -> int:
|
||||
return int(cast(Union[str, int], test_dict.get("maxseconds") or DEFAULT_TOOL_TEST_WAIT or 86400))
|
||||
|
||||
|
||||
def expanded_inputs_from_json(expanded_inputs_json: ExpandedToolInputsJsonified) -> ExpandedToolInputs:
|
||||
loaded_inputs: ExpandedToolInputs = {}
|
||||
for key, value in expanded_inputs_json.items():
|
||||
@@ -1829,7 +1825,7 @@ class ToolTestDescription:
|
||||
self.inputs = expanded_inputs_from_json(json_dict.get("inputs", {}))
|
||||
self.tool_id = json_dict["tool_id"]
|
||||
self.tool_version = json_dict.get("tool_version")
|
||||
self.maxseconds = _get_maxseconds(json_dict)
|
||||
self.maxseconds = json_dict.get("maxseconds")
|
||||
|
||||
def test_data(self):
|
||||
"""
|
||||
@@ -1839,31 +1835,31 @@ class ToolTestDescription:
|
||||
|
||||
def to_dict(self) -> ToolTestDescriptionDict:
|
||||
inputs = expanded_inputs_to_json(self.inputs)
|
||||
return ToolTestDescriptionDict(
|
||||
{
|
||||
"inputs": inputs,
|
||||
"outputs": self.outputs,
|
||||
"output_collections": [_.to_dict() for _ in self.output_collections],
|
||||
"num_outputs": self.num_outputs,
|
||||
"command_line": self.command_line,
|
||||
"command_version": self.command_version,
|
||||
"stdout": self.stdout,
|
||||
"stderr": self.stderr,
|
||||
"expect_exit_code": self.expect_exit_code,
|
||||
"expect_failure": self.expect_failure,
|
||||
"expect_test_failure": self.expect_test_failure,
|
||||
"name": self.name,
|
||||
"test_index": self.test_index,
|
||||
"tool_id": self.tool_id,
|
||||
"tool_version": self.tool_version,
|
||||
"required_files": self.required_files,
|
||||
"required_data_tables": self.required_data_tables,
|
||||
"required_loc_files": self.required_loc_files,
|
||||
"error": self.error,
|
||||
"exception": self.exception,
|
||||
"maxseconds": self.maxseconds,
|
||||
}
|
||||
)
|
||||
test_description_def: ToolTestDescriptionDict = {
|
||||
"inputs": inputs,
|
||||
"outputs": self.outputs,
|
||||
"output_collections": [_.to_dict() for _ in self.output_collections],
|
||||
"num_outputs": self.num_outputs,
|
||||
"command_line": self.command_line,
|
||||
"command_version": self.command_version,
|
||||
"stdout": self.stdout,
|
||||
"stderr": self.stderr,
|
||||
"expect_exit_code": self.expect_exit_code,
|
||||
"expect_failure": self.expect_failure,
|
||||
"expect_test_failure": self.expect_test_failure,
|
||||
"name": self.name,
|
||||
"test_index": self.test_index,
|
||||
"tool_id": self.tool_id,
|
||||
"tool_version": self.tool_version,
|
||||
"required_files": self.required_files,
|
||||
"required_data_tables": self.required_data_tables,
|
||||
"required_loc_files": self.required_loc_files,
|
||||
"error": self.error,
|
||||
"exception": self.exception,
|
||||
}
|
||||
if self.maxseconds is not None:
|
||||
test_description_def["maxseconds"] = self.maxseconds
|
||||
return ToolTestDescriptionDict(**test_description_def)
|
||||
|
||||
|
||||
def test_data_iter(required_files):
|
||||
|
||||
@@ -2205,6 +2205,24 @@ class TestToolsApi(ApiTestCase, TestsTools):
|
||||
exception_raised = e
|
||||
assert exception_raised, "Expected invalid column selection to fail job"
|
||||
|
||||
@skip_without_tool("implicit_conversion_format_input")
|
||||
def test_implicit_conversion_input_dataset_tracking(self):
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
compressed_path = self.test_data_resolver.get_filename("1.fastqsanger.gz")
|
||||
with open(compressed_path, "rb") as fh:
|
||||
dataset = self.dataset_populator.new_dataset(
|
||||
history_id, content=fh, file_type="fastqsanger.gz", wait=True
|
||||
)
|
||||
outputs = self._run(
|
||||
"Grep1", history_id=history_id, inputs={"data": {"src": "hda", "id": dataset["id"]}}, assert_ok=True
|
||||
)
|
||||
job_details = self.dataset_populator.get_job_details(outputs["jobs"][0]["id"], full=True).json()
|
||||
assert job_details["inputs"]["input"]["id"] != dataset["id"]
|
||||
converted_input = self.dataset_populator.get_history_dataset_details(
|
||||
history_id=history_id, content_id=job_details["inputs"]["input"]["id"]
|
||||
)
|
||||
assert converted_input["extension"] == "fastqsanger"
|
||||
|
||||
@skip_without_tool("column_multi_param")
|
||||
def test_implicit_conversion_and_reduce(self):
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
|
||||
@@ -68,6 +68,21 @@ class TestTestParsing(TestCase):
|
||||
tool_source = get_tool_source(path)
|
||||
self.tool_source = tool_source
|
||||
|
||||
def test_maxseconds_not_filled_with_default(self):
|
||||
self._init_tool_for_path(functional_test_tool_path("simple_constructs.xml"))
|
||||
test_dicts = list(self._parse_tests())
|
||||
assert test_dicts[0].maxseconds is None
|
||||
assert "maxseconds" not in test_dicts[0].to_dict()
|
||||
|
||||
def test_maxseconds_parsed(self):
|
||||
self._init_tool_for_path(functional_test_tool_path("maxseconds.xml"))
|
||||
test_def = next(iter(self._parse_tests()))
|
||||
assert test_def.maxseconds == 5
|
||||
test_dict = test_def.to_dict()
|
||||
print(test_dict)
|
||||
assert "maxseconds" in test_dict
|
||||
assert test_dict["maxseconds"] == 5
|
||||
|
||||
def test_simple_state_parsing(self):
|
||||
self._init_tool_for_path(functional_test_tool_path("simple_constructs.xml"))
|
||||
test_dicts = self._parse_tests()
|
||||
|
||||
@@ -700,7 +700,7 @@ OUTPUTS_FILTER_EXPRESSION = """
|
||||
<outputs>
|
||||
<data name="another_valid_name" format="fasta" label="a label">
|
||||
<filter>an invalid condition</filter>
|
||||
<filter>an and condition</filter>
|
||||
<filter> an and condition </filter>
|
||||
</data>
|
||||
<collection name="yet_another_valid_name" type="list" format="fasta" label="another label">
|
||||
<filter>another invalid condition</filter>
|
||||
|
||||
Reference in New Issue
Block a user