Skip to content

Commit

Permalink
Merge pull request #240 from wtsi-npg/select_all_wells_for_run
Browse files Browse the repository at this point in the history
Allow clickable run names in Well table to jump to wells-by-run
  • Loading branch information
mgcam authored Aug 21, 2024
2 parents 72312f2 + 3d5b54f commit 6f5dd2f
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 90 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Added

* Clickable icon next to run names to change to the view of all wells in that run

## [2.3.0] - 2024-07-30

### Added
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/WellTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
* Renders a table for a list of wells and generates buttons for selecting wells
*/
import { combineLabelWithPlate, listStudiesForTooltip } from "../utils/text.js"
import { ElTooltip, ElButton } from "element-plus";
import { ElTooltip, ElButton } from "element-plus"
import { Top } from "@element-plus/icons-vue"
const tooltipDelay = 500
const studyNameHighlight = 'BIOSCAN UK for flying insects'
defineProps({
wellCollection: Object
wellCollection: Object,
allowNav: Boolean,
})
defineEmits(['wellSelected'])
defineEmits(['wellSelected', 'runSelected'])
</script>

<template>
Expand All @@ -30,7 +32,7 @@ defineEmits(['wellSelected'])
<th>Well time complete</th>
</tr>
<tr :key="wellObj.id_product" v-for="wellObj in wellCollection">
<td :id="wellObj.run_name">{{ wellObj.run_name }}</td>
<td :id="wellObj.run_name">{{ wellObj.run_name }}<el-icon v-if="allowNav" :size="10" v-on:click="$emit('runSelected', wellObj.run_name)"><Top /></el-icon></td>
<td class="well_selector">
<el-tooltip placement="top" effect="light" :show-after="tooltipDelay"
:content="'<span>'.concat(listStudiesForTooltip(wellObj.study_names)).concat('</span>')"
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/__tests__/WellTable.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,15 @@ describe('Rows of data give rows in the table', () => {
expect(wellButton.classes('el-tooltip__trigger')).toBeTruthy()
expect(wellButton.classes('el-button--warning')).toBeTruthy()
})

test('No run selection icons appear by default', () => {
expect(() => table.get('el-icon')).toThrowError()
})

test('Setting allowNav property causes buttons to appear', async () => {
await table.setProps({'allowNav': true})
let icon = table.get('el-icon')
await icon.trigger('click')
expect(table.emitted().runSelected[0][0]).toEqual('TEST1')
})
})
6 changes: 5 additions & 1 deletion frontend/src/views/WellsByStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ function updateUrlQuery(newParams) {
}
}
function showRun(runName) {
router.push({ name: 'WellsByRun', params: { runName: [runName] } })
}
onMounted(() => {
// If there are no query properties in the URL, we can "redirect" to a
// sensible default
Expand All @@ -141,7 +145,7 @@ onMounted(() => {
<template>
<el-tabs v-model="activeTab" type="border-card" @tab-change="clickTabChange">
<el-tab-pane v-for="tab in appConfig.qc_flow_statuses" :key="tab.param" :label="tab.label" :name="tab.param">
<WellTable :wellCollection="wellCollection" @wellSelected="updateUrlQuery"/>
<WellTable allowNav :wellCollection="wellCollection" @wellSelected="updateUrlQuery" @runSelected="showRun"/>
</el-tab-pane>
<el-pagination v-model:currentPage="activePage" layout="prev, pager, next" v-bind:total="totalNumberOfWells"
background :pager-count="5" :page-size="pageSize" :hide-on-single-page="true"
Expand Down
196 changes: 111 additions & 85 deletions frontend/src/views/__tests__/WellsByStatus.spec.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import { describe, expect, test, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { ref } from 'vue'
// import {createRouter, createWebHistory} from 'vue-router'
// or routes arg to render(Component, {routes: []|vue-router obj})
import { describe, expect, test, vi } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { ref } from "vue";

import { createTestingPinia } from '@pinia/testing'
import ElementPlus from 'element-plus'
import { createRouter, createWebHistory } from 'vue-router'
import { createTestingPinia } from "@pinia/testing";
import ElementPlus from "element-plus";
import { createRouter, createWebHistory } from "vue-router";
// or routes arg to render(Component, {routes: []|vue-router obj})

import WellsByStatus from '@/views/WellsByStatus.vue'
import WellsByStatus from "@/views/WellsByStatus.vue";

// The giga-faking of most of the app:


const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/', redirect: '/ui/wells' }, { path: '/ui/wells', component: WellsByStatus }]
})
routes: [
{ path: "/", redirect: "/ui/wells" },
{ path: "/ui/wells", component: WellsByStatus },
{
path: "/ui/run/:runName+",
name: "WellsByRun",
props: true,
component: {
props: ["runName"],
template: "<div>{{props.runName[0]}}</div>",
},
},
],
});

const testWells = []
const testWells = [];
for (let index = 0; index < 10; index++) {
testWells.push({
label: String.fromCharCode(97 + index) + "1",
Expand All @@ -31,29 +41,29 @@ for (let index = 0; index < 10; index++) {
well_status: "Complete",
qc_state: null,
instrument_name: 1234,
instrument_type: 'Revio',
study_names: ['Some study'],
})
instrument_type: "Revio",
study_names: ["Some study"],
});
}

