mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Drop ToolsView, which is be superseded by the advanced search results view. The central panel search result view has a similar display but improved loading/filtering characteristics and we could use it in a 'show all the tools' display like this as well.
This commit is contained in:
@@ -9,7 +9,7 @@ import flushPromises from "flush-promises";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("ToolsView/ToolsView.vue", () => {
|
||||
describe("ToolSchemaJson/ToolsView.vue", () => {
|
||||
let wrapper;
|
||||
let axiosMock;
|
||||
const defaultSchemaElementTag = "application/ld+json";
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import ToolsView from "./ToolsView";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import flushPromises from "flush-promises";
|
||||
|
||||
// test response
|
||||
import testToolsListResponse from "./testData/toolsList";
|
||||
import testCitation from "./testData/citation";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import axios from "axios";
|
||||
|
||||
jest.mock("app");
|
||||
|
||||
describe("ToolsView/ToolsView.vue", () => {
|
||||
const localVue = getLocalVue();
|
||||
|
||||
let wrapper;
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(async () => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
axiosMock.onGet("/api/tools?tool_help=True").reply(200, testToolsListResponse);
|
||||
axiosMock.onGet(new RegExp(`./*/citations`)).reply(200, testCitation);
|
||||
wrapper = mount(ToolsView, { localVue, attachTo: document.body });
|
||||
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.restore();
|
||||
});
|
||||
|
||||
it("should render infinite scroll div", async () => {
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining('<div infinite-scroll-disabled="busy">'));
|
||||
});
|
||||
|
||||
it("should return defined number of tools", async () => {
|
||||
expect(wrapper.vm.getToolsNumber() === 5).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should render only specific number of tools, equal to current buffer", async () => {
|
||||
const buttons = wrapper.findAll('[type="button"]').filter((button) => button.text() === "Info");
|
||||
// one 'info' button per tool
|
||||
expect(wrapper.vm.buffer.length === buttons.length).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should open modal on button click", async () => {
|
||||
// findAll() returns WrapperArray, thus regular array.find() won't work
|
||||
const infoButton = wrapper
|
||||
.findAll('[type="button"]')
|
||||
.filter((button) => button.text() === "Info")
|
||||
.at(0);
|
||||
const modalId = "modal--" + infoButton.attributes().index;
|
||||
const modal = wrapper.find("#" + modalId);
|
||||
expect(modal.element).not.toBeVisible();
|
||||
await infoButton.trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(modal.element).toBeVisible();
|
||||
});
|
||||
|
||||
it("citation should open on click", async () => {
|
||||
const infoButton = wrapper
|
||||
.findAll('[type="button"]')
|
||||
.filter((button) => button.text() === "Citations")
|
||||
.at(0);
|
||||
const citation = wrapper.find("#" + infoButton.attributes("aria-controls").replace(/ /g, "_"));
|
||||
|
||||
expect(citation.element).not.toBeVisible();
|
||||
expect(infoButton.attributes("aria-expanded") === "false").toBeTruthy();
|
||||
|
||||
await infoButton.trigger("click");
|
||||
await flushPromises();
|
||||
expect(infoButton.attributes("aria-expanded") === "true").toBeTruthy();
|
||||
expect(citation.element).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,211 +0,0 @@
|
||||
<template>
|
||||
<div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy">
|
||||
<h2 class="mb-3" style="text-align: center">
|
||||
<span id="tools-view">Consolidated view of {{ tools.length }} available tools.</span>
|
||||
</h2>
|
||||
<div v-if="!loading">
|
||||
<b-container fluid class="mb-4">
|
||||
<b-row class="justify-content-center">
|
||||
<b-col md="6">
|
||||
<b-form-group description="Search for strings or regular expressions">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="filterText"
|
||||
placeholder="Type to Search"
|
||||
@keyup.native="filter('filterByText')"
|
||||
@keyup.esc.native="filterText = ''" />
|
||||
<b-input-group-append>
|
||||
<b-btn :disabled="!filter" @click="filter = ''">Clear (esc)</b-btn>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
<isotope
|
||||
ref="iso"
|
||||
:options="isoOptions"
|
||||
:list="buffer"
|
||||
style="margin: 0 auto"
|
||||
@filter="filterOption = arguments[2]">
|
||||
<b-card v-for="(info, index) in buffer" :key="index" ref="cards" class="m-2" style="width: 23rem">
|
||||
<template v-slot:header>
|
||||
<div>
|
||||
<b-link :href="info.url" target="_blank">
|
||||
<h4 class="tools-view-name">{{ info.name }}</h4>
|
||||
</b-link>
|
||||
<b-badge class="tools-view-section">{{ info.section }}</b-badge>
|
||||
</div>
|
||||
</template>
|
||||
<p class="card-text" v-html="helpSummary(info.help) || info.description" />
|
||||
<p class="card-text">
|
||||
<b-btn v-b-modal="'modal-' + '-' + index" :index="index">Info</b-btn>
|
||||
<b-modal :id="'modal-' + '-' + index" centered ok-only :static="true" :title="info.name">
|
||||
<b>{{ info.version + " / " + info.id }}</b>
|
||||
<p>{{ info.description }}</p>
|
||||
<p v-html="info.help"></p>
|
||||
</b-modal>
|
||||
</p>
|
||||
<Citations
|
||||
:id="info.id"
|
||||
simple
|
||||
source="tools"
|
||||
@rendered="layout"
|
||||
@show="show(index)"
|
||||
@shown="shown(index)"
|
||||
@hidden="hidden(index)" />
|
||||
</b-card>
|
||||
</isotope>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import infiniteScroll from "vue-infinite-scroll";
|
||||
import isotope from "vueisotope";
|
||||
import axios from "axios";
|
||||
import Citations from "components/Citation/Citations.vue";
|
||||
import { setTimeout } from "timers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Citations,
|
||||
isotope,
|
||||
},
|
||||
directives: { infiniteScroll },
|
||||
props: {
|
||||
transitionDuration: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tools: [],
|
||||
buffer: [],
|
||||
filterOption: null,
|
||||
filterText: "",
|
||||
busy: false,
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isoOptions() {
|
||||
return {
|
||||
transitionDuration: this.transitionDuration,
|
||||
masonry: {
|
||||
fitWidth: true,
|
||||
},
|
||||
getFilterData: {
|
||||
filterByText: (el) => {
|
||||
const re = new RegExp(this.filterText, "i");
|
||||
return el.name.match(re) || el.description.match(re) || el.help.match(re);
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios
|
||||
.get(`${getAppRoot()}api/tools?tool_help=True`)
|
||||
.then((response) => {
|
||||
this.initialize(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
show(index) {
|
||||
this.$refs.cards[index].style.zIndex = "1";
|
||||
},
|
||||
shown(index) {
|
||||
this.layout();
|
||||
setTimeout(() => {
|
||||
this.$refs.cards[index].style.zIndex = "auto";
|
||||
}, 200);
|
||||
},
|
||||
hidden(index) {
|
||||
this.layout();
|
||||
},
|
||||
layout() {
|
||||
this.$refs.iso.layout();
|
||||
},
|
||||
filter(key) {
|
||||
this.$refs.iso.filter(key);
|
||||
},
|
||||
toolsExtracted(tools) {
|
||||
function extractSections(acc, section) {
|
||||
function extractTools(_acc, tool) {
|
||||
return tool.name
|
||||
? [
|
||||
..._acc,
|
||||
{
|
||||
id: tool.id,
|
||||
name: tool.name,
|
||||
section: section.name,
|
||||
description: tool.description,
|
||||
url: getAppRoot() + String(tool.link).substring(1),
|
||||
version: tool.version,
|
||||
help: tool.help,
|
||||
},
|
||||
]
|
||||
: _acc;
|
||||
}
|
||||
if ("elems" in section) {
|
||||
return acc.concat(section.elems.reduce(extractTools, []));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
return tools
|
||||
.reduce(extractSections, [])
|
||||
.map((a) => [Math.random(), a])
|
||||
.sort((a, b) => a[0] - b[0])
|
||||
.map((a) => a[1]);
|
||||
},
|
||||
loadMore() {
|
||||
if (this.buffer.length < this.tools.length) {
|
||||
this.busy = true;
|
||||
|
||||
setTimeout(() => {
|
||||
const start = this.buffer.length;
|
||||
const end = start + 10;
|
||||
const newItems = this.tools.slice(start, end);
|
||||
this.buffer = this.buffer.concat(newItems);
|
||||
|
||||
this.busy = false;
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
helpSummary(help) {
|
||||
const parser = new DOMParser();
|
||||
const helpDoc = parser.parseFromString(help, "text/html");
|
||||
const xpath = "//strong[text()='What it does']/../following-sibling::*";
|
||||
const match = helpDoc.evaluate(
|
||||
xpath,
|
||||
helpDoc,
|
||||
null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||
null
|
||||
).singleNodeValue;
|
||||
if (match) {
|
||||
return match.innerHTML;
|
||||
}
|
||||
const helpText = helpDoc.documentElement.textContent;
|
||||
if (helpText) {
|
||||
return helpText.substring(0, helpText.indexOf("\n\n"));
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getToolsNumber() {
|
||||
return this.tools.length;
|
||||
},
|
||||
initialize(tools) {
|
||||
this.tools = this.toolsExtracted(tools);
|
||||
this.buffer = this.tools.slice(0, 20);
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1 +0,0 @@
|
||||
[{"content": " @article{Blankenberg_2011, title={Making whole genome multiple alignments usable for biologists}, volume={27}, ISSN={1367-4803}, url={http://dx.doi.org/10.1093/bioinformatics/btr398}, DOI={10.1093/bioinformatics/btr398}, number={17}, journal={Bioinformatics}, publisher={Oxford University Press (OUP)}, author={Blankenberg, Daniel and Taylor, James and Nekrutenko, Anton}, year={2011}, month={Jul}, pages={2426\u00e2\u0080\u00932428}}\n", "format": "bibtex"}]
|
||||
@@ -40,7 +40,6 @@ import Sharing from "components/Sharing/Sharing";
|
||||
import StoredWorkflowInvocations from "components/Workflow/StoredWorkflowInvocations";
|
||||
import ToolAdvancedSearch from "components/Panels/Common/ToolAdvancedSearch";
|
||||
import ToolsJson from "components/ToolsView/ToolsSchemaJson/ToolsJson";
|
||||
import ToolsView from "components/ToolsView/ToolsView";
|
||||
import TourList from "components/Tour/TourList";
|
||||
import TourRunner from "components/Tour/TourRunner";
|
||||
import TrsImport from "components/Workflow/TrsImport";
|
||||
@@ -298,10 +297,6 @@ export function getRouter(Galaxy) {
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "tools/view",
|
||||
component: ToolsView,
|
||||
},
|
||||
{
|
||||
path: "tools/json",
|
||||
component: ToolsJson,
|
||||
|
||||
Reference in New Issue
Block a user