Skip to content

Commit f05c8a8

Browse files
authored
Merge pull request #20914 from guerler/allow_creating_visualizations
Allow creation of visualizations without dataset
2 parents e44d3f0 + f83d772 commit f05c8a8

File tree

9 files changed

+73
-44
lines changed

9 files changed

+73
-44
lines changed

client/src/api/plugins.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,29 @@ export interface Plugin {
2929
html: string;
3030
logo?: string;
3131
name: string;
32+
params?: Record<string, ParamType>;
3233
target?: string;
3334
tags?: Array<string>;
3435
tests?: Array<TestType>;
3536
}
3637

38+
export interface ParamType {
39+
required?: boolean;
40+
}
41+
3742
export interface PluginData {
3843
hdas: Array<Dataset>;
3944
}
4045

41-
export interface ParamType {
46+
export interface TestParamType {
4247
ftype?: string;
4348
label?: string;
4449
name: string;
4550
value: string;
4651
}
4752

4853
export interface TestType {
49-
param: ParamType;
54+
param: TestParamType;
5055
}
5156

5257
export async function fetchPlugins(datasetId?: string): Promise<Array<Plugin>> {

client/src/components/Visualizations/VisualizationCreate.test.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@ import { createPinia, defineStore, setActivePinia } from "pinia";
44
import { getLocalVue } from "tests/jest/helpers";
55
import { ref } from "vue";
66

7-
import { fetchPluginHistoryItems } from "@/api/plugins";
7+
import { fetchPlugin, fetchPluginHistoryItems } from "@/api/plugins";
88

99
import VisualizationCreate from "./VisualizationCreate.vue";
1010
import FormCardSticky from "@/components/Form/FormCardSticky.vue";
1111

12+
const PLUGIN = {
13+
name: "scatterplot",
14+
description: "A great scatterplot plugin.",
15+
html: "Scatterplot Plugin",
16+
logo: "/logo.png",
17+
help: "Some help text",
18+
tags: ["tag1", "tag2"],
19+
};
20+
1221
jest.mock("vue-router/composables", () => ({
1322
useRouter: () => ({
1423
push: jest.fn(),
@@ -18,22 +27,13 @@ jest.mock("vue-router/composables", () => ({
1827
jest.mock("@/api/plugins", () => ({
1928
fetchPlugin: jest.fn(() =>
2029
Promise.resolve({
21-
name: "scatterplot",
22-
description: "A great scatterplot plugin.",
23-
html: "Scatterplot Plugin",
24-
logo: "/logo.png",
25-
help: "Some help text",
26-
tags: ["tag1", "tag2"],
30+
params: { dataset_id: { required: true } },
31+
...PLUGIN,
2732
}),
2833
),
2934
fetchPluginHistoryItems: jest.fn(() => Promise.resolve({ hdas: [] })),
3035
}));
3136

32-
jest.mock("./utilities", () => ({
33-
getTestExtensions: jest.fn(() => ["txt"]),
34-
getTestUrls: jest.fn(() => [{ name: "Example", url: "https://example.com/data.txt" }]),
35-
}));
36-
3737
let mockedStore;
3838
jest.mock("@/stores/historyStore", () => ({
3939
useHistoryStore: () => mockedStore,
@@ -95,3 +95,16 @@ it("adds hid to dataset names when fetching history items", async () => {
9595
{ id: "dataset2", name: "102: Second Dataset" },
9696
]);
9797
});
98+
99+
it("displays create new visualization option if dataset is not required", async () => {
100+
fetchPlugin.mockResolvedValueOnce(PLUGIN);
101+
const wrapper = mount(VisualizationCreate, {
102+
localVue,
103+
propsData: {
104+
visualization: "scatterplot",
105+
},
106+
});
107+
await wrapper.vm.$nextTick();
108+
const results = await wrapper.vm.doQuery();
109+
expect(results).toEqual([{ id: "", name: "Open visualization..." }]);
110+
});

client/src/components/Visualizations/VisualizationCreate.vue

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { storeToRefs } from "pinia";
33
import { computed, onMounted, type Ref, ref } from "vue";
44
import { useRouter } from "vue-router/composables";
55
6-
import { type Dataset, fetchPlugin, fetchPluginHistoryItems, type Plugin } from "@/api/plugins";
6+
import { fetchPlugin, fetchPluginHistoryItems, type Plugin } from "@/api/plugins";
77
import type { OptionType } from "@/components/SelectionField/types";
88
import { useMarkdown } from "@/composables/markdown";
99
import { useHistoryStore } from "@/stores/historyStore";
1010
11-
import { getTestExtensions, getTestUrls } from "./utilities";
11+
import { getRequiresDataset, getTestExtensions, getTestUrls } from "./utilities";
1212
1313
import VisualizationExamples from "./VisualizationExamples.vue";
1414
import Heading from "@/components/Common/Heading.vue";
@@ -27,20 +27,20 @@ const props = defineProps<{
2727
}>();
2828
2929
const errorMessage = ref("");
30+
const formatsVisible = ref(false);
3031
const plugin: Ref<Plugin | undefined> = ref();
3132
32-
const urlData = computed(() => getTestUrls(plugin.value));
3333
const extensions = computed(() => getTestExtensions(plugin.value));
34-
const formatsVisible = ref(false);
35-
36-
function addHidToName(hdas: Array<Dataset>) {
37-
return hdas.map((entry) => ({ id: entry.id, name: `${entry.hid}: ${entry.name}` }));
38-
}
34+
const requiresDataset = computed(() => getRequiresDataset(plugin.value));
35+
const testUrls = computed(() => getTestUrls(plugin.value));
3936
4037
async function doQuery() {
4138
if (currentHistoryId.value && plugin.value) {
4239
const data = await fetchPluginHistoryItems(plugin.value.name, currentHistoryId.value);
43-
return addHidToName(data.hdas);
40+
return [
41+
...(!requiresDataset.value ? [{ id: "", name: `Open visualization...` }] : []),
42+
...data.hdas.map((hda) => ({ id: hda.id, name: `${hda.hid}: ${hda.name}` })),
43+
];
4444
} else {
4545
return [];
4646
}
@@ -51,7 +51,8 @@ async function getPlugin() {
5151
}
5252
5353
function onSelect(dataset: OptionType) {
54-
router.push(`/visualizations/display?visualization=${plugin.value?.name}&dataset_id=${dataset.id}`, {
54+
const query = dataset.id ? `&dataset_id=${dataset.id}` : "";
55+
router.push(`/visualizations/display?visualization=${plugin.value?.name}${query}`, {
5556
// @ts-ignore
5657
title: dataset.name,
5758
});
@@ -73,11 +74,11 @@ defineExpose({ doQuery });
7374
:logo="plugin?.logo"
7475
:name="plugin?.html">
7576
<template v-slot:buttons>
76-
<VisualizationExamples :url-data="urlData" />
77+
<VisualizationExamples :url-data="testUrls" />
7778
</template>
7879
<div class="my-3">
7980
<SelectionField
80-
object-name="Select a dataset..."
81+
object-name="Make a selection..."
8182
object-title="Select to Visualize"
8283
object-type="history_dataset_id"
8384
:object-query="doQuery"

client/src/components/Visualizations/VisualizationFrame.vue

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ const iframeRef = ref<HTMLIFrameElement | null>(null);
2323
const srcWithRoot = computed(() => {
2424
let url = "";
2525
if (props.visualization === "trackster") {
26-
if (props.datasetId) {
27-
url = `/visualization/trackster?dataset_id=${props.datasetId}`;
28-
} else {
26+
if (props.visualizationId) {
2927
url = `/visualization/trackster?id=${props.visualizationId}`;
28+
} else {
29+
url = `/visualization/trackster?dataset_id=${props.datasetId}`;
3030
}
3131
} else {
32-
if (props.datasetId) {
33-
url = `/plugins/visualizations/${props.visualization}/show?dataset_id=${props.datasetId}`;
34-
} else {
32+
if (props.visualizationId) {
3533
url = `/plugins/visualizations/${props.visualization}/saved?id=${props.visualizationId}`;
34+
} else {
35+
const query = props.datasetId ? `?dataset_id=${props.datasetId}` : "";
36+
url = `/plugins/visualizations/${props.visualization}/show${query}`;
3637
}
3738
}
3839

client/src/components/Visualizations/utilities.test.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { getFilename, getTestUrls } from "./utilities";
1+
import { getFilename, getRequiresDataset, getTestUrls } from "./utilities";
22

33
describe("Utility Functions", () => {
4-
describe("getTestUrls", () => {
4+
describe("getRequiresDataset and getTestUrls", () => {
5+
it("return wether the visualization requires a dataset or not", () => {
6+
expect(getRequiresDataset({})).toEqual(false);
7+
const plugin = {
8+
params: { dataset_id: { required: true } },
9+
};
10+
expect(getRequiresDataset(plugin)).toEqual(true);
11+
});
12+
513
it("returns empty array when plugin is undefined", () => {
614
expect(getTestUrls()).toEqual([]);
715
});

client/src/components/Visualizations/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { Plugin } from "@/api/plugins";
22

3+
export function getRequiresDataset(plugin?: Plugin): boolean {
4+
return plugin?.params?.dataset_id?.required || false;
5+
}
6+
37
export function getTestExtensions(plugin?: Plugin): string[] {
48
const results: string[] = [];
59
if (plugin?.data_sources) {

client/visualizations.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ heatmap:
3838
version: 0.0.15
3939
jupyterlite:
4040
package: "@galaxyproject/jupyterlite"
41-
version: 0.0.12
41+
version: 0.0.16
4242
kepler:
4343
package: "@galaxyproject/kepler"
4444
version: 0.0.3

lib/galaxy/visualization/plugins/config_parser.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,11 @@ def parse_visualization(self, xml_tree):
7272
log.info("Visualizations plugin disabled: %s. Skipping...", returned["name"])
7373
return None
7474

75-
# record the embeddable flag - defaults to False
76-
returned["embeddable"] = False
77-
if "embeddable" in xml_tree.attrib:
78-
returned["embeddable"] = asbool(xml_tree.attrib.get("embeddable"))
79-
80-
# record the visible flag - defaults to False
81-
returned["hidden"] = False
82-
if "hidden" in xml_tree.attrib:
83-
returned["hidden"] = asbool(xml_tree.attrib.get("hidden"))
75+
# record boolean flags - defaults to False
76+
for keyword in ["embeddable", "hidden"]:
77+
returned[keyword] = False
78+
if keyword in xml_tree.attrib:
79+
returned[keyword] = asbool(xml_tree.attrib.get(keyword))
8480

8581
# a (for now) text description of what the visualization does
8682
description = xml_tree.find("description")

lib/galaxy/visualization/plugins/plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def to_dict(self):
116116
"tags": self.config.get("tags"),
117117
"title": self.config.get("title"),
118118
"target": self.config.get("render_target", "galaxy_main"),
119+
"params": self.config.get("params"),
119120
"embeddable": self.config.get("embeddable"),
120121
"entry_point": self.config.get("entry_point"),
121122
"settings": self.config.get("settings"),

0 commit comments

Comments
 (0)