const configResponse = {
"qc_flow_statuses": [
{ "label": "Inbox", "param": "inbox" },
{ "label": "In Progress", "param": "in_progress" },
{ "label": "On Hold", "param": "on_hold" },
{ "label": "QC Complete", "param": "qc_complete" },
{ "label": "Aborted", "param": "aborted" },
{ "label": "Unknown", "param": "unknown" }
qc_flow_statuses: [
{ label: "Inbox", param: "inbox" },
{ label: "In Progress", param: "in_progress" },
{ label: "On Hold", param: "on_hold" },
{ label: "QC Complete", param: "qc_complete" },
{ label: "Aborted", param: "aborted" },
{ label: "Unknown", param: "unknown" },
],
"qc_states": [
{ "description": "Passed", "only_prelim": false },
{ "description": "Failed", "only_prelim": false },
{ "description": "Failed, Instrument", "only_prelim": false },
{ "description": "Failed, SMRT cell", "only_prelim": false },
{ "description": "On hold", "only_prelim": true },
{ "description": "Undecided", "only_prelim": false }
]
}
qc_states: [
{ description: "Passed", only_prelim: false },
{ description: "Failed", only_prelim: false },
{ description: "Failed, Instrument", only_prelim: false },
{ description: "Failed, SMRT cell", only_prelim: false },
{ description: "On hold", only_prelim: true },
{ description: "Undecided", only_prelim: false },
],
};

fetch.mockResponses(
[
Expand All @@ -62,10 +72,10 @@ fetch.mockResponses(
userinfo: {
sub: "00ucz5ud94VnrDGv0416",
email: "[email protected]",
email_verified: true
}
email_verified: true,
},
}),
{ status: 200 }
{ status: 200 },
],
// Get wells data
[
Expand All @@ -74,83 +84,99 @@ fetch.mockResponses(
page_number: 1,
total_number_of_items: 100,
qc_flow_status: "inbox",
wells: testWells
wells: testWells,
}),
{ status: 200 }
],
)
{ status: 200 },
]
);

const wrapper = mount(WellsByStatus, {
global: {
plugins: [
ElementPlus,
createTestingPinia({
createSpy: vi.fn,
stubActions: false
stubActions: false,
}),
router
router,
],
provide: {
appConfig: ref(configResponse)
}
}
})


appConfig: ref(configResponse),
},
},
});

describe('View loads configuration on mount', async () => {
describe("View loads configuration on mount", async () => {
// onMount populates a Pinia store. Wait for reactivity to occur
await flushPromises()
await flushPromises();

test('Tabs in run-table configured and rendered', () => {
test("Tabs in run-table configured and rendered", () => {
for (const flowStatus of configResponse.qc_flow_statuses) {
let tabId = `[id="tab-${flowStatus['param']}"]`
expect(wrapper.find(tabId).exists()).toBe(true)
let tabId = `[id="tab-${flowStatus["param"]}"]`;
expect(wrapper.find(tabId).exists()).toBe(true);
}
})
});

test('Default tab is inbox', () => {
const inboxButton = wrapper.get('[id="tab-inbox"]')
expect(inboxButton.classes('is-active')).toBeTruthy()
})
test("Default tab is inbox", () => {
const inboxButton = wrapper.get('[id="tab-inbox"]');
expect(inboxButton.classes("is-active")).toBeTruthy();
});

test('Runs are rendered in rows', () => {
const table = wrapper.get('table')
expect(table.exists()).toBe(true)
const rows = table.findAll('tr')
expect(rows.length).toEqual(11) // 10 rows plus header
})
test("Runs are rendered in rows", () => {
const table = wrapper.get("table");
expect(table.exists()).toBe(true);
const rows = table.findAll("tr");
expect(rows.length).toEqual(11); // 10 rows plus header
});

test('Navigating to another tab makes things happen', async () => {
test("Navigating to another tab makes things happen", async () => {
// Anticipate the loading of new tab data
fetch.mockResponseOnce(
JSON.stringify({
page_size: 10,
page_number: 1,
total_number_of_items: 1,
qc_flow_status: "on_hold",
wells: [{
label: 'X1',
run_name: 'TRACTION-RUN-299',
run_start_time: "2023-04-24T10:10:10",
run_complete_time: "2023-05-25T02:10:10",
well_start_time: "2023-04-24T10:10:10",
well_complete_time: "2023-05-25T02:10:10",
run_status: "Complete",
well_status: "Complete",
qc_state: null,
instrument_name: '1234',
instrument_type: 'Revio',
study_names: [],
}]
wells: [
{
label: "X1",
run_name: "TRACTION-RUN-299",
run_start_time: "2023-04-24T10:10:10",
run_complete_time: "2023-05-25T02:10:10",
well_start_time: "2023-04-24T10:10:10",
well_complete_time: "2023-05-25T02:10:10",
run_status: "Complete",
well_status: "Complete",
qc_state: null,
instrument_name: "1234",
instrument_type: "Revio",
study_names: [],
},
],
})
)
);

await wrapper.get('[id="tab-on_hold"]').trigger('click')
await flushPromises() // Awaiting data change, but also router.isReady
await wrapper.get('[id="tab-on_hold"]').trigger("click");
await flushPromises(); // Awaiting data change, but also router.isReady

expect(fetch.requests().slice(-1)[0].url).toEqual('/api/pacbio/wells?qc_status=on_hold&page_size=10&page_number=1')
expect(fetch.requests().slice(-1)[0].url).toEqual(
"/api/pacbio/wells?qc_status=on_hold&page_size=10&page_number=1"
);
// Access the current route by strange fashion
expect(router.currentRoute.value.query).toEqual({activeTab: 'on_hold', page: '1'})
})
})
expect(router.currentRoute.value.query).toEqual({
activeTab: "on_hold",
page: "1",
});
});

test("Route changes when WellTable emits a runSelected event", async () => {
// console.log(wrapper.html())
await wrapper
.findComponent({ name: "well-table" })
.vm.$emit("runSelected", "TRACTION-RUN-299");
await flushPromises()
expect(router.currentRoute.value.path).toEqual(
"/ui/run/TRACTION-RUN-299"
);
});
});

0 comments on commit 6f5dd2f

Please sign in to comment.