diff --git a/scanomatic/ui_server_data/js/src/qc/actions.js b/scanomatic/ui_server_data/js/src/qc/actions.js index 92da2236..aa07922d 100644 --- a/scanomatic/ui_server_data/js/src/qc/actions.js +++ b/scanomatic/ui_server_data/js/src/qc/actions.js @@ -5,52 +5,45 @@ import { } from './selectors'; import type { State, TimeSeries, PlateOfTimeSeries, QualityIndexQueue, - PlateValueArray, Phenotype, QCMarkType, QCMarksMap, + PlateValueArray, Phenotype, Mark, QCMarksMap, } from './state'; import { getPlateGrowthData, getPhenotypeData, setCurveQCMark, setCurveQCMarkAll } from '../api'; export type Action - = {| type: 'PLATE_SET', plate: number |} - | {| type: 'PROJECT_SET', project: string |} - | {| type: 'CURVE_FOCUS', plate: number, row: number, col: number |} - | {| type: 'CURVE_QCMARK_SET', plate: number, row: number, col: number, mark: QCMarkType, phenotype: ?Phenotype |} + = {| +type: 'PLATE_SET', plate: number |} + | {| +type: 'PROJECT_SET', project: string |} + | {| +type: 'CURVE_FOCUS', plate: number, row: number, col: number |} + | {| +type: 'CURVE_QCMARK_SET', plate: number, row: number, col: number, mark: Mark, phenotype: ?Phenotype |} | {| - type: 'PLATE_GROWTHDATA_SET', + +type: 'PLATE_GROWTHDATA_SET', plate: number, times: TimeSeries, smooth: PlateOfTimeSeries, raw: PlateOfTimeSeries, |} - | {| type: 'QUALITYINDEX_QUEUE_SET', queue: QualityIndexQueue |} - | {| type: 'QUALITYINDEX_SET', index: number |} - | {| type: 'QUALITYINDEX_NEXT' |} - | {| type: 'QUALITYINDEX_PREVIOUS' |} - | {| type: 'PHENOTYPE_SET', phenotype: Phenotype |} + | {| +type: 'QUALITYINDEX_QUEUE_SET', queue: QualityIndexQueue |} + | {| +type: 'QUALITYINDEX_SET', index: number |} + | {| +type: 'QUALITYINDEX_NEXT' |} + | {| +type: 'QUALITYINDEX_PREVIOUS' |} + | {| +type: 'PHENOTYPE_SET', phenotype: Phenotype |} | {| - type: 'PLATE_PHENOTYPEDATA_SET', + +type: 'PLATE_PHENOTYPEDATA_SET', plate: number, phenotype: Phenotype, phenotypes: PlateValueArray, qcmarks: QCMarksMap, |} | {| - type: 'CURVE_QCMARK_SET', - plate: number, - row: number, - col: number, - phenotype: ?Phenotype, - mark: QCMarkType, - |} - | {| - type: 'CURVE_QCMARK_SETDIRTY', - plate: number, - row: number, - col: number, - phenotype: ?Phenotype, - mark: QCMarkType, + +type: 'CURVE_QCMARK_SET', + +plate: number, + +row: number, + +col: number, + +phenotype: ?Phenotype, + +mark: Mark, + +dirty: bool, |} | {| - type: 'CURVE_QCMARK_REMOVEDIRTY', + +type: 'CURVE_QCMARK_REMOVEDIRTY', plate: number, row: number, col: number, @@ -134,7 +127,7 @@ export function setStoreCurveQCMark( plate: number, row: number, col: number, - mark: QCMarkType, + mark: Mark, phenotype: ?Phenotype, ) : Action { return { @@ -144,6 +137,7 @@ export function setStoreCurveQCMark( plate, col, row, + dirty: false, }; } @@ -151,16 +145,17 @@ export function setStoreCurveQCMarkDirty( plate: number, row: number, col: number, - mark: QCMarkType, + mark: Mark, phenotype: ?Phenotype, ) : Action { return { - type: 'CURVE_QCMARK_SETDIRTY', + type: 'CURVE_QCMARK_SET', phenotype, mark, plate, col, row, + dirty: true, }; } @@ -181,7 +176,7 @@ export function setQCMarkNotDirty( export type ThunkAction = (dispatch: Action => any, getState: () => State) => any; export function updateFocusCurveQCMark( - mark: QCMarkType, + mark: Mark, phenotype: ?Phenotype, key: string, ) : ThunkAction { @@ -219,14 +214,16 @@ export function updateFocusCurveQCMark( } else { previousMark = getFocusCurveQCMarkAllPhenotypes(state); } - previousMark - .forEach((prevMark, pheno) => dispatch(setStoreCurveQCMark( - plate, - focus.row, - focus.col, - prevMark || 'OK', - pheno, - ))); + if (previousMark) { + previousMark + .forEach((prevMark, pheno) => dispatch(setStoreCurveQCMark( + plate, + focus.row, + focus.col, + prevMark || 'OK', + pheno, + ))); + } return Promise.resolve(); }); }; diff --git a/scanomatic/ui_server_data/js/src/qc/actions.spec.js b/scanomatic/ui_server_data/js/src/qc/actions.spec.js index 66d05f5e..7bc465ed 100644 --- a/scanomatic/ui_server_data/js/src/qc/actions.spec.js +++ b/scanomatic/ui_server_data/js/src/qc/actions.spec.js @@ -38,10 +38,10 @@ describe('/qc/actions', () => { 'GenerationTimeWhen', [[0]], new Map([ - ['badData', [[0], [0]]], - ['empty', [[1], [1]]], - ['noGrowth', [[2], [2]]], - ['undecidedProblem', [[3], [3]]], + ['BadData', [[0], [0]]], + ['Empty', [[1], [1]]], + ['NoGrowth', [[2], [2]]], + ['UndecidedProblem', [[3], [3]]], ]), )) .toEqual({ @@ -50,10 +50,10 @@ describe('/qc/actions', () => { phenotype: 'GenerationTimeWhen', phenotypes: [[0]], qcmarks: new Map([ - ['badData', [[0], [0]]], - ['empty', [[1], [1]]], - ['noGrowth', [[2], [2]]], - ['undecidedProblem', [[3], [3]]], + ['BadData', [[0], [0]]], + ['Empty', [[1], [1]]], + ['NoGrowth', [[2], [2]]], + ['UndecidedProblem', [[3], [3]]], ]), }); }); @@ -187,8 +187,8 @@ describe('/qc/actions', () => { 'GenerationTime', [[10, 154]], new Map([ - ['badData', [[0], [1]]], - ['empty', null], + ['BadData', [[0], [1]]], + ['Empty', null], ]), ) .build(); @@ -214,8 +214,8 @@ describe('/qc/actions', () => { 'GenerationTime', [[10, 154]], new Map([ - ['badData', [[0], [1]]], - ['empty', null], + ['BadData', [[0], [1]]], + ['Empty', null], ]), ) .build(); @@ -293,28 +293,28 @@ describe('/qc/actions', () => { const gtData = { phenotypes: [[0]], qcmarks: new Map([ - ['badData', [[], []]], - ['empty', [[0], [1]]], - ['noGrowth', [[1], [0]]], - ['undecidedProblem', [[1, 1], [0, 1]]], + ['BadData', [[], []]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1], [0]]], + ['UndecidedProblem', [[1, 1], [0, 1]]], ]), }; const gtWhenData = { phenotypes: [[5]], qcmarks: new Map([ - ['badData', [[1], [1]]], - ['empty', [[2], [1]]], - ['noGrowth', [[1], [2]]], - ['undecidedProblem', [[2, 1], [0, 1]]], + ['BadData', [[1], [1]]], + ['Empty', [[2], [1]]], + ['NoGrowth', [[1], [2]]], + ['UndecidedProblem', [[2, 1], [0, 1]]], ]), }; const expYeildData = { phenotypes: [[4]], qcmarks: new Map([ - ['badData', [[1], [4]]], - ['empty', [[2], [4]]], - ['noGrowth', [[1], [4]]], - ['undecidedProblem', [[2, 1], [0, 4]]], + ['BadData', [[1], [4]]], + ['Empty', [[2], [4]]], + ['NoGrowth', [[1], [4]]], + ['UndecidedProblem', [[2, 1], [0, 4]]], ]), }; let getPhenotypeData; @@ -415,10 +415,10 @@ describe('/qc/actions', () => { const gtData = { phenotypes: [[0]], qcmarks: new Map([ - ['badData', [[], []]], - ['empty', [[0], [1]]], - ['noGrowth', [[1], [0]]], - ['undecidedProblem', [[1, 1], [0, 1]]], + ['BadData', [[], []]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1], [0]]], + ['UndecidedProblem', [[1, 1], [0, 1]]], ]), }; let getPhenotypeData; diff --git a/scanomatic/ui_server_data/js/src/qc/reducers/plate.js b/scanomatic/ui_server_data/js/src/qc/reducers/plate.js index 4478adfc..da85546c 100644 --- a/scanomatic/ui_server_data/js/src/qc/reducers/plate.js +++ b/scanomatic/ui_server_data/js/src/qc/reducers/plate.js @@ -1,6 +1,6 @@ // @flow import type { Action } from '../actions'; -import type { Plate as State, QCMarkType, QCMarksMap, PlateCoordinatesArray } from '../state'; +import type { Plate as State, Mark, QCMarksMap, PlateCoordinatesArray } from '../state'; const initialState : State = { number: 0, qIndex: 0 }; @@ -22,8 +22,8 @@ function addMark(previous: ?PlateCoordinatesArray, row: number, col: number) } function removeMark(previous: ?PlateCoordinatesArray, row: number, col: number) -: ?PlateCoordinatesArray { - if (!previous) return null; +: PlateCoordinatesArray { + if (!previous) return [[], []]; const next = [[], []]; for (let i = 0; i < previous[0].length; i += 1) { if (previous[0][i] !== row && previous[1][i] !== col) { @@ -34,13 +34,14 @@ function removeMark(previous: ?PlateCoordinatesArray, row: number, col: number) return next; } -function updateQCMarks(marks: QCMarksMap, row: number, col: number, mark: QCMarkType) : QCMarksMap { - return new Map([ - ['noGrowth', mark === 'NoGrowth' ? addMark(marks.get('noGrowth'), row, col) : removeMark(marks.get('noGrowth'), row, col)], - ['empty', mark === 'Empty' ? addMark(marks.get('empty'), row, col) : removeMark(marks.get('empty'), row, col)], - ['badData', mark === 'BadData' ? addMark(marks.get('badData'), row, col) : removeMark(marks.get('badData'), row, col)], - ['undecidedProblem', mark === 'UndecidedProblem' ? addMark(marks.get('undecidedProblem'), row, col) : removeMark(marks.get('undecidedProblem'), row, col)], - ]); +function updateQCMarks(marks: QCMarksMap, row: number, col: number, mark: Mark) : QCMarksMap { + return new Map(['NoGrowth', 'Empty', 'BadData', 'UndecidedProblem'] + .map(markType => [ + markType, + mark === markType ? + addMark(marks.get(markType), row, col) : + removeMark(marks.get(markType), row, col), + ])); } export default function plate(state: State = initialState, action: Action) { @@ -72,7 +73,6 @@ export default function plate(state: State = initialState, action: Action) { }, ); } - case 'CURVE_QCMARK_SETDIRTY': case 'CURVE_QCMARK_SET': { if (action.plate !== state.number) return state; const nextQC = new Map(state.qcmarks); @@ -97,7 +97,7 @@ export default function plate(state: State = initialState, action: Action) { } return Object.assign({}, state, { qcmarks: nextQC, - dirty: action.type === 'CURVE_QCMARK_SETDIRTY' ? + dirty: action.dirty ? (state.dirty || []).concat([[action.row, action.col]]) : (state.dirty || []) .filter(([row, col]) => action.row !== row || action.col !== col), diff --git a/scanomatic/ui_server_data/js/src/qc/reducers/plate.spec.js b/scanomatic/ui_server_data/js/src/qc/reducers/plate.spec.js index b353a870..7c999292 100644 --- a/scanomatic/ui_server_data/js/src/qc/reducers/plate.spec.js +++ b/scanomatic/ui_server_data/js/src/qc/reducers/plate.spec.js @@ -67,10 +67,10 @@ describe('/qc/reducers/plate', () => { const phenotype = 'GenerationTime'; const phenotypes = [[1, 1], [2, 1]]; const qcmarks = new Map([ - ['badData', [[], []]], - ['empty', [[0], [1]]], - ['noGrowth', [[1, 1], [0, 1]]], - ['undecidedProblem', [[0], [0]]], + ['BadData', [[], []]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1, 1], [0, 1]]], + ['UndecidedProblem', [[0], [0]]], ]); it('sets the plate phenotype data', () => { @@ -251,10 +251,10 @@ describe('/qc/reducers/plate', () => { .toEqual(Object.assign({}, state, { qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[0], [0]]], - ['noGrowth', null], - ['empty', null], - ['undecidedProblem', null], + ['BadData', [[0], [0]]], + ['NoGrowth', null], + ['Empty', null], + ['UndecidedProblem', null], ])], ]), dirty: [], @@ -267,11 +267,11 @@ describe('/qc/reducers/plate', () => { qIndex: 0, qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[0], [1]]], - ['empty', [[1], [1]]], + ['BadData', [[0], [1]]], + ['Empty', [[1], [1]]], ])], ['GenerationTimeWhen', new Map([ - ['badData', [[], []]], + ['BadData', [[], []]], ])], ]), }; @@ -280,13 +280,13 @@ describe('/qc/reducers/plate', () => { qIndex: 0, qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[0, 0], [1, 0]]], - ['empty', [[1], [1]]], - ['noGrowth', null], - ['undecidedProblem', null], + ['BadData', [[0, 0], [1, 0]]], + ['Empty', [[1], [1]]], + ['NoGrowth', null], + ['UndecidedProblem', null], ])], ['GenerationTimeWhen', new Map([ - ['badData', [[], []]], + ['BadData', [[], []]], ])], ]), dirty: [], @@ -299,7 +299,7 @@ describe('/qc/reducers/plate', () => { qIndex: 0, qcmarks: new Map([ ['GenerationTime', new Map([ - ['empty', [[0], [0]]], + ['Empty', [[0], [0]]], ])], ]), }; @@ -308,10 +308,10 @@ describe('/qc/reducers/plate', () => { qIndex: 0, qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[0], [0]]], - ['empty', [[], []]], - ['noGrowth', null], - ['undecidedProblem', null], + ['BadData', [[0], [0]]], + ['Empty', [[], []]], + ['NoGrowth', null], + ['UndecidedProblem', null], ])], ]), dirty: [], @@ -328,10 +328,10 @@ describe('/qc/reducers/plate', () => { .toEqual(Object.assign({}, state, { qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[0], [0]]], - ['noGrowth', null], - ['empty', null], - ['undecidedProblem', null], + ['BadData', [[0], [0]]], + ['NoGrowth', null], + ['Empty', null], + ['UndecidedProblem', null], ])], ]), dirty: [[1, 1]], @@ -359,8 +359,8 @@ describe('/qc/reducers/plate', () => { qIndex: 0, qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[1], [1]]], - ['empty', [[0], [0]]], + ['BadData', [[1], [1]]], + ['Empty', [[0], [0]]], ])], ['GenerationTimeWhen', new Map()], ]), @@ -378,16 +378,16 @@ describe('/qc/reducers/plate', () => { qIndex: 0, qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[1, 0], [1, 0]]], - ['empty', [[], []]], - ['noGrowth', null], - ['undecidedProblem', null], + ['BadData', [[1, 0], [1, 0]]], + ['Empty', [[], []]], + ['NoGrowth', null], + ['UndecidedProblem', null], ])], ['GenerationTimeWhen', new Map([ - ['badData', [[0], [0]]], - ['empty', null], - ['noGrowth', null], - ['undecidedProblem', null], + ['BadData', [[0], [0]]], + ['Empty', null], + ['NoGrowth', null], + ['UndecidedProblem', null], ])], ]), dirty: [], @@ -415,10 +415,10 @@ describe('/qc/reducers/plate', () => { .toEqual(Object.assign({}, state, { qcmarks: new Map([ ['GenerationTime', new Map([ - ['badData', [[5], [0]]], - ['noGrowth', null], - ['empty', null], - ['undecidedProblem', null], + ['BadData', [[5], [0]]], + ['NoGrowth', null], + ['Empty', null], + ['UndecidedProblem', null], ])], ]), dirty: [[5, 0]], diff --git a/scanomatic/ui_server_data/js/src/qc/selectors.js b/scanomatic/ui_server_data/js/src/qc/selectors.js index 8431430d..b2679467 100644 --- a/scanomatic/ui_server_data/js/src/qc/selectors.js +++ b/scanomatic/ui_server_data/js/src/qc/selectors.js @@ -7,7 +7,7 @@ import type { PlateValueArray as _PlateValueArray, PlateCoordinatesArray as _PlateCoordinatesArray, Phenotype, - QCMarkType, + Mark, QCMarksMap, } from './state'; @@ -87,30 +87,31 @@ function isMarked(data: ?PlateCoordinatesArray, row: number, col: number) : bool return false; } -function parseFocusCurveQCMark(state: State, phenotype: Phenotype): ?QCMarkType { +function parseFocusCurveQCMark(state: State, phenotype: Phenotype): ?Mark { const focus = getFocus(state); if (!focus) return null; const { row, col } = focus; if (!state.plate || !state.plate.qcmarks) return null; const marks = state.plate.qcmarks.get(phenotype); - if (isMarked(marks.get('badData'), row, col)) return 'BadData'; - if (isMarked(marks.get('noGrowth'), row, col)) return 'NoGrowth'; - if (isMarked(marks.get('empty'), row, col)) return 'Empty'; - if (isMarked(marks.get('undecidedProblem'), row, col)) return 'UndecidedProblem'; + if (!marks) return 'OK'; + if (isMarked(marks.get('BadData'), row, col)) return 'BadData'; + if (isMarked(marks.get('NoGrowth'), row, col)) return 'NoGrowth'; + if (isMarked(marks.get('Empty'), row, col)) return 'Empty'; + if (isMarked(marks.get('UndecidedProblem'), row, col)) return 'UndecidedProblem'; return 'OK'; } -export function getFocusCurveQCMark(state: State): ?QCMarkType { +export function getFocusCurveQCMark(state: State): ?Mark { const phenotype = getPhenotype(state); if (!phenotype) return null; return parseFocusCurveQCMark(state, phenotype); } -export function getFocusCurveQCMarkAllPhenotypes(state: State): ?Map { +export function getFocusCurveQCMarkAllPhenotypes(state: State): ?Map { if (!state.plate || !state.plate.qcmarks) return null; const marks = new Map(); (state.plate.qcmarks || new Map()) - .forEach((_, phenotype) => marks.set(phenotype, parseFocusCurveQCMark(state, phenotype))); + .forEach((_, phenotype) => marks.set(phenotype, parseFocusCurveQCMark(state, phenotype) || 'OK')); return marks; } diff --git a/scanomatic/ui_server_data/js/src/qc/selectors.spec.js b/scanomatic/ui_server_data/js/src/qc/selectors.spec.js index 67814b63..51d0b3e2 100644 --- a/scanomatic/ui_server_data/js/src/qc/selectors.spec.js +++ b/scanomatic/ui_server_data/js/src/qc/selectors.spec.js @@ -39,10 +39,10 @@ describe('/qc/selectors', () => { 'test', [[5, 4, 3], [5, 5, 1]], new Map([ - ['badData', [[0], [0]]], - ['empty', [[0], [1]]], - ['noGrowth', [[1, 1], [0, 1]]], - ['undecidedProblem', [[1, 0], [2, 2]]], + ['BadData', [[0], [0]]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1, 1], [0, 1]]], + ['UndecidedProblem', [[1, 0], [2, 2]]], ]), ) .build(); @@ -61,19 +61,19 @@ describe('/qc/selectors', () => { 'test', [[5, 4, 3], [5, 5, 1]], new Map([ - ['badData', [[0], [0]]], - ['empty', [[0], [1]]], - ['noGrowth', [[1, 1], [0, 1]]], - ['undecidedProblem', [[1, 0], [2, 2]]], + ['BadData', [[0], [0]]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1, 1], [0, 1]]], + ['UndecidedProblem', [[1, 0], [2, 2]]], ]), ) .build(); expect(selectors.getCurrentPhenotypeQCMarks(state)) .toEqual(new Map([ - ['badData', [[0], [0]]], - ['empty', [[0], [1]]], - ['noGrowth', [[1, 1], [0, 1]]], - ['undecidedProblem', [[1, 0], [2, 2]]], + ['BadData', [[0], [0]]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1, 1], [0, 1]]], + ['UndecidedProblem', [[1, 0], [2, 2]]], ])); }); @@ -260,35 +260,35 @@ describe('/qc/selectors', () => { 'GenerationTime', [[]], new Map([ - ['badData', [[0], [0]]], - ['empty', [[0], [1]]], - ['noGrowth', [[1], [0]]], - ['undecidedProblem', [[1], [1]]], + ['BadData', [[0], [0]]], + ['Empty', [[0], [1]]], + ['NoGrowth', [[1], [0]]], + ['UndecidedProblem', [[1], [1]]], ]), ); - it('returns BadData if badData', () => { + it('returns BadData if BadData', () => { const state = builder .setQualityIndex(3) .build(); expect(selectors.getFocusCurveQCMark(state)).toBe('BadData'); }); - it('returns NoGrowth if noGrowth', () => { + it('returns NoGrowth if NoGrowth', () => { const state = builder .setQualityIndex(1) .build(); expect(selectors.getFocusCurveQCMark(state)).toBe('NoGrowth'); }); - it('returns Empty if empty', () => { + it('returns Empty if Empty', () => { const state = builder .setQualityIndex(2) .build(); expect(selectors.getFocusCurveQCMark(state)).toBe('Empty'); }); - it('returns UndecidedProblem if undecidedProblem', () => { + it('returns UndecidedProblem if UndecidedProblem', () => { const state = builder .setQualityIndex(0) .build(); @@ -319,17 +319,17 @@ describe('/qc/selectors', () => { .setPlatePhenotypeData( 'GenerationTime', [[]], - new Map([['badData', [[0], [0]]]]), + new Map([['BadData', [[0], [0]]]]), ) .setPlatePhenotypeData( 'GenerationTimeWhen', [[]], - new Map([['noGrowth', [[0], [0]]]]), + new Map([['NoGrowth', [[0], [0]]]]), ) .setPlatePhenotypeData( 'ExperimentGrowthYield', [[]], - new Map([['empty', [[1], [1]]]]), + new Map([['Empty', [[1], [1]]]]), ) .setQualityIndex(0) .build(); diff --git a/scanomatic/ui_server_data/js/src/qc/state.js b/scanomatic/ui_server_data/js/src/qc/state.js index 2017d6ca..7366a492 100644 --- a/scanomatic/ui_server_data/js/src/qc/state.js +++ b/scanomatic/ui_server_data/js/src/qc/state.js @@ -28,7 +28,6 @@ export type Phenotype = "GenerationTime" | "GenerationTimeWhen" | "InitialValue"; -export type QCMarkType = 'OK' | 'NoGrowth' | 'BadData' | 'Empty' | 'UndecidedProblem'; export type Settings = { +project?: string, @@ -37,7 +36,7 @@ export type Settings = { export type PhenotypeDataMap = Map; -export type Mark = 'badData' | 'empty' | 'noGrowth' | 'undecidedProblem'; +export type Mark = 'OK' | 'NoGrowth' | 'BadData' | 'Empty' | 'UndecidedProblem'; export type QCMarksMap = Map; export type PhenotypeQCMarksMap = Map;