From 8f6127a41fd49d510137e94bf8606844c48a49b3 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 21 Feb 2023 13:19:05 +0100 Subject: [PATCH 1/9] add configurable gtn integration --- client/src/components/Tool/ToolCard.vue | 7 + .../Tool/ToolTutorialRecommendations.vue | 74 ++++++++ .../src/composables/toolTrainingMaterial.ts | 168 ++++++++++++++++++ client/src/style/scss/ui.scss | 4 + doc/source/admin/galaxy_options.rst | 32 ++++ lib/galaxy/config/sample/galaxy.yml.sample | 24 ++- lib/galaxy/config/schemas/config_schema.yml | 29 +++ lib/galaxy/managers/configuration.py | 3 + 8 files changed, 335 insertions(+), 6 deletions(-) create mode 100644 client/src/components/Tool/ToolTutorialRecommendations.vue create mode 100644 client/src/composables/toolTrainingMaterial.ts diff --git a/client/src/components/Tool/ToolCard.vue b/client/src/components/Tool/ToolCard.vue index 54770c5590e..692b39faa3a 100644 --- a/client/src/components/Tool/ToolCard.vue +++ b/client/src/components/Tool/ToolCard.vue @@ -9,6 +9,7 @@ import Heading from "components/Common/Heading"; import ToolSelectPreferredObjectStore from "./ToolSelectPreferredObjectStore"; import ToolTargetPreferredObjectStorePopover from "./ToolTargetPreferredObjectStorePopover"; import { getAppRoot } from "onload/loadConfig"; +import ToolTutorialRecommendations from "./ToolTutorialRecommendations.vue"; import { computed, ref, watch } from "vue"; import { useCurrentUser } from "composables/user"; @@ -165,6 +166,12 @@ function onUpdatePreferredObjectStoreId(selectedToolPreferredObjectStoreId) {
+ +
Help diff --git a/client/src/components/Tool/ToolTutorialRecommendations.vue b/client/src/components/Tool/ToolTutorialRecommendations.vue new file mode 100644 index 00000000000..fb3541cd0f9 --- /dev/null +++ b/client/src/components/Tool/ToolTutorialRecommendations.vue @@ -0,0 +1,74 @@ + + + diff --git a/client/src/composables/toolTrainingMaterial.ts b/client/src/composables/toolTrainingMaterial.ts new file mode 100644 index 00000000000..3589806bd74 --- /dev/null +++ b/client/src/composables/toolTrainingMaterial.ts @@ -0,0 +1,168 @@ +import { useConfig } from "./config"; +import { computed, ref, watch, type Ref } from "vue"; + +type Name = string; +type Repo = string; +type ID = string; + +type TrainingId = `${Repo}/${Name}` | ID; + +type TrainingDetails = { + tool_id: Array< + [ + string, // ID (unused) + string // Version + ] + >; + tutorials: Array< + [ + string, // tutorial ID (unused) + string, // Title + string, // Category + string // URL + ] + >; +}; + +type TrainingMaterialResponse = { + [id: TrainingId]: TrainingDetails; +}; + +type Config = { + tool_training_recommendations: boolean; + tool_training_recommendations_api_url: string; + tool_training_recommendations_link: string; +}; + +export type TutorialDetails = { + category: string; + title: string; + url: URL; +}; + +const cachedResponse: Ref = ref(null); + +export function useToolTrainingMaterial(id: ID, name: Name, version: string, repo?: Repo) { + const { config, isLoaded }: { config: Ref; isLoaded: Ref } = useConfig(); + const apiEnabled = computed(() => { + return Boolean( + isLoaded.value && + config.value.tool_training_recommendations && + config.value.tool_training_recommendations_api_url + ); + }); + + watch( + () => isLoaded.value, + async () => { + if (!isLoaded.value) { + return; + } + + if (apiEnabled.value) { + const res = await fetch(config.value.tool_training_recommendations_api_url); + + if (res.ok) { + cachedResponse.value = await res.json(); + } + } + }, + { immediate: true } + ); + + const identifier = computed(() => { + if (repo) { + return `${repo}/${name.toLowerCase()}`; + } else { + return id; + } + }); + + const trainingAvailable = computed(() => { + if (!apiEnabled.value || !cachedResponse.value) { + return false; + } + + return Object.keys(cachedResponse.value).includes(identifier.value); + }); + + const trainingDetails = computed(() => { + if (!trainingAvailable.value) { + return null; + } + + return cachedResponse.value?.[identifier.value] ?? null; + }); + + const trainingCategories = computed(() => { + if (!trainingDetails.value) { + return []; + } + + const categories = new Set(); + + trainingDetails.value.tutorials.forEach((tutorial) => { + categories.add(tutorial[2]); + }); + + return Array.from(categories); + }); + + const tutorialDetails = computed(() => { + if (!trainingDetails.value) { + return []; + } + + const details: TutorialDetails[] = []; + + trainingDetails.value.tutorials.forEach((tutorial) => { + details.push({ + title: tutorial[1], + category: tutorial[2], + url: new URL(tutorial[3], config.value.tool_training_recommendations_api_url), + }); + }); + + return details; + }); + + const allTutorialsUrl = computed(() => { + if (!isLoaded.value || !config.value.tool_training_recommendations_link) { + return; + } + + let url = config.value.tool_training_recommendations_link; + + url = url.replace("{training_tool_identifier}", identifier.value); + url = url.replace("{tool_id}", id); + url = url.replace("{name}", name); + url = url.replace("{repository}", repo ?? ""); + url = url.replace("{version}", version); + + return url; + }); + + const versionAvailable = computed(() => { + if (!trainingDetails.value) { + return false; + } + + for (let i = 0; i < trainingDetails.value.tool_id.length; i++) { + const element = trainingDetails.value.tool_id[i]!; + + if (element[1] === version) { + return true; + } + } + + return false; + }); + + return { + trainingAvailable, + trainingCategories, + tutorialDetails, + allTutorialsUrl, + versionAvailable, + }; +} diff --git a/client/src/style/scss/ui.scss b/client/src/style/scss/ui.scss index 8f2e316092d..244c6cb99b9 100644 --- a/client/src/style/scss/ui.scss +++ b/client/src/style/scss/ui.scss @@ -382,6 +382,10 @@ $ui-margin-horizontal-large: $margin-v * 2; column-gap: 0.25rem; } +.flex-gapy-1 { + row-gap: 0.25rem; +} + /* Heading Sizes */ .h-xl { font-size: $h1-font-size; diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst index 6d75b11385e..d70b3c2290a 100644 --- a/doc/source/admin/galaxy_options.rst +++ b/doc/source/admin/galaxy_options.rst @@ -5080,4 +5080,36 @@ :Type: bool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``tool_training_recommendations`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:Description: + Displays a link to training material, if any includes the current + tool. When activated the following options also need to be set: + - tool_training_recommendations_link +:Default: ``true`` +:Type: bool + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``tool_training_recommendations_link`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:Description: + Template URL to display all tutorials containing current tool. + Valid template inputs are: {repository} {name} {id} + {version} +:Default: ``https://training.galaxyproject.org/training-material/by-tool/{repository}/{name}.html`` +:Type: str + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``tool_training_recommendations_api_url`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:Description: + URL to API describing tutorials containing specific tools. When + CORS is used, make sure to add this host. +:Default: ``https://training.galaxyproject.org/training-material/api/top-tools.json`` +:Type: str diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample index 9e34bd51206..e039f198b4a 100644 --- a/lib/galaxy/config/sample/galaxy.yml.sample +++ b/lib/galaxy/config/sample/galaxy.yml.sample @@ -1,21 +1,21 @@ # Galaxy is configured by default to be usable in a single-user development # environment. To tune the application for a multi-user production # environment, see the documentation at: -# +# # https://docs.galaxyproject.org/en/master/admin/production.html -# +# # Throughout this sample configuration file, except where stated otherwise, # uncommented values override the default if left unset, whereas commented # values are set to the default value. Relative paths are relative to the root # Galaxy directory. -# +# # Examples of many of these options are explained in more detail in the Galaxy # Community Hub. -# +# # https://galaxyproject.org/admin/config -# +# # Config hackers are encouraged to check there before asking for help. -# +# # Configuration for Gravity process manager. # ``uwsgi:`` section will be ignored if Galaxy is started via Gravity commands (e.g ``./run.sh``, ``galaxy`` or ``galaxyctl``). gravity: @@ -2710,3 +2710,15 @@ galaxy: # integration. #enable_beacon_integration: false + # Displays a link to training material, if any includes the current + # tool. When activated the following options also need to be set: - + # tool_training_recommendations_link + #tool_training_recommendations: true + + # Template URL to display all tutorials containing current tool. Valid + # template inputs are: {repository} {name} {id} {version} + #tool_training_recommendations_link: https://training.galaxyproject.org/training-material/by-tool/{repository}/{name}.html + + # URL to API describing tutorials containing specific tools. When CORS + # is used, make sure to add this host. + #tool_training_recommendations_api_url: https://training.galaxyproject.org/training-material/api/top-tools.json diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index ccc0955444b..befb4e953b6 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -3698,3 +3698,32 @@ mapping: desc: | Enables user preferences and api endpoint for the beacon integration. + tool_training_recommendations: + type: bool + default: true + required: false + desc: | + Displays a link to training material, if any includes the current tool. + When activated the following options also need to be set: + - tool_training_recommendations_link + + tool_training_recommendations_link: + type: str + default: https://training.galaxyproject.org/training-material/by-tool/{training_tool_identifier}.html + required: false + desc: | + Template URL to display all tutorials containing current tool. + Valid template inputs are: + {repository} + {name} + {tool_id} + {training_tool_identifier} + {version} + + tool_training_recommendations_api_url: + type: str + default: https://training.galaxyproject.org/training-material/api/top-tools.json + required: false + desc: | + URL to API describing tutorials containing specific tools. + When CORS is used, make sure to add this host. diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py index dd87c229486..55e3cc018e8 100644 --- a/lib/galaxy/managers/configuration.py +++ b/lib/galaxy/managers/configuration.py @@ -203,6 +203,9 @@ class ConfigSerializer(base.ModelSerializer): "user_library_import_dir_available": lambda item, key, **context: bool(item.get("user_library_import_dir")), "welcome_directory": _use_config, "themes": _use_config, + "tool_training_recommendations": _use_config, + "tool_training_recommendations_link": _use_config, + "tool_training_recommendations_api_url": _use_config, } From 0a26f58bc40954fe234db67fd854061105b77ec3 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:32:27 +0100 Subject: [PATCH 2/9] fix config description --- doc/source/admin/galaxy_options.rst | 9 +++++---- lib/galaxy/config/sample/galaxy.yml.sample | 10 ++++++---- lib/galaxy/config/schemas/config_schema.yml | 3 ++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst index d70b3c2290a..30093f71de5 100644 --- a/doc/source/admin/galaxy_options.rst +++ b/doc/source/admin/galaxy_options.rst @@ -5087,7 +5087,8 @@ :Description: Displays a link to training material, if any includes the current tool. When activated the following options also need to be set: - - tool_training_recommendations_link + tool_training_recommendations_link, + tool_training_recommendations_api_url :Default: ``true`` :Type: bool @@ -5098,9 +5099,9 @@ :Description: Template URL to display all tutorials containing current tool. - Valid template inputs are: {repository} {name} {id} - {version} -:Default: ``https://training.galaxyproject.org/training-material/by-tool/{repository}/{name}.html`` + Valid template inputs are: {repository} {name} {tool_id} + {training_tool_identifier} {version} +:Default: ``https://training.galaxyproject.org/training-material/by-tool/{training_tool_identifier}.html`` :Type: str diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample index e039f198b4a..02261b95e66 100644 --- a/lib/galaxy/config/sample/galaxy.yml.sample +++ b/lib/galaxy/config/sample/galaxy.yml.sample @@ -2711,13 +2711,15 @@ galaxy: #enable_beacon_integration: false # Displays a link to training material, if any includes the current - # tool. When activated the following options also need to be set: - - # tool_training_recommendations_link + # tool. When activated the following options also need to be set: + # tool_training_recommendations_link, + # tool_training_recommendations_api_url #tool_training_recommendations: true # Template URL to display all tutorials containing current tool. Valid - # template inputs are: {repository} {name} {id} {version} - #tool_training_recommendations_link: https://training.galaxyproject.org/training-material/by-tool/{repository}/{name}.html + # template inputs are: {repository} {name} {tool_id} + # {training_tool_identifier} {version} + #tool_training_recommendations_link: https://training.galaxyproject.org/training-material/by-tool/{training_tool_identifier}.html # URL to API describing tutorials containing specific tools. When CORS # is used, make sure to add this host. diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index befb4e953b6..7035f1e0d7f 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -3705,7 +3705,8 @@ mapping: desc: | Displays a link to training material, if any includes the current tool. When activated the following options also need to be set: - - tool_training_recommendations_link + tool_training_recommendations_link, + tool_training_recommendations_api_url tool_training_recommendations_link: type: str From b967fd61a15be1ccefe837bec09d56faee4b2191 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:45:45 +0100 Subject: [PATCH 3/9] dont reload cache on tool change --- client/src/composables/toolTrainingMaterial.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/composables/toolTrainingMaterial.ts b/client/src/composables/toolTrainingMaterial.ts index 3589806bd74..5f797c72a6d 100644 --- a/client/src/composables/toolTrainingMaterial.ts +++ b/client/src/composables/toolTrainingMaterial.ts @@ -59,7 +59,7 @@ export function useToolTrainingMaterial(id: ID, name: Name, version: string, rep return; } - if (apiEnabled.value) { + if (apiEnabled.value && !cachedResponse.value) { const res = await fetch(config.value.tool_training_recommendations_api_url); if (res.ok) { From 0a7ad2a18fca454cfda6d7e9aba0dbb08ba14b50 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:45:01 +0100 Subject: [PATCH 4/9] fix text for single tutorial --- .../components/Tool/ToolTutorialRecommendations.vue | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/components/Tool/ToolTutorialRecommendations.vue b/client/src/components/Tool/ToolTutorialRecommendations.vue index fb3541cd0f9..fbaad52a9fd 100644 --- a/client/src/components/Tool/ToolTutorialRecommendations.vue +++ b/client/src/components/Tool/ToolTutorialRecommendations.vue @@ -8,6 +8,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faCaretDown } from "@fortawesome/free-solid-svg-icons"; import { useUid } from "@/composables/utils/uid"; import slugify from "slugify"; +import { computed } from "vue"; const props = defineProps<{ name: string; @@ -31,6 +32,14 @@ function idForCategory(category: string) { function tutorialsInCategory(category: string) { return tutorialDetails.value.filter((tut) => tut.category === category); } + +const tutorialText = computed(() => { + if (tutorialDetails.value.length > 1) { + return `There are ${tutorialDetails.value.length} tutorials available which use this tool.`; + } else { + return "There is 1 tutorial available which uses this tool."; + } +}); From a8aa365fc007203b5b34f1eb1e28b32b96657898 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Wed, 22 Feb 2023 11:46:54 +0100 Subject: [PATCH 8/9] move tutorials above footer --- client/src/components/Tool/ToolCard.vue | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/components/Tool/ToolCard.vue b/client/src/components/Tool/ToolCard.vue index 744453d8642..4de127fc9db 100644 --- a/client/src/components/Tool/ToolCard.vue +++ b/client/src/components/Tool/ToolCard.vue @@ -171,6 +171,12 @@ function onUpdatePreferredObjectStoreId(selectedToolPreferredObjectStoreId) {
+ + - -
From 2a3bcf378ef7ca64443094d16180a19317428a84 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Wed, 26 Apr 2023 11:54:04 +0200 Subject: [PATCH 9/9] import ExtrenalLink via absolute path Co-authored-by: Alireza Heidari --- client/src/components/Tool/ToolTutorialRecommendations.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Tool/ToolTutorialRecommendations.vue b/client/src/components/Tool/ToolTutorialRecommendations.vue index 1d8cc5a7979..e253a108905 100644 --- a/client/src/components/Tool/ToolTutorialRecommendations.vue +++ b/client/src/components/Tool/ToolTutorialRecommendations.vue @@ -1,7 +1,7 @@