Skip to content

Commit

Permalink
Improvements to help terms & tool help.
Browse files Browse the repository at this point in the history
- Allow tools to declare markdown help.
- Allow tools to reference help terms from their Markdown tools.
- Auto generate help terms for each datatype from values in datatypes_conf.xml
- Add permenant links for each help term - both the auto generated ones and the YAML specified
ones.
- Add some initial help terms for collection operations.
- Use new help term for "map over" from tool form.
  • Loading branch information
jmchilton committed Aug 21, 2024
1 parent 2cd33e8 commit f8f2b7c
Show file tree
Hide file tree
Showing 27 changed files with 657 additions and 104 deletions.
7 changes: 6 additions & 1 deletion client/src/components/Form/Elements/FormData/FormData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { BATCH, SOURCE, VARIANTS } from "./variants";
import FormSelection from "../FormSelection.vue";
import FormSelect from "@/components/Form/Elements/FormSelect.vue";
import HelpText from "@/components/Help/HelpText.vue";
library.add(faCopy, faFile, faFolder, faCaretDown, faCaretUp, faExclamation, faLink, faUnlink);
Expand Down Expand Up @@ -650,7 +651,11 @@ const noOptionsWarningMessage = computed(() => {
</BFormCheckbox>
<div class="info text-info">
<FontAwesomeIcon icon="fa-exclamation" />
<span v-localize class="ml-1">
<span v-if="props.type == 'data' && currentVariant.src == SOURCE.COLLECTION" class="ml-1">
The supplied input will be <HelpText text="mapped over" uri="galaxy.collections.mapOver" /> this
tool.
</span>
<span v-else v-localize class="ml-1">
This is a batch mode input field. Individual jobs will be triggered for each dataset.
</span>
</div>
Expand Down
18 changes: 18 additions & 0 deletions client/src/components/Help/HelpPopover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
import { BPopover } from "bootstrap-vue";
import HelpTerm from "./HelpTerm.vue";
interface Props {
target: any;
term: string;
}
defineProps<Props>();
</script>

<template>
<BPopover :target="target" triggers="hover" placement="bottom">
<HelpTerm :term="term" />
</BPopover>
</template>
31 changes: 31 additions & 0 deletions client/src/components/Help/HelpTerm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { BAlert } from "bootstrap-vue";
import { toRef } from "vue";
import { useHelpForTerm } from "@/stores/helpTermsStore";
import LoadingSpan from "@/components/LoadingSpan.vue";
import ConfigurationMarkdown from "@/components/ObjectStore/ConfigurationMarkdown.vue";
interface Props {
term: string;
}
const props = defineProps<Props>();
const { loading, hasHelp, help } = useHelpForTerm(toRef(props, "term"));
</script>

<template>
<div>
<div v-if="loading">
<LoadingSpan message="Loading Galaxy help terms" />
</div>
<div v-else-if="hasHelp">
<ConfigurationMarkdown :markdown="help" :admin="true" />
</div>
<div v-else>
<BAlert variant="error"> Something went wrong, no Galaxy help found for term or URI {{ term }}. </BAlert>
</div>
</div>
</template>
27 changes: 5 additions & 22 deletions client/src/components/Help/HelpText.vue
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
<script setup lang="ts">
import { computed } from "vue";
import { hasHelp as hasHelpText, help as helpText } from "./terms";
import ConfigurationMarkdown from "@/components/ObjectStore/ConfigurationMarkdown.vue";
import HelpPopover from "./HelpPopover.vue";
interface Props {
uri: string;
text: string;
}
const props = defineProps<Props>();
const hasHelp = computed<boolean>(() => {
return hasHelpText(props.uri);
});
const help = computed<string>(() => {
return helpText(props.uri) as string;
});
defineProps<Props>();
</script>

<template>
<span>
<b-popover
v-if="hasHelp"
<HelpPopover
:target="
() => {
return $refs.helpTarget;
}
"
triggers="hover"
placement="bottom">
<ConfigurationMarkdown :markdown="help" :admin="true" />
</b-popover>
<span v-if="hasHelp" ref="helpTarget" class="help-text">{{ text }}</span>
<span v-else>{{ text }}</span>
:term="uri" />
<span ref="helpTarget" class="help-text">{{ text }}</span>
</span>
</template>

Expand Down
25 changes: 25 additions & 0 deletions client/src/components/Help/terms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ unix:
More information on stack traces can be found on [Wikipedia](https://en.wikipedia.org/wiki/Stack_trace).
galaxy:
collections:
flatList: |
A flat list is just a simple dataset collection of type ``list`` that contains only datasets and not
other collections.
mapOver: |
When a tool consumes a dataset but is run with a collection, the collection *maps over* the collection.
This means instead of just running the tool once - the tool will be run once for each element of the
provided collection. Additionally, the outputs of the tool will be collected into a collection that
matches the structure of the provided collection. This matching structure means the output collections
will have the same element identifiers as the provided collection and they will appear in the same order.
It is easiest to visualize "mapping over" a collection is in the context of a tool that consumes a dataset
and produces a dataset, but the semantics apply rather naturally to tools that consume collections or
produce collections as well.
For instance, consider a tool that consumes a ``paired`` collection and produces an output dataset.
If a list of paired collections (collection type ``list:paired``) is passed to the tool - it will
will produce a flat list (collection type ``list``) of output datasets with the same number of elements
in the same order as the provided list of ``paired`` collections.
In the case of outputs, consider a tool that takes in a dataset and produces a flat list. If this tool
is run over a flat list of datasets - that list will be "mapped over" and each element will produce a list.
These lists will be gathered together in a nested list structured (collection type ``list:list``) where
the outer element count and structure matches that of the input and the inner list for each of those
is just the outputs of the tool for the corresponding element of the input.
jobs:
states:
# upload, waiting, failed, paused, deleting, deleted, stop, stopped, skipped.
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Tool/ToolCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ const showHelpForum = computed(() => isConfigLoaded.value && config.value.enable
<div>
<div v-if="props.options.help" class="mt-2 mb-4">
<Heading h2 separator bold size="sm"> Help </Heading>
<ToolHelp :content="props.options.help" />
<ToolHelp :content="props.options.help" :format="props.options.help_format" />
</div>
<ToolTutorialRecommendations
Expand Down
54 changes: 15 additions & 39 deletions client/src/components/Tool/ToolHelp.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
<script setup>
import { useFormattedToolHelp } from "composables/formattedToolHelp";
const props = defineProps({
content: {
type: String,
required: true,
},
});
const { formattedContent } = useFormattedToolHelp(props.content);
<script setup lang="ts">
import ToolHelpMarkdown from "./ToolHelpMarkdown.vue";
import ToolHelpRst from "./ToolHelpRst.vue";
defineProps<{
format: string;
content: string;
}>();
</script>

<template>
<div class="form-help form-text" v-html="formattedContent" />
<span>
<ToolHelpMarkdown v-if="format == 'markdown'" :content="content" />
<ToolHelpRst v-else-if="format == 'restructuredtext'" :content="content" />
<div v-else class="form-help form-text">
{{ content }}
</div>
</span>
</template>

<style lang="scss" scoped>
@import "scss/theme/blue.scss";
.form-help {
&:deep(h3) {
font-size: $h4-font-size;
font-weight: bold;
}
&:deep(h4) {
font-size: $h5-font-size;
font-weight: bold;
}
&:deep(h5) {
font-size: $h6-font-size;
font-weight: bold;
}
&:deep(h6) {
font-size: $h6-font-size;
text-decoration: underline;
}
}
</style>
67 changes: 67 additions & 0 deletions client/src/components/Tool/ToolHelpMarkdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { markup } from "@/components/ObjectStore/configurationMarkdown";
import { useFormattedToolHelp } from "@/composables/formattedToolHelp";
import { getAppRoot } from "@/onload/loadConfig";
import HelpPopover from "@/components/Help/HelpPopover.vue";
const props = defineProps<{
content: string;
}>();
const markdownHtml = computed(() => markup(props.content ?? "", false));
// correct links and header information... this should work the same between rst and
// markdown entirely I think.
const { formattedContent } = useFormattedToolHelp(markdownHtml);
const helpHtml = ref<HTMLDivElement>();
interface InternalTypeReference {
element: HTMLElement;
term: string;
}
const internalHelpReferences = ref<InternalTypeReference[]>([]);
function setupPopovers() {
internalHelpReferences.value.length = 0;
if (helpHtml.value) {
const links = helpHtml.value.getElementsByTagName("a");
Array.from(links).forEach((link) => {
if (link.href.startsWith("gxhelp://")) {
const uri = link.href.substr("gxhelp://".length);
internalHelpReferences.value.push({ element: link, term: uri });
link.href = `${getAppRoot()}help/terms/${uri}`;
link.style.color = "inherit";
link.style.textDecorationLine = "underline";
link.style.textDecorationStyle = "dashed";
}
});
const imgs = helpHtml.value.getElementsByTagName("img");
Array.from(imgs).forEach((img) => {
if (img.src.startsWith("gxstatic://")) {
const rest = img.src.substr("gxstatic://".length);
img.src = `${getAppRoot()}static/${rest}`;
}
});
}
}
onMounted(setupPopovers);
</script>

<template>
<span>
<!-- Disable v-html warning because we allow markdown generated HTML
in various places in the Galaxy interface. Raw HTML is not allowed
here because admin = false in the call to markup.
-->
<!-- eslint-disable-next-line vue/no-v-html -->
<div ref="helpHtml" v-html="formattedContent" />
<span v-for="(value, i) in internalHelpReferences" v-bind:key="i">
<HelpPopover :target="value.element" :term="value.term" />
</span>
</span>
</template>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "tests/jest/helpers";

import ToolHelp from "./ToolHelp";
import ToolHelpRst from "./ToolHelpRst";

const localVue = getLocalVue();

Expand All @@ -25,9 +25,9 @@ const expectedHelpText = `
<h6>h4 Heading</h6>
<a target="_blank">empty link</a>`;

describe("ToolHelp", () => {
describe("ToolHelp RST", () => {
it("modifies help text", () => {
const wrapper = mount(ToolHelp, {
const wrapper = mount(ToolHelpRst, {
propsData: {
content: inputHelpText,
},
Expand Down
42 changes: 42 additions & 0 deletions client/src/components/Tool/ToolHelpRst.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script setup>
import { useFormattedToolHelp } from "composables/formattedToolHelp";
const props = defineProps({
content: {
type: String,
required: true,
},
});
const { formattedContent } = useFormattedToolHelp(props.content);
</script>

<template>
<div class="form-help form-text" v-html="formattedContent" />
</template>

<style lang="scss" scoped>
@import "scss/theme/blue.scss";
.form-help {
&:deep(h3) {
font-size: $h4-font-size;
font-weight: bold;
}
&:deep(h4) {
font-size: $h5-font-size;
font-weight: bold;
}
&:deep(h5) {
font-size: $h6-font-size;
font-weight: bold;
}
&:deep(h6) {
font-size: $h6-font-size;
text-decoration: underline;
}
}
</style>
6 changes: 6 additions & 0 deletions client/src/entry/analysis/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DatasetAttributes from "components/DatasetInformation/DatasetAttributes";
import DatasetDetails from "components/DatasetInformation/DatasetDetails";
import DatasetError from "components/DatasetInformation/DatasetError";
import FormGeneric from "components/Form/FormGeneric";
import HelpTerm from "components/Help/HelpTerm";
import HistoryExportTasks from "components/History/Export/HistoryExport";
import HistoryPublished from "components/History/HistoryPublished";
import HistoryView from "components/History/HistoryView";
Expand Down Expand Up @@ -195,6 +196,11 @@ export function getRouter(Galaxy) {
path: "about",
component: AboutGalaxy,
},
{
path: "help/terms/:term",
component: HelpTerm,
props: true,
},
{
path: "carbon_emissions_calculations",
component: CarbonEmissionsCalculations,
Expand Down
Loading

0 comments on commit f8f2b7c

Please sign in to comment.