Skip to content

Commit

Permalink
Ideas for improved collection help
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Aug 19, 2024
1 parent 2cd33e8 commit fff78f9
Show file tree
Hide file tree
Showing 16 changed files with 264 additions and 88 deletions.
6 changes: 5 additions & 1 deletion client/src/components/Form/Elements/FormData/FormData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { faCaretDown, faCaretUp, faExclamation, faLink, faUnlink } from "@fortaw
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BButtonGroup, BCollapse, BFormCheckbox, BTooltip } from "bootstrap-vue";
import { computed, onMounted, type Ref, ref, watch } from "vue";
import HelpText from "@/components/Help/HelpText.vue";
import { getGalaxyInstance } from "@/app";
import { useDatatypesMapper } from "@/composables/datatypesMapper";
Expand Down Expand Up @@ -650,7 +651,10 @@ const noOptionsWarningMessage = computed(() => {
</BFormCheckbox>
<div class="info text-info">
<FontAwesomeIcon icon="fa-exclamation" />
<span v-localize class="ml-1">
<span class="ml-1" v-if="props.type == 'data' && currentVariant.src == SOURCE.COLLECTION">

Check failure on line 654 in client/src/components/Form/Elements/FormData/FormData.vue

View workflow job for this annotation

GitHub Actions / client-unit-test (18)

Attribute "v-if" should go before "class"
The supplied input will be <HelpText text="mapped over" uri="galaxy.collections.mapOver" /> this tool.
</span>
<span v-localize class="ml-1" v-else>

Check failure on line 657 in client/src/components/Form/Elements/FormData/FormData.vue

View workflow job for this annotation

GitHub Actions / client-unit-test (18)

Attribute "v-else" should go before "class"
This is a batch mode input field. Individual jobs will be triggered for each dataset.
</span>
</div>
Expand Down
20 changes: 20 additions & 0 deletions client/src/components/Help/HelpPopover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script setup lang="ts">
import ConfigurationMarkdown from "@/components/ObjectStore/ConfigurationMarkdown.vue";
interface Props {
target: any;
help: string;
}
defineProps<Props>();
</script>


<template>
<b-popover
:target="target"
triggers="hover"
placement="bottom">
<ConfigurationMarkdown :markdown="help" :admin="true" />
</b-popover>
</template>
21 changes: 6 additions & 15 deletions client/src/components/Help/HelpText.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed } from "vue";
import { toRef } from "vue";
import { hasHelp as hasHelpText, help as helpText } from "./terms";
import { useHelp } from "./useHelp";
import ConfigurationMarkdown from "@/components/ObjectStore/ConfigurationMarkdown.vue";
import HelpPopover from "./HelpPopover.vue";
interface Props {
uri: string;
Expand All @@ -12,28 +12,19 @@ interface Props {
const props = defineProps<Props>();
const hasHelp = computed<boolean>(() => {
return hasHelpText(props.uri);
});
const help = computed<string>(() => {
return helpText(props.uri) as string;
});
const { hasHelp, help } = useHelp(toRef(props, "uri"));
</script>

<template>
<span>
<b-popover
<HelpPopover
v-if="hasHelp"
:target="
() => {
return $refs.helpTarget;
}
"
triggers="hover"
placement="bottom">
<ConfigurationMarkdown :markdown="help" :admin="true" />
</b-popover>
:help="help" />
<span v-if="hasHelp" ref="helpTarget" class="help-text">{{ text }}</span>
<span v-else>{{ text }}</span>
</span>
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
17 changes: 17 additions & 0 deletions client/src/components/Help/useHelp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { computed,type Ref } from "vue";

import { hasHelp as hasHelpText, help as helpText } from "./terms";

export function useHelp(
uri: Ref<string>,
) {
const hasHelp = computed<boolean>(() => {
return hasHelpText(uri.value);
});

const help = computed<string>(() => {
return helpText(uri.value) as string;
});

return { hasHelp, help };
}
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" :type="props.options.help_type" />
</div>
<ToolTutorialRecommendations
Expand Down
51 changes: 14 additions & 37 deletions client/src/components/Tool/ToolHelp.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,19 @@
<script setup>
import { useFormattedToolHelp } from "composables/formattedToolHelp";
<script setup lang="ts">
import ToolHelpMarkdown from "./ToolHelpMarkdown.vue";
import ToolHelpRst from "./ToolHelpRst.vue";
const props = defineProps({
content: {
type: String,
required: true,
},
});
defineProps<{
type: string;
content: string;
}>();
const { formattedContent } = useFormattedToolHelp(props.content);
</script>

<template>
<div class="form-help form-text" v-html="formattedContent" />
<span>
<ToolHelpMarkdown v-if="type=='markdown'" :content="content" />
<ToolHelpRst v-else-if="type=='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>
64 changes: 64 additions & 0 deletions client/src/components/Tool/ToolHelpMarkdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { hasHelp, help } from "@/components/Help/terms";
import { markup } from "@/components/ObjectStore/configurationMarkdown";
import { useFormattedToolHelp } from "@/composables/formattedToolHelp";
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;
markdown: 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("help://")) {
const uri = link.href.substr("help://".length);
if( hasHelp(uri)) {
const helpMarkdown = help(uri);
internalHelpReferences.value.push({element: link, markdown: helpMarkdown});
}
link.href = "#";
link.style.color = "inherit";
link.style.textDecorationLine = "underline";
link.style.textDecorationStyle = "dashed";
}
});
}
}
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:key="i">

Check failure on line 60 in client/src/components/Tool/ToolHelpMarkdown.vue

View workflow job for this annotation

GitHub Actions / client-unit-test (18)

Elements in iteration expect to have 'v-bind:key' directives

Check failure on line 60 in client/src/components/Tool/ToolHelpMarkdown.vue

View workflow job for this annotation

GitHub Actions / client-unit-test (18)

'i' is defined but never used
<HelpPopover :target="value.element" :help="value.markdown" />
</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>
7 changes: 6 additions & 1 deletion lib/galaxy/tool_util/parser/cwl.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from galaxy.tool_util.deps import requirements
from .interface import (
HelpContent,
PageSource,
PagesSource,
ToolSource,
Expand Down Expand Up @@ -80,7 +81,11 @@ def parse_edam_topics(self):
return []

def parse_help(self):
return self.tool_proxy.doc()
doc = self.tool_proxy.doc()
if doc:
return HelpContent(type="plain_text", content=doc)
else:
return None

def parse_sanitize(self):
return False
Expand Down
13 changes: 10 additions & 3 deletions lib/galaxy/tool_util/parser/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ class Citation(BaseModel):
content: str


class HelpContent(BaseModel):
type: Literal["restructuredtext", "plain_text", "markdown"]
content: str


class ToolSource(metaclass=ABCMeta):
"""This interface represents an abstract source to parse tool
information from.
Expand Down Expand Up @@ -333,9 +338,11 @@ def parse_stdio(self):
return [], []

@abstractmethod
def parse_help(self) -> Optional[str]:
"""Return RST definition of help text for tool or None if the tool
doesn't define help text.
def parse_help(self) -> Optional[HelpContent]:
"""Return help text for tool or None if the tool doesn't define help text.
The returned object contains the help text and an indication if it ``rst``
or ``markdown``.
"""

@abstractmethod
Expand Down
Loading

0 comments on commit fff78f9

Please sign in to comment.