Skip to content

Commit 08ed7cc

Browse files
authored
Create data visualizations for template queries (#103)
This commit adds the feature for generating visualizations for template queries. There are now two available sections to run queries from: 1. Overview 2. Templates Templates are pre-filtered by the selected class, and can be further filtered by selecting a category. Each category displays the total count of its templates. Note that some templates may be in more than one category, so tallying the category counts may not match the total count. The `Show All` category does display the correct total though. Currently the constraints within the templates fetch their values each time the popup is opened, and do not cache them when the components unmount. The same is true for fetching templates. This will be addressed in another story. Closes: #102 Squashed commits: * Allow switching between default and template views * Rename "supervisor" to "appManager" * Lift the default constraints into the app manager component * Lift switching app views to the app manager component * Display when no queries have been executed * Render category tags * Place category tags in an accordion * Display just the "Run Query" button for templates * Filter template constraints per class * Filter templates by selected categories * Remove categories with no available templates * Display the count for each category * Display only the tags for the selected class view * Render checkbox items for ONE OF constraints * Update the template query with the newly selected values * Run a query and update visualizations * Fetch values for a template constraint * Display a tag above a constraint widget for every selected value * Display the operation next to constraint widget tags * Display success icon if the template query had been executed * Display the description when hovering over the info icon * Disable the QueryController for template view * Display notification when no constraint items were received * Extract categories to prevent Template component re-renders * Update the categories when changing classes
1 parent 3294c58 commit 08ed7cc

28 files changed

+1429
-502
lines changed

.storybook/main.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ module.exports = {
1717
},
1818
],
1919
stories: [
20-
'../README.story.mdx',
21-
'../docs/**/*.story.mdx',
22-
'../src/**/*.story.jsx',
23-
'../docs/**/*.stories.mdx',
24-
'../src/**/*.stories.jsx',
20+
// Todo: Re-enable these after fixing them
21+
// '../README.story.mdx',
22+
// '../docs/**/*.story.mdx',
23+
// '../docs/**/*.stories.mdx',
24+
// '../src/**/*.story.jsx',
25+
// '../src/**/*.stories.jsx',
26+
'../src/**/TemplateConstraint.stories.jsx',
2527
],
2628
webpackFinal: async (config, { configType }) => {
2729
const { hasFoundAny, matches } = getLoaders(config, loaderByName('babel-loader'))
@@ -47,6 +49,9 @@ module.exports = {
4749
)
4850
}
4951

52+
// Allow absolute imports
53+
config.resolve.modules = [...(config.resolve.modules || []), path.resolve(__dirname, '..')]
54+
5055
return config
5156
},
5257
}

src/actionConstants.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ export const FETCH_INITIAL_SUMMARY = 'global/fetch/initial_summary'
77

88
export const LOCK_ALL_CONSTRAINTS = 'global/contraint/limit_reached'
99
export const RESET_ALL_CONSTRAINTS = 'global/constraint/remove_all'
10-
export const APPLY_CONSTRAINT_TO_QUERY = 'global/constraint/addQuery'
11-
export const DELETE_CONSTRAINT_FROM_QUERY = 'global/constraint/deleteQuery'
10+
export const APPLY_OVERVIEW_CONSTRAINT_TO_QUERY = 'global/constraint/addToQuery'
11+
export const DELETE_OVERVIEW_CONSTRAINT_FROM_QUERY = 'global/constraint/deleteQuery'
1212
export const UNSET_CONSTRAINT = 'global/constraint/unset'
1313

1414
export const SET_AVAILABLE_COLUMNS = 'global/table/set_columns'
@@ -24,22 +24,29 @@ export const ADD_QUERY_CONSTRAINT = 'queryController/constraint/add'
2424
*/
2525
export const ADD_CONSTRAINT = 'constraintFactory/add'
2626
export const REMOVE_CONSTRAINT = 'constraintFactory/remove'
27-
export const APPLY_CONSTRAINT = 'constraintFactory/apply'
27+
export const APPLY_DATA_BROWSER_CONSTRAINT = 'constraintFactory/apply'
2828
export const RESET_LOCAL_CONSTRAINT = 'constraintFactory/reset'
2929
export const FETCH_CONSTRAINT_ITEMS = 'constraintFactory/init'
30+
export const ADD_TEMPLATE_CONSTRAINT = 'constraint/template/add'
31+
export const TEMPLATE_CONSTRAINT_UPDATED = 'constraint/template/updated'
3032

3133
/**
3234
* Pie Chart
3335
*/
3436
export const SET_INITIAL_ORGANISMS = 'pieChart/fetch/initial'
3537

3638
/**
37-
* Supervisor
39+
* AppManager
3840
*/
39-
export const CHANGE_MINE = 'supervisor/mine/change'
40-
export const CHANGE_CLASS = 'supervisor/class/change'
41+
export const CHANGE_MINE = 'appManager/mine/change'
42+
export const CHANGE_CLASS = 'appManager/class/change'
43+
export const CHANGE_CONSTRAINT_VIEW = 'appManager/view/change'
44+
export const TOGGLE_CATEGORY_VISIBILITY = 'appManager/category/visibility'
45+
export const TOGGLE_VIEW_IS_LOADING = 'appManager/view/isLoading'
4146

4247
/**
4348
* Table
4449
*/
4550
export const CHANGE_PAGE = 'table/page/change'
51+
52+
export const UPDATE_TEMPLATE_QUERIES = 'templateView/templates/update'

src/blueprintIcons.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exports.usedIcons = [
77
IconNames.UNLOCK,
88
IconNames.LOCK,
99
IconNames.CARET_DOWN,
10+
IconNames.CARET_RIGHT,
1011
IconNames.ERROR,
1112
IconNames.FLASH,
1213
IconNames.MOON,
@@ -21,4 +22,5 @@ exports.usedIcons = [
2122
IconNames.WARNING_SIGN,
2223
IconNames.REMOVE,
2324
IconNames.DELETE,
25+
IconNames.INFO_SIGN,
2426
]

src/components/App.jsx

Lines changed: 183 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,186 @@ import '@emotion/core'
44
import { assign } from '@xstate/immer'
55
import { enableMapSet } from 'immer'
66
import React, { useEffect } from 'react'
7-
import { CHANGE_CLASS, CHANGE_MINE, FETCH_INITIAL_SUMMARY } from 'src/actionConstants'
7+
import {
8+
CHANGE_CLASS,
9+
CHANGE_CONSTRAINT_VIEW,
10+
CHANGE_MINE,
11+
FETCH_INITIAL_SUMMARY,
12+
TOGGLE_CATEGORY_VISIBILITY,
13+
TOGGLE_VIEW_IS_LOADING,
14+
UPDATE_TEMPLATE_QUERIES,
15+
} from 'src/actionConstants'
816
import { fetchClasses, fetchInstances } from 'src/fetchSummary'
9-
import { sendToBus, SupervisorServiceContext, useMachineBus } from 'src/machineBus'
10-
import { Machine } from 'xstate'
17+
import { AppManagerServiceContext, sendToBus, useMachineBus } from 'src/machineBus'
18+
import { forwardTo, Machine } from 'xstate'
1119

1220
import { ConstraintSection } from './Layout/ConstraintSection'
1321
import { ChartSection, TableSection } from './Layout/DataVizSection'
1422
import { Header } from './Layout/Header'
23+
import { templateViewMachine } from './templateViewMachine'
1524

1625
enableMapSet()
1726

18-
const isProduction = process.env.NODE_ENV === 'production'
27+
// Todo: Change this after fixing biotestmine dev environment
28+
const isProduction = true
1929

20-
const supervisorMachine = Machine(
30+
/** @type {import('../types').ConstraintConfig[]} */
31+
const defaultQueries = [
2132
{
22-
id: 'Supervisor',
33+
type: 'checkbox',
34+
name: 'Organism',
35+
label: 'Or',
36+
path: 'organism.shortName',
37+
op: 'ONE OF',
38+
valuesQuery: {
39+
select: ['primaryIdentifier'],
40+
model: {
41+
name: 'genomic',
42+
},
43+
where: [],
44+
},
45+
},
46+
{
47+
type: 'select',
48+
name: 'Pathway Name',
49+
label: 'Pn',
50+
path: 'pathways.name',
51+
op: 'ONE OF',
52+
valuesQuery: {
53+
select: ['pathways.name', 'primaryIdentifier'],
54+
model: {
55+
name: 'genomic',
56+
},
57+
orderBy: [
58+
{
59+
path: 'pathways.name',
60+
direction: 'ASC',
61+
},
62+
],
63+
},
64+
},
65+
{
66+
type: 'select',
67+
name: 'GO Annotation',
68+
label: 'GA',
69+
path: 'goAnnotation.ontologyTerm.name',
70+
op: 'ONE OF',
71+
valuesQuery: {
72+
select: ['goAnnotation.ontologyTerm.name', 'primaryIdentifier'],
73+
model: {
74+
name: 'genomic',
75+
},
76+
orderBy: [
77+
{
78+
path: 'Gene.goAnnotation.ontologyTerm.name',
79+
direction: 'ASC',
80+
},
81+
],
82+
},
83+
},
84+
]
85+
86+
const appManagerMachine = Machine(
87+
{
88+
id: 'AppManager',
2389
initial: 'loading',
2490
context: {
91+
appView: 'defaultView',
2592
classView: 'Gene',
2693
intermines: [],
2794
modelClasses: [],
95+
showAllLabel: 'Show All',
96+
possibleQueries: defaultQueries,
97+
categories: {},
98+
viewIsLoading: false,
2899
selectedMine: {
29100
name: isProduction ? 'HumanMine' : 'biotestmine',
30101
rootUrl: isProduction
31102
? 'https://www.humanmine.org/humanmine'
32103
: 'http://localhost:9999/biotestmine',
33104
},
34105
},
106+
on: {
107+
[CHANGE_MINE]: { target: 'loading', actions: 'changeMine' },
108+
[CHANGE_CLASS]: { actions: ['changeClass'] },
109+
[UPDATE_TEMPLATE_QUERIES]: { actions: 'updateTemplateQueries' },
110+
[TOGGLE_VIEW_IS_LOADING]: { actions: 'toggleViewIsLoading' },
111+
[TOGGLE_CATEGORY_VISIBILITY]: { actions: 'forwardToTemplateView' },
112+
[CHANGE_CONSTRAINT_VIEW]: [
113+
{ target: 'defaultView', cond: 'isDefaultView' },
114+
{ target: 'templateView', cond: 'isTemplateView' },
115+
{ target: 'invalidAppView' },
116+
],
117+
},
35118
states: {
36119
loading: {
120+
on: {
121+
[CHANGE_CLASS]: { actions: 'changeClass' },
122+
},
37123
invoke: {
38124
id: 'fetchMines',
39125
src: 'fetchMinesAndClasses',
40126
onDone: {
41-
target: 'idle',
127+
target: 'defaultView',
42128
actions: 'setIntermines',
43129
},
44130
onError: {
45-
target: 'idle',
131+
target: 'defaultView',
46132
actions: (ctx, event) =>
47133
console.error('FETCH: could not retrieve intermines', { ctx, event }),
48134
},
49135
},
50136
},
51-
idle: {
137+
defaultView: {
138+
entry: ['setBrowserView', 'setPossibleOverviewQueries'],
52139
on: {
53-
[CHANGE_MINE]: { target: 'loading', actions: 'changeMine' },
54140
[CHANGE_CLASS]: { actions: 'changeClass' },
55141
},
56142
},
143+
templateView: {
144+
entry: ['setTemplateView', 'clearPossibleQueries'],
145+
on: {
146+
[CHANGE_CLASS]: { actions: ['changeClass', 'forwardToTemplateView'] },
147+
},
148+
invoke: {
149+
id: 'templateView',
150+
src: templateViewMachine,
151+
data: {
152+
// Hack until xstate v5 introduces shallow merging
153+
...templateViewMachine.context,
154+
classView: (ctx) => ctx.classView,
155+
showAllLabel: (ctx) => ctx.showAllLabel,
156+
rootUrl: (ctx) => ctx.selectedMine.rootUrl,
157+
},
158+
},
159+
},
160+
invalidAppView: {},
57161
},
58162
},
59163
{
60164
actions: {
165+
forwardToTemplateView: forwardTo('templateView'),
166+
// @ts-ignore
167+
toggleViewIsLoading: assign((ctx, { isLoading }) => {
168+
ctx.viewIsLoading = isLoading
169+
}),
170+
// @ts-ignore
171+
updateTemplateQueries: assign((ctx, { queries, categories }) => {
172+
ctx.possibleQueries = queries
173+
ctx.categories = categories
174+
}),
175+
setTemplateView: assign((ctx) => {
176+
ctx.appView = 'templateView'
177+
}),
178+
clearPossibleQueries: assign((ctx) => {
179+
ctx.possibleQueries = []
180+
}),
181+
setPossibleOverviewQueries: assign((ctx) => {
182+
ctx.possibleQueries = defaultQueries
183+
}),
184+
setBrowserView: assign((ctx) => {
185+
ctx.appView = 'defaultView'
186+
}),
61187
// @ts-ignore
62188
changeMine: assign((ctx, { newMine }) => {
63189
ctx.selectedMine = ctx.intermines.find((mine) => mine.name === newMine)
@@ -76,8 +202,22 @@ const supervisorMachine = Machine(
76202
.map((cl) => ({ displayName: cl.displayName, name: cl.name }))
77203
}),
78204
},
205+
guards: {
206+
// @ts-ignore
207+
isDefaultView: (_, { newTabId }) => {
208+
return newTabId === 'defaultView'
209+
},
210+
// @ts-ignore
211+
isTemplateView: (_, { newTabId }) => {
212+
return newTabId === 'templateView'
213+
},
214+
// @ts-ignore
215+
showAllCategories: (ctx, { tagName }) => {
216+
return tagName === ctx.showAllLabel
217+
},
218+
},
79219
services: {
80-
fetchMinesAndClasses: async (ctx, event) => {
220+
fetchMinesAndClasses: async (ctx) => {
81221
let instances
82222
let modelClasses
83223

@@ -109,27 +249,52 @@ const supervisorMachine = Machine(
109249
)
110250

111251
export const App = () => {
112-
const [state, send] = useMachineBus(supervisorMachine)
252+
const [state, send] = useMachineBus(appManagerMachine)
253+
254+
const {
255+
appView,
256+
classView,
257+
possibleQueries,
258+
categories,
259+
selectedMine,
260+
showAllLabel,
261+
viewIsLoading,
262+
} = state.context
113263

114-
const { classView, selectedMine } = state.context
115264
const rootUrl = selectedMine.rootUrl
116265

117266
useEffect(() => {
118-
sendToBus({ type: FETCH_INITIAL_SUMMARY, globalConfig: { rootUrl, classView } })
119-
}, [rootUrl, classView])
267+
if (state.matches('defaultView')) {
268+
sendToBus({ type: FETCH_INITIAL_SUMMARY, globalConfig: { rootUrl, classView } })
269+
}
270+
}, [rootUrl, classView, state])
271+
272+
const toggleCategory = ({ isVisible, tagName }) => {
273+
send({ type: TOGGLE_CATEGORY_VISIBILITY, isVisible, tagName })
274+
}
120275

121276
return (
122277
<div className="light-theme">
123-
<SupervisorServiceContext.Provider value={{ state, send }}>
278+
<AppManagerServiceContext.Provider value={{ state, send }}>
124279
<Header />
125-
</SupervisorServiceContext.Provider>
280+
</AppManagerServiceContext.Provider>
126281
<main
127282
css={{
128283
display: 'grid',
129284
gridTemplateColumns: '230px 1fr',
130285
}}
131286
>
132-
<ConstraintSection />
287+
<ConstraintSection
288+
queries={possibleQueries}
289+
view={appView}
290+
classCategoryTags={Object.values(categories)}
291+
isLoading={viewIsLoading}
292+
toggleCategory={toggleCategory}
293+
showAllLabel={showAllLabel}
294+
classView={classView}
295+
rootUrl={rootUrl}
296+
showAll={categories[showAllLabel]?.isVisible ?? true}
297+
/>
133298
<section
134299
css={{
135300
padding: '10px 30px 0',

src/components/Constraints/CheckboxPopup.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const CheckboxPopup = ({ nonIdealTitle = undefined, nonIdealDescription =
1010

1111
const { availableValues, selectedValues } = state?.context
1212

13-
if (availableValues.length === 0) {
13+
if (availableValues?.length === 0) {
1414
return <NoValuesProvided title={nonIdealTitle} description={nonIdealDescription} />
1515
}
1616

@@ -25,11 +25,12 @@ export const CheckboxPopup = ({ nonIdealTitle = undefined, nonIdealDescription =
2525
return (
2626
<div>
2727
<Label className="sr-only">Select organisms to set constraints</Label>
28-
{availableValues.map((value) => {
28+
{availableValues?.map((value) => {
29+
const label = value.count ? `${value.item} (${value.count})` : `${value.item}`
2930
return (
3031
<Checkbox
3132
key={value.item}
32-
label={`${value.item} (${value.count})`}
33+
label={label}
3334
checked={selectedValues.some((name) => name === value.item)}
3435
onChange={onChangeHandler(value.item)}
3536
/>

0 commit comments

Comments
 (0)