Skip to content

Workflow license and creator edit keyboard access #18936

New issue

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

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

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 107 additions & 125 deletions client/src/components/License/LicenseSelector.vue
Original file line number Diff line number Diff line change
@@ -1,130 +1,112 @@
<script setup lang="ts">
import { watchImmediate } from "@vueuse/core";
import { BAlert } from "bootstrap-vue";
import { computed, ref } from "vue";
import Multiselect from "vue-multiselect";

import { GalaxyApi } from "@/api";
import { type components } from "@/api/schema";
import { errorMessageAsString } from "@/utils/simple-error";

import License from "@/components/License/License.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";

const defaultLicense: LicenseType = {
licenseId: null,
name: "*Do not specify a license.*",
};

type LicenseMetadataModel = components["schemas"]["LicenseMetadataModel"];
type LicenseType = {
licenseId: string | null;
name: string;
};

interface Props {
inputLicense: string;
}

const props = defineProps<Props>();

const emit = defineEmits<{
(e: "onLicense", license: string | null): void;
}>();

const licensesLoading = ref(false);
const errorMessage = ref<string>("");
const currentLicense = ref<LicenseType>();
const licenses = ref<LicenseMetadataModel[] | undefined>([]);

const licenseOptions = computed(() => {
const options: LicenseType[] = [];

options.push(defaultLicense);

for (const license of licenses.value || []) {
if (license.licenseId == currentLicense.value?.licenseId || license.recommended) {
options.push({
licenseId: license.licenseId,
name: license.name,
});
}
}

return options;
});

function onLicense(license: LicenseType) {
emit("onLicense", license.licenseId);
}

async function fetchLicenses() {
const { error, data } = await GalaxyApi().GET("/api/licenses");

if (error) {
errorMessage.value = errorMessageAsString(error) || "Unable to fetch licenses.";
}

licenses.value = data;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we put this in a store, so it doesn't reload on re-rendering this component?


licensesLoading.value = false;
}

async function setCurrentLicense() {
if (!licenses.value?.length && !licensesLoading.value) {
licensesLoading.value = true;

await fetchLicenses();
}

const inputLicense = props.inputLicense;

currentLicense.value = (licenses.value || []).find((l) => l.licenseId == inputLicense) || defaultLicense;
}

watchImmediate(
() => props.inputLicense,
() => {
setCurrentLicense();
}
);
</script>

<template>
<div v-if="editLicense">
<LoadingSpan v-if="licensesLoading" message="Loading licenses..." />
<b-form-select
<div>
<BAlert v-if="licensesLoading" variant="info" class="m-0" show>
<LoadingSpan message="Loading licenses" />
</BAlert>
<BAlert v-else-if="errorMessage" variant="danger" class="m-0" show>
{{ errorMessage }}
</BAlert>
<Multiselect
v-else
v-model="license"
v-model="currentLicense"
data-description="license select"
:options="licenseOptions"></b-form-select>
<License v-if="currentLicenseInfo" :license-id="license" :input-license-info="currentLicenseInfo">
<template v-slot:buttons>
<span v-b-tooltip.hover title="Save License"
><FontAwesomeIcon data-description="license save" icon="save" @click="onSave"
/></span>
<span v-b-tooltip.hover title="Cancel Edit"><FontAwesomeIcon icon="times" @click="disableEdit" /></span>
</template>
</License>
<div v-else>
<a href="#" @click.prevent="onSave">Save without license</a> or
<a href="#" @click.prevent="editLicense = false">cancel edit.</a>
</div>
</div>
<div v-else-if="license" data-description="license selector" :data-license="license">
<License :license-id="license">
<template v-slot:buttons>
<span v-b-tooltip.hover title="Edit License"
><FontAwesomeIcon icon="edit" data-description="edit license link" @click="editLicense = true"
/></span>
</template>
</License>
</div>
<div v-else data-description="license selector" data-license="null">
<i
><a href="#" data-description="edit license link" @click.prevent="editLicense = true"
>Specify a license for this workflow.</a
></i
>
track-by="licenseId"
:options="licenseOptions"
label="name"
placeholder="Select a license"
@select="onLicense" />
<License v-if="currentLicense?.licenseId" :license-id="currentLicense.licenseId" />
</div>
</template>

<script>
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEdit, faSave, faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import axios from "axios";
import BootstrapVue from "bootstrap-vue";
import LoadingSpan from "components/LoadingSpan";
import { getAppRoot } from "onload/loadConfig";
import Vue from "vue";

import License from "./License";

library.add(faSave);
library.add(faTimes);
library.add(faEdit);

Vue.use(BootstrapVue);

export default {
components: { License, LoadingSpan, FontAwesomeIcon },
props: {
inputLicense: {
type: String,
},
},
data() {
return {
license: this.inputLicense || null,
licensesLoading: false,
licenses: [],
editLicense: false,
};
},
computed: {
currentLicenseInfo() {
for (const license of this.licenses) {
if (license.licenseId == this.license) {
return license;
}
}
return null;
},
licenseOptions() {
const options = [];
options.push({
value: null,
text: "*Do not specify a license.*",
});
for (const license of this.licenses) {
if (license.licenseId == this.license || license.recommended) {
options.push({
value: license.licenseId,
text: license.name,
});
}
}
return options;
},
},
watch: {
inputLicense() {
this.license = this.inputLicense;
},
},
mounted() {
const url = `${getAppRoot()}api/licenses`;
axios
.get(url)
.then((response) => response.data)
.then((data) => {
this.licenses = data;
this.licensesLoading = false;
})
.catch((e) => {
console.error(e);
});
},
methods: {
onSave() {
this.onLicense(this.license);
this.disableEdit();
},
disableEdit() {
this.editLicense = false;
},
onLicense(license) {
this.$emit("onLicense", license);
},
},
};
</script>
24 changes: 18 additions & 6 deletions client/src/components/SchemaOrg/CreatorEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@
<div v-for="(creator, index) in creatorsCurrent" :key="index">
<CreatorViewer :creator="creator">
<template v-slot:buttons>
<span v-b-tooltip.hover title="Edit Creator"
><FontAwesomeIcon icon="edit" @click="onEdit(index)"
/></span>
<span v-b-tooltip.hover title="Remove Creator">
<FontAwesomeIcon icon="times" @click="onRemove(index)" />
</span>
<BButton
v-b-tooltip.hover
class="inline-icon-button"
variant="link"
size="sm"
title="Edit Creator"
@click="onEdit(index)">
<FontAwesomeIcon icon="edit" />
</BButton>
<BButton
v-b-tooltip.hover
class="inline-icon-button"
variant="link"
size="sm"
title="Remove Creator"
@click="onRemove(index)">
<FontAwesomeIcon icon="times" />
</BButton>
</template>
</CreatorViewer>
</div>
Expand Down
13 changes: 4 additions & 9 deletions client/src/utils/navigation/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -747,15 +747,10 @@ workflow_editor:
canvas_body: '#workflow-canvas'
edit_annotation: '#workflow-annotation'
edit_name: '#workflow-name'
license_selector:
selector: 'license selector'
type: data-description
edit_license_link:
selector: 'edit license link'
type: data-description
license_select:
selector: 'license select'
type: data-description
license_selector: '[data-description="license select"]'
license_current_value: '[data-description="license select"] .multiselect__single'
license_selector_input: '[data-description="license select"] input.multiselect__input'
license_selector_option: '[data-description="license select"] .multiselect__element'
license_save:
selector: 'license save'
type: data-description
Expand Down
11 changes: 6 additions & 5 deletions lib/galaxy/selenium/navigates_galaxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,11 +1204,12 @@ def rule_builder_enter_source_text(self, json):
text_area_elem.send_keys(json)

def workflow_editor_set_license(self, license: str) -> None:
editor = self.components.workflow_editor
editor.edit_license_link.wait_for_and_click()
select = editor.license_select.wait_for_select()
select.select_by_value(license)
editor.license_save.wait_for_and_click()
license_selector = self.components.workflow_editor.license_selector
license_selector.wait_for_and_click()
license_selector.wait_for_and_send_keys(license)

license_selector_option = self.components.workflow_editor.license_selector_option
license_selector_option.wait_for_and_click()

def workflow_editor_click_option(self, option_label):
self.workflow_editor_click_options()
Expand Down
5 changes: 2 additions & 3 deletions lib/galaxy_test/selenium/test_workflow_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,14 @@ def test_edit_license(self):
name = self.workflow_create_new()
editor.canvas_body.wait_for_visible()
editor.license_selector.wait_for_visible()
editor.license_selector.assert_no_axe_violations_with_impact_of_at_least("serious")
editor.license_selector.assert_data_value("license", "null")
assert "Do not specify" in editor.license_current_value.wait_for_text()

self.workflow_editor_set_license("MIT")
self.workflow_editor_click_save()

self.workflow_index_open_with_name(name)
editor.license_selector.wait_for_visible()
editor.license_selector.assert_data_value("license", "MIT")
assert "MIT" in editor.license_current_value.wait_for_text()

@selenium_test
def test_optional_select_data_field(self):
Expand Down
Loading