Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
dbolotin committed Sep 5, 2024
1 parent 9e9da51 commit c4bf405
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 61 deletions.
36 changes: 17 additions & 19 deletions model/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import {
} from '@milaboratory/sdk-ui';
import { BlockArgs, BlockArgsValid } from './args';
import { parseResourceMap } from './helpers';
import { ProgressPrefix } from './logs';
import { SupportedPresetList } from './preset';

export const platforma = BlockModel.create<BlockArgs>('Heavy')

.initialArgs({})

.output('presets', (ctx) =>
ctx.prerun?.resolve({ field: 'presets', assertFieldType: 'Input' })?.getFileHandle()
SupportedPresetList.parse(
ctx.prerun?.resolve({ field: 'presets', assertFieldType: 'Input' })?.getFileContentAsJson()
)
)

.output('preset', (ctx) =>
Expand All @@ -35,20 +39,18 @@ export const platforma = BlockModel.create<BlockArgs>('Heavy')
)
)

.output('clonesSpec', (ctx) => {
const collection = ctx.outputs
?.resolve({ field: 'clones', assertFieldType: 'Input' })
?.parsePObjectCollection();
if (collection === undefined) return undefined;
// if (collection === undefined || !collection.isComplete) return undefined;
const pColumns = Object.entries(collection)
.map(([id, obj]) => obj)
.filter(isPColumn);
return pColumns.map((obj) => obj.spec);
// parseResourceMap(ctx.outputs?.resolve({ field: 'clones', assertFieldType: 'Input' }), (acc) =>
// acc.listInputFields()
// )
// return ctx.outputs?.resolve({ field: 'clones', assertFieldType: 'Input' })?.listInputFields();
.output('logs', (ctx) => {
return parseResourceMap(
ctx.outputs?.resolve({ field: 'logs', assertFieldType: 'Input' }),
(acc) => acc.getLogHandle()
);
})

.output('progress', (ctx) => {
return parseResourceMap(
ctx.outputs?.resolve({ field: 'logs', assertFieldType: 'Input' }),
(acc) => acc.getProgressLog(ProgressPrefix)
);
})

.output('clones', (ctx) => {
Expand All @@ -61,10 +63,6 @@ export const platforma = BlockModel.create<BlockArgs>('Heavy')
.map(([id, obj]) => obj)
.filter(isPColumn);
return ctx.createPFrame(pColumns);
// parseResourceMap(ctx.outputs?.resolve({ field: 'clones', assertFieldType: 'Input' }), (acc) =>
// acc.listInputFields()
// )
// return ctx.outputs?.resolve({ field: 'clones', assertFieldType: 'Input' })?.listInputFields();
})

.output('inputOptions', (ctx) => {
Expand Down
1 change: 1 addition & 0 deletions model/src/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ProgressPrefix = '[==PROGRESS==]';
42 changes: 42 additions & 0 deletions model/src/preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { z } from 'zod';

export const MiXCRStage = z.union([
z.literal('parse'),
z.literal('refineTags'),
z.literal('consensus'),
z.literal('align'),
z.literal('refineTagsAndSort'),
z.literal('exportAlignments'),
z.literal('assemblePartial'),
z.literal('extend'),
z.literal('assemble'),
z.literal('assembleContigs'),
z.literal('assembleCells'),
z.literal('exportClones'),
z.literal('exportCloneGroups'),
z.literal('qc'),
z.literal('findAlleles'),
z.literal('findShmTrees')
]);
export type MiXCRStage = z.infer<typeof MiXCRStage>;

export const SupportedMiXCRFlags = z.literal('species');
export type SupportedMiXCRFlags = z.infer<typeof SupportedMiXCRFlags>;

export const SupportedPresetOverview = z.object({
vendor: z.string().nullable(),
label: z.string(),
presetName: z.string(),
requiredFlags: z.array(SupportedMiXCRFlags),
analysisStages: z.array(MiXCRStage),
reportTypes: z.array(MiXCRStage)
});
export type SupportedPresetOverview = z.infer<typeof SupportedPresetOverview>;

export const SupportedPresetList = z.array(z.unknown()).transform((presets) =>
presets
.map((p) => SupportedPresetOverview.safeParse(p))
.filter((p) => p.success)
.map((p) => p.data!)
);
export type SupportedPresetList = z.infer<typeof SupportedPresetList>;
2 changes: 1 addition & 1 deletion test/src/wf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,6 @@ blockTest(
console.log(clonesPfColumnList)
expect(clonesPfColumnList).length.to.greaterThanOrEqual(1);

// console.dir(clonotypingStableState2, { depth: 8 });
console.dir(clonotypingStableState2, { depth: 8 });
}
);
20 changes: 12 additions & 8 deletions ui/src/SettingsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ const inputOptions = computed(() =>
const args = app.createArgsModel();
const presets = asyncComputed(async () => {
const presetsFile = app.getOutputFieldOkOptional("presets")
if (presetsFile === undefined)
return undefined;
return JSON.parse(new TextDecoder().decode(await platforma.blobDriver.getContent(presetsFile.handle)))
})
// const presets = asyncComputed(async () => {
// const presetsFile = app.getOutputFieldOkOptional("presets")
// if (presetsFile === undefined)
// return undefined;
// return JSON.parse(new TextDecoder().decode(await platforma.blobDriver.getContent(presetsFile.handle)))
// })
const presets = computed(() => app.getOutputFieldOkOptional("presets"))
const presetOptions = computed(() => {
return presets.value?.map((preset: any) => ({ text: `${preset.label}${preset.vendor ? ' - ' + preset.vendor : ''}`, value: preset.presetName }))
return presets.value?.map(preset => ({ text: `${preset.label}${preset.vendor ? ' - ' + preset.vendor : ''}`, value: preset.presetName }))
})
const preset = computed(() => app.args.preset === undefined ? undefined : presets.value?.find((p: any) => p.presetName === app.args.preset))
const preset = computed(() => app.args.preset === undefined ? undefined : presets.value?.find(p => p.presetName === app.args.preset))
const needSpecies = computed(() => app.args.preset === undefined ? undefined : presets.value?.find(p => p.presetName === app.args.preset))
</script>

Expand Down
35 changes: 23 additions & 12 deletions workflow/src/get-preset.tpl.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,35 @@ self := import("@milaboratory/tengo-sdk:tpl")
ll := import("@milaboratory/tengo-sdk:ll")
exec := import("@milaboratory/tengo-sdk:exec")

self.validateInputs({
"__options__,closed": "",
"request": {
"__options__,closed": "",
"presetName": "string",
"species,omitempty": "string"
}
})

self.defineOutputs("preset")

self.body(func(inputs) {
preset := inputs.preset
presetName := inputs.request.presetName
species := inputs.request.species

mixcrCmdBuilder := exec.builder().
printErrStreamToStdout().
cmd("mixcr").
arg("exportPreset").
arg("--preset-name").
arg(presetName)

if !is_string(preset) {
ll.panic("preset is not string: %v", preset)
if !is_undefined(species) {
mixcrCmdBuilder.arg("--species").arg(species)
}

mixcrCmd := exec.builder().
printErrStreamToStdout().
cmd("mixcr").
arg("exportPreset").
arg("--preset-name").
arg(preset).
arg("preset.json").
saveFileContent("preset.json").
run()
mixcrCmd := mixcrCmdBuilder.arg("preset.json").
saveFileContent("preset.json").
run()

presetContent := mixcrCmd.getFileContent("preset.json")

Expand Down
12 changes: 9 additions & 3 deletions workflow/src/main.tpl.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ wf.body(func(args) {
inputRef := args.input

preset := args.preset
species := args.species

input := wf.resolve(inputRef)

getPreset := render.create(getPresetTpl, {
preset: args.preset
request: {
presetName: preset,
species: species
}
})
presetContent := getPreset.output("preset", 24 * 60 * 60 * 1000)

Expand All @@ -38,7 +42,8 @@ wf.body(func(args) {

runMixcr := render.createEphemeral(processTpl, {
params: smart.createJsonResource({
preset: preset
preset: preset,
species: species
}),
presetContent: presetContent,
pfconvParams: pfconvParams,
Expand All @@ -55,12 +60,13 @@ wf.body(func(args) {
spec: runMixcr.output("reports.spec"),
data: runMixcr.output("reports.data")
},
clonse: runMixcr.output("clones")
clones: runMixcr.output("clones")
}

outputs := {
qc: pframes.exportColumnData(runMixcr.output("qc.data")),
reports: pframes.exportColumnData(runMixcr.output("reports.data")),
logs: runMixcr.output("logs.data"),
clones: pframes.exportFrame(runMixcr.output("clones"))
}

Expand Down
27 changes: 15 additions & 12 deletions workflow/src/mixcr-analyze.tpl.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ json := import("json")

exportClonesTpl := assets.importTemplate(":export-clones")

self.defineOutputs("qc", "reports", "clones")
self.defineOutputs("qc", "reports", "clones", "log")

progressPrefix := "[==PROGRESS==]"

self.body(func(inputs) {
inputData := inputs[pConstants.VALUE_FIELD_NAME]

params := inputs.params
preset := params.preset
species := params.species
fileExtension := params.fileExtension
reports := params.reports

Expand All @@ -37,10 +40,16 @@ self.body(func(inputs) {

if inputDataMeta.keyLength == 1 {
mixcrCmdBuilder := exec.builder().
printErrStreamToStdout().
cmd("mixcr").
arg("analyze").
arg(preset)
printErrStreamToStdout().
env("MI_PROGRESS_PREFIX", progressPrefix).
cmd("mixcr").
arg("analyze")

if !is_undefined(species) {
mixcrCmdBuilder.arg("--species").arg(species)
}

mixcrCmdBuilder.arg(preset)

// The following code is split into two loops to maintain correct file index ordering (R1, R2, ...)

Expand Down Expand Up @@ -94,17 +103,11 @@ self.body(func(inputs) {

result := {
qc: mixcrCmd.getFile("result.qc.json"),
log: mixcrCmd.getStdoutStream(),
reports: reportsMap.lockAndBuild(),
clones: clones
}

// cloneColumns := smart.mapBuilder()

// for cloneColumn in pfconvParams.columns {
// cloneColumns.add(cloneColumn.id, )
// result["clones/" + cloneColumn.id] = clones.getFutureInputField(cloneColumn.id)
// }

return result
} else {
ll.panic("not yet supported")
Expand Down
13 changes: 13 additions & 0 deletions workflow/src/process.tpl.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ self.body(func(inputs) {
aggregationTargets := [{
type: "Resource",
name: "qc"
}, {
type: "Resource",
name: "log"
}, {
type: "ResourceMap",
name: "reports",
Expand Down Expand Up @@ -114,6 +117,16 @@ self.body(func(inputs) {
},
"qc.data": mixcrResults.output("qc"),

"logs.spec": {
kind: "PColumn",
name: "pl7.app/log",
valueType: "Log",
axesSpec: [
inputSpec.axesSpec[0]
]
},
"logs.data": mixcrResults.output("log"),

"reports.spec": {
kind: "PColumn",
name: "mixcr.com/report",
Expand Down
4 changes: 2 additions & 2 deletions workflow/src/test/columns.test.tpl.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ calculateColumnsParamsTpl := assets.importTemplate(":calculate-pfconv-params")
self.defineOutputs("conf")

self.body(func(inputs) {
presetName := inputs.presetName
request := inputs.request

preset := render.create(getPresetTpl, {
preset: presetName
request: request
}).output("preset")

pfconvConfig := render.create(calculateColumnsParamsTpl, {
Expand Down
25 changes: 21 additions & 4 deletions workflow/src/test/columns.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { awaitStableState, tplTest, ML } from '@milaboratory/sdk-test';
import { ExpectStatic } from 'vitest';

type TestCase = {
type Request = {
presetName: string;
species?: string;
};

type TestCase = Request & {
check: (expect: ExpectStatic, config: any) => void;
};

Expand All @@ -14,20 +18,33 @@ const testCases: TestCase[] = [
expect(config.axes).to.have.lengthOf(1);
expect(config.columns.find((c: any) => c.column === 'readCount')).toBeDefined();
}
},
{
presetName: 'generic-single-cell-gex',
species: 'human',
check: (expect, config) => {
console.dir(config, { depth: 5 });
expect(config.axes).to.have.lengthOf(1);
expect(config.columns.find((c: any) => c.column === 'readCount')).toBeDefined();
}
}
];

tplTest.for(testCases)(
'checking preset for $presetName',
async ({ presetName, check }, { helper, expect }) => {
{ timeout: 10000 },
async ({ presetName, species, check }, { helper, expect }) => {
const resultC = (
await helper.renderTemplate(true, 'test.columns.test', ['conf'], (tx) => {
return {
presetName: tx.createValue(ML.Pl.JsonObject, JSON.stringify(presetName))
request: tx.createValue(
ML.Pl.JsonObject,
JSON.stringify({ presetName, species } satisfies Request)
)
};
})
).computeOutput('conf', (c) => c?.getDataAsJson());
const result = await awaitStableState(resultC);
const result = await awaitStableState(resultC, 10000);
check(expect, result);
}
);

0 comments on commit c4bf405

Please sign in to comment.