Merge branch 'release_24.2' into dev

This commit is contained in:
Nicola Soranzo
2025-01-13 16:10:31 +00:00
23 changed files with 455 additions and 215 deletions
@@ -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"
}
+16 -16
View File
@@ -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:
+4
View File
@@ -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:
+1
View File
@@ -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),
}
+2 -2
View File
@@ -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
+27 -31
View File
@@ -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):
+18
View File
@@ -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()
+1 -1
View File
@@ -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>