Skip to content

Commit 9114d24

Browse files
committed
reports page
1 parent 434fd78 commit 9114d24

File tree

9 files changed

+257
-36
lines changed

9 files changed

+257
-36
lines changed

model/src/helpers.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// (!!!) TO be moved to a common SDK helper library library
3+
//
4+
5+
import { TreeNodeAccessor } from '@platforma-sdk/model';
6+
7+
8+
export const ResourceMapResourceTypeName = 'PColumnData/ResourceMap';
9+
export const ResourceMapResourcePartitionedTypeName = 'PColumnData/Partitioned/ResourceMap';
10+
11+
export type PColumnKey = (string | number)[];
12+
13+
export type PColumnResourceMapEntry<T> = {
14+
key: PColumnKey;
15+
value: T;
16+
};
17+
18+
export type PColumnResourceMapData<T> = {
19+
isComplete: boolean;
20+
data: PColumnResourceMapEntry<T>[];
21+
};
22+
23+
function populateResourceMapData<T>(
24+
acc: TreeNodeAccessor | undefined,
25+
resourceParser: (acc: TreeNodeAccessor) => T | undefined,
26+
data: PColumnResourceMapEntry<T>[],
27+
keyPrefix: PColumnKey = []
28+
): boolean {
29+
if (acc === undefined) return false;
30+
switch (acc.resourceType.name) {
31+
case ResourceMapResourceTypeName: {
32+
let isComplete = acc.getInputsLocked();
33+
for (const keyStr of acc.listInputFields()) {
34+
const value = acc.resolve({ field: keyStr, assertFieldType: 'Input' });
35+
if (value === undefined) isComplete = false;
36+
else {
37+
const key = [...keyPrefix, ...JSON.parse(keyStr)] as PColumnKey;
38+
const converted = resourceParser(value);
39+
if (converted === undefined) isComplete = false;
40+
else data.push({ key, value: converted });
41+
}
42+
}
43+
return isComplete;
44+
}
45+
case ResourceMapResourcePartitionedTypeName: {
46+
let isComplete = acc.getInputsLocked();
47+
for (const keyStr of acc.listInputFields()) {
48+
const value = acc.resolve({ field: keyStr, assertFieldType: 'Input' });
49+
if (value === undefined) isComplete = false;
50+
else {
51+
const key = [...keyPrefix, ...JSON.parse(keyStr)] as PColumnKey;
52+
isComplete = isComplete && populateResourceMapData(value, resourceParser, data, key);
53+
}
54+
}
55+
return isComplete;
56+
}
57+
default:
58+
throw new Error(`Unknown resource type: ${acc.resourceType.name}`);
59+
}
60+
}
61+
62+
export function parseResourceMap<T>(
63+
acc: TreeNodeAccessor | undefined,
64+
resourceParser: (acc: TreeNodeAccessor) => T | undefined
65+
): PColumnResourceMapData<NonNullable<T>> {
66+
const data: PColumnResourceMapEntry<NonNullable<T>>[] = [];
67+
const isComplete = populateResourceMapData(acc, resourceParser, data, []);
68+
return { isComplete, data };
69+
}

model/src/index.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
InferOutputsType,
77
PlDataTableState,
88
isPColumn,
9-
isPColumnSpec,
9+
isPColumnSpec
1010
} from '@platforma-sdk/model';
11+
import { parseResourceMap } from './helpers';
1112

1213
/**
1314
* Block arguments coming from the user interface
@@ -18,29 +19,35 @@ export type BlockArgs = {
1819
};
1920

2021
export type TreeSelection = {
21-
donor?: string,
22-
treeId?: number
23-
}
22+
donor?: string;
23+
treeId?: number;
24+
};
25+
26+
export type ReportSelection = {
27+
donor?: string;
28+
type: 'alleles' | 'shmTrees';
29+
};
2430

2531
export type UiState = {
2632
treeTableState?: PlDataTableState;
27-
treeSelectionForTreeNodesTable: TreeSelection
33+
treeSelectionForTreeNodesTable: TreeSelection;
34+
reportSelection: ReportSelection;
2835
};
2936

3037
export type ColumnOption = {
31-
ref: Ref,
32-
label: string,
33-
spec: PColumnSpec
34-
}
38+
ref: Ref;
39+
label: string;
40+
spec: PColumnSpec;
41+
};
3542

3643
export const platforma = BlockModel.create<BlockArgs, UiState>('Heavy')
3744

3845
.initialArgs({
3946
datasetColumns: [null]
4047
})
4148

42-
.output('donorColumnOptions', (ctx) => {
43-
return ctx.resultPool
49+
.output('donorColumnOptions', (ctx) =>
50+
ctx.resultPool
4451
.getSpecsFromResultPool()
4552
.entries.filter((v) => isPColumnSpec(v.obj))
4653
.filter((v) => {
@@ -55,8 +62,8 @@ export const platforma = BlockModel.create<BlockArgs, UiState>('Heavy')
5562
v.obj.annotations?.['pl7.app/label'] ?? `unlabelled`
5663
}`
5764
} satisfies Option)
58-
);
59-
})
65+
)
66+
)
6067

6168
.output('datasetColumnOptions', (ctx) => {
6269
if (ctx.args.donorColumn === undefined) {
@@ -117,20 +124,27 @@ export const platforma = BlockModel.create<BlockArgs, UiState>('Heavy')
117124
.output('treeNodes', (ctx) => {
118125
const pCols = ctx.outputs?.resolve('treeNodes')?.getPColumns();
119126
if (pCols === undefined) return undefined;
120-
127+
121128
return ctx.createPFrame(pCols);
122129
})
123130

124-
.output('allelesLog', (ctx) => {
125-
return ctx.outputs?.resolve('allelesLog')?.listInputFields();
126-
})
131+
.output('allelesReports', (ctx) =>
132+
parseResourceMap(
133+
ctx.outputs?.resolve({ field: 'allelesReports', assertFieldType: 'Input' }),
134+
(acc) => acc.getFileContentAsString()
135+
)
136+
)
127137

128-
.output('treesLog', (ctx) => {
129-
return ctx.outputs?.resolve('treesLog')?.listInputFields();
130-
})
138+
.output('treesReports', (ctx) =>
139+
parseResourceMap(
140+
ctx.outputs?.resolve({ field: 'treesReports', assertFieldType: 'Input' }),
141+
(acc) => acc.getFileContentAsString()
142+
)
143+
)
131144

132145
.sections([
133146
{ type: 'link', href: '/', label: 'Settings' },
147+
{ type: 'link', href: '/reports', label: 'Reports' },
134148
{ type: 'link', href: '/trees', label: 'Trees Table' },
135149
{ type: 'link', href: '/treeNodes', label: 'Tree Nodes Table' }
136150
])

ui/src/ReportsPage.vue

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<script setup lang="ts">
2+
import { platforma } from '@platforma-open/milaboratories.mixcr-shm-trees.model';
3+
import { useApp } from './app';
4+
import { computed, reactive, watch } from 'vue';
5+
import { ListOption, PlBlockPage, PlBtnGroup, PlDropdown, PlTextArea, SimpleOption } from '@platforma-sdk/ui-vue';
6+
7+
const app = useApp();
8+
9+
const uiState = app.createUiModel({}, () => ({
10+
treeSelectionForTreeNodesTable: {},
11+
reportSelection: {
12+
type: 'alleles'
13+
}
14+
}))
15+
16+
type State = {
17+
donors?: ListOption<string>[]
18+
}
19+
20+
const state = reactive({} as State)
21+
22+
watch(() => app.getOutputFieldOkOptional('allelesReports'), (reports) => {
23+
if (reports === undefined) {
24+
delete state.donors
25+
return
26+
}
27+
const donors = []
28+
for (const data of reports.data) {
29+
donors.push(data.key[0] as string)
30+
}
31+
state.donors = donors.map((donor) => ({ text: donor, value: donor }))
32+
33+
// if previously selected donor isn't in available options, remove selection
34+
const selectedDonor = uiState.model.reportSelection.donor
35+
if (!(selectedDonor === undefined) && donors.find((o) => o === selectedDonor) === undefined) {
36+
delete uiState.model.reportSelection.donor
37+
}
38+
}, { immediate: true })
39+
40+
const reportText = computed(() => {
41+
const selecetedDonor = uiState.model.reportSelection.donor
42+
if (selecetedDonor === undefined) {
43+
return undefined
44+
}
45+
46+
var field
47+
switch(uiState.model.reportSelection.type) {
48+
case 'alleles': {
49+
field = app.getOutputFieldOkOptional('allelesReports')
50+
break
51+
}
52+
case 'shmTrees': {
53+
field = app.getOutputFieldOkOptional('treesReports')
54+
break
55+
}
56+
}
57+
58+
if (field === undefined) {
59+
return undefined
60+
}
61+
62+
const selectedReport = field.data.find((data) => data.key[0] === selecetedDonor)
63+
64+
if (selectedReport === undefined || selectedReport.value === undefined) {
65+
return undefined
66+
}
67+
68+
return selectedReport.value
69+
})
70+
71+
const reportOptions = [
72+
{
73+
text: 'Alleles inference report',
74+
value: 'alleles'
75+
},
76+
{
77+
text: 'SHM trees reconstruction report',
78+
value: 'shmTrees'
79+
},
80+
]
81+
82+
</script>
83+
84+
<template>
85+
<PlBlockPage>
86+
<template v-if="state.donors">
87+
<PlDropdown v-model=uiState.model.reportSelection.donor :options=state.donors clearable>Show for donor</PlDropdown>
88+
<PlBtnGroup v-model=uiState.model.reportSelection.type :options=reportOptions />
89+
<template v-if="uiState.model.reportSelection.donor">
90+
<template v-if="reportText">
91+
<PlTextArea v-model="reportText" :rows=50 />
92+
</template>
93+
<template v-else>
94+
Waiting...
95+
</template>
96+
</template>
97+
</template>
98+
<template v-else>
99+
Waiting for reports...
100+
</template>
101+
</PlBlockPage>
102+
</template>

ui/src/TreeNodesTablePage.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ const app = useApp();
2323
const pFrameDriver = platforma.pFrameDriver
2424
2525
const uiState = app.createUiModel({}, () => ({
26-
treeSelectionForTreeNodesTable: {}
26+
treeSelectionForTreeNodesTable: {},
27+
reportSelection: {
28+
type: 'alleles'
29+
}
2730
}))
2831
2932
const state = reactive({} as State)
@@ -153,18 +156,20 @@ const treesOptions = computed(() => {
153156
<template v-if="state.donors">
154157
<PlBlockPage>
155158
<PlRow>
156-
<PlDropdown :options="donorOptions ?? []" v-model="donorProperty" label="Donor" clearable />
159+
<PlDropdown :options="donorOptions ?? []" v-model=donorProperty label="Donor" clearable />
157160
<PlSpacer/>
158-
<PlDropdown :options="treesOptions ?? []" v-model="uiState.model.treeSelectionForTreeNodesTable.treeId" label="Tree" clearable />
161+
<PlDropdown :options="treesOptions ?? []" v-model=uiState.model.treeSelectionForTreeNodesTable.treeId label="Tree" clearable />
159162
</PlRow>
160163
<!-- TODO generate and save title -->
161164
<GraphMaker
162165
v-if="app.outputs.treeNodes?.ok && app.outputs.treeNodes.value &&
163166
!(uiState.model.treeSelectionForTreeNodesTable.donor === undefined || uiState.model.treeSelectionForTreeNodesTable.treeId === undefined || settings === undefined)"
164167
:p-frame-handle=app.outputs.treeNodes.value
165168
:settings=settings
169+
@settings-update=""
166170
:p-frame-driver=platforma.pFrameDriver
167171
graph-title="Title"
172+
@graph-title-update=""
168173
/>
169174
</PlBlockPage>
170175
</template>

ui/src/TreeTablePage.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { PlAgDataTable, PlBlockPage, PlDataTableSettings } from '@platforma-sdk/
77
const app = useApp();
88
99
const uiState = app.createUiModel({}, () => ({
10-
treeSelectionForTreeNodesTable: {}
10+
treeSelectionForTreeNodesTable: {},
11+
reportSelection: {
12+
type: 'alleles'
13+
}
1114
}))
1215
1316
const tableSettings = computed<PlDataTableSettings>(() => ({

ui/src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { defineApp } from '@platforma-sdk/ui-vue';
33
import SettingsPage from './SettingsPage.vue';
44
import TreeTablePage from './TreeTablePage.vue';
55
import TreeNodesTablePage from './TreeNodesTablePage.vue';
6+
import ReportsPage from './ReportsPage.vue';
67

78
export const sdkPlugin = defineApp(platforma, () => {
89
return {
910
routes: {
1011
'/': SettingsPage,
12+
'/reports': ReportsPage,
1113
'/trees': TreeTablePage,
1214
'/treeNodes': TreeNodesTablePage
1315
}

workflow/src/main.tpl.tengo

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ wf := import("@platforma-sdk/workflow-tengo:workflow")
33
render := import("@platforma-sdk/workflow-tengo:render")
44
assets := import("@platforma-sdk/workflow-tengo:assets")
55
ll := import("@platforma-sdk/workflow-tengo:ll")
6+
pframes := import("@platforma-sdk/workflow-tengo:pframes")
67

78
processTpl := assets.importTemplate(":process")
89

@@ -32,10 +33,14 @@ wf.body(func(args) {
3233

3334
return {
3435
outputs: {
35-
"allelesLog": results.output("allelesLog"),
36-
"treesLog": results.output("treesLog"),
3736
"trees": results.output("trees"),
38-
"treeNodes": results.output("treeNodes")
37+
"treeNodes": results.output("treeNodes"),
38+
39+
"allelesLogs": results.output("allelesLogs"),
40+
"treesLogs": results.output("treesLogs"),
41+
42+
"allelesReports": pframes.exportColumnData(results.output("allelesReports")),
43+
"treesReports": pframes.exportColumnData(results.output("treesReports"))
3944
},
4045
exports: {}
4146
}

0 commit comments

Comments
 (0)