@datagrok/hit-triage 1.9.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +199 -0
- package/css/hit-triage.css +0 -41
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/files/pepTriage/campaigns/DMT-1/campaign.json +1 -0
- package/files/pepTriage/campaigns/DMT-1/enriched_table.csv +2262 -0
- package/files/pepTriage/templates/Demo Template.json +1 -0
- package/package.json +6 -4
- package/src/app/accordeons/new-campaign-accordeon.ts +24 -6
- package/src/app/accordeons/new-pep-triage-template-accordeon.ts +208 -0
- package/src/app/accordeons/new-template-accordeon.ts +15 -9
- package/src/app/consts.ts +23 -10
- package/src/app/dialogs/functions-dialog.ts +14 -221
- package/src/app/hit-app-base.ts +3 -6
- package/src/app/hit-design-views/info-view.ts +4 -4
- package/src/app/hit-triage-app.ts +44 -17
- package/src/app/hit-triage-views/info-view.ts +261 -44
- package/src/app/pep-triage-app.ts +87 -0
- package/src/app/pep-triage-views/info-view.ts +123 -0
- package/src/app/types.ts +20 -65
- package/src/app/utils/calculate-single-cell.ts +20 -219
- package/src/app/utils.ts +100 -123
- package/src/package-api.ts +12 -0
- package/src/package.g.ts +24 -0
- package/src/package.ts +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"Demo Template","key":"DMT","sequenceColumnName":"Helm","campaignFields":[{"name":"Chemist","type":"String","required":true},{"name":"Supervisor","type":"String","required":true}],"dataSourceType":"Query","compute":{"descriptors":{"enabled":true,"args":["MolWt","HeavyAtomMolWt"]},"functions":[{"name":"addChemPropertiesColumns","package":"Chem","args":{"MW":false,"HBA":true,"HBD":true,"logP":false,"logS":false,"PSA":false,"rotatableBonds":false,"stereoCenters":false,"moleculeCharge":false}}],"scripts":[],"queries":[]},"submit":{"fName":"demoFileSubmit","package":"HitTriage"},"queryFunctionName":"Demo Peptide Sequences"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/hit-triage",
|
|
3
3
|
"friendlyName": "Hit Triage",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.10.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Davit Rizhinashvili",
|
|
7
7
|
"email": "drizhinashvili@datagrok.ai"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@datagrok-libraries/compute-utils": "^1.44.9",
|
|
20
|
+
"@datagrok-libraries/statistics": "^1.12.4",
|
|
20
21
|
"@datagrok-libraries/utils": "^4.6.5",
|
|
21
22
|
"cash-dom": "^8.1.5",
|
|
22
23
|
"css-loader": "^6.5.1",
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"typeahead-standalone": "4.14.1",
|
|
29
30
|
"typescript": "^5.6.3",
|
|
30
31
|
"uuid": "^9.0.0",
|
|
31
|
-
"@datagrok-libraries/test": "^1.1.0"
|
|
32
|
+
"@datagrok-libraries/test": "^1.1.0",
|
|
33
|
+
"@datagrok-libraries/bio": "^5.63.6"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
36
|
"@types/uuid": "^9.0.2",
|
|
@@ -52,8 +54,8 @@
|
|
|
52
54
|
"release-hittriage-dev": "grok publish dev --release",
|
|
53
55
|
"debug-hittriage-public": "grok publish public",
|
|
54
56
|
"release-hittriage-public": "grok publish public --release",
|
|
55
|
-
"link-all": "npm link datagrok-api @datagrok-libraries/compute-utils @datagrok-libraries/utils",
|
|
56
|
-
"build-all": "npm --prefix ./../../js-api run build && npm --prefix ./../../libraries/compute-utils run build && npm --prefix ./../../libraries/utils run build && npm run build"
|
|
57
|
+
"link-all": "npm link datagrok-api @datagrok-libraries/compute-utils @datagrok-libraries/statistics @datagrok-libraries/utils",
|
|
58
|
+
"build-all": "npm --prefix ./../../js-api run build && npm --prefix ./../../libraries/compute-utils run build && npm --prefix ./../../libraries/statistics run build && npm --prefix ./../../libraries/utils run build && npm run build"
|
|
57
59
|
},
|
|
58
60
|
"canEdit": [
|
|
59
61
|
"Administrators"
|
|
@@ -7,10 +7,17 @@ import {CampaignFieldTypes, HitTriageTemplate, IngestType} from '../types';
|
|
|
7
7
|
import * as C from '../consts';
|
|
8
8
|
import '../../../css/hit-triage.css';
|
|
9
9
|
|
|
10
|
-
type INewCampaignResult = {
|
|
10
|
+
export type INewCampaignResult = {
|
|
11
11
|
df: DG.DataFrame,
|
|
12
12
|
type: IngestType,
|
|
13
|
-
campaignProps: {[key: string]: any}
|
|
13
|
+
campaignProps: {[key: string]: any},
|
|
14
|
+
friendlyName?: string,
|
|
15
|
+
extraData?: {[key: string]: any},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type CampaignAccordeonOptions = {
|
|
19
|
+
extraFormElements?: HTMLElement[],
|
|
20
|
+
getExtraData?: () => {[key: string]: any},
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
type HitTriageCampaignAccordeon = {
|
|
@@ -24,7 +31,9 @@ type HitTriageCampaignAccordeon = {
|
|
|
24
31
|
* @return {HitTriageCampaignAccordeon} Object containing root element, promise for the campaign result and cancel
|
|
25
32
|
*/
|
|
26
33
|
export async function newCampaignAccordeon(template: HitTriageTemplate,
|
|
27
|
-
dataSourceFunctionsMap: {[key: string]: DG.Func | DG.DataQuery}
|
|
34
|
+
dataSourceFunctionsMap: {[key: string]: DG.Func | DG.DataQuery},
|
|
35
|
+
dataSourceTag: string = C.HitTriageDataSourceTag,
|
|
36
|
+
options?: CampaignAccordeonOptions): Promise<HitTriageCampaignAccordeon> {
|
|
28
37
|
const errorDiv = ui.divText('', {style: {color: 'red'}});
|
|
29
38
|
// handling file input
|
|
30
39
|
let fileDf: DG.DataFrame | null = null;
|
|
@@ -52,7 +61,7 @@ export async function newCampaignAccordeon(template: HitTriageTemplate,
|
|
|
52
61
|
if (Object.keys(dataSourceFunctionsMap).length === 0) {
|
|
53
62
|
// functions that have special tag and are applicable for data source. they should return a dataframe with molecules
|
|
54
63
|
const dataSourceFunctions =
|
|
55
|
-
Array.from(new Set(DG.Func.find({meta: {role:
|
|
64
|
+
Array.from(new Set(DG.Func.find({meta: {role: dataSourceTag}}).concat(DG.Func.find({tags: [dataSourceTag]}))));
|
|
56
65
|
// for display purposes we use friendly name of the function
|
|
57
66
|
dataSourceFunctions.forEach((func) => {
|
|
58
67
|
dataSourceFunctionsMap[func.friendlyName ?? func.name] = func;
|
|
@@ -82,6 +91,9 @@ export async function newCampaignAccordeon(template: HitTriageTemplate,
|
|
|
82
91
|
functionInputDiv.style.display = 'none';
|
|
83
92
|
const dataInputsDiv = ui.div([fileInputDiv, functionInputDiv]);
|
|
84
93
|
|
|
94
|
+
const campaignNameInput = ui.input.string('Campaign Name', {value: '', nullable: true});
|
|
95
|
+
campaignNameInput.setTooltip('Optional friendly name for the campaign');
|
|
96
|
+
|
|
85
97
|
// campaign properties. each template might have number of additional fields that should
|
|
86
98
|
// be filled by user for the campaign. they are cast into DG.Property objects and displayed as a form
|
|
87
99
|
const campaignProps = template.campaignFields
|
|
@@ -104,6 +116,8 @@ export async function newCampaignAccordeon(template: HitTriageTemplate,
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
const form = ui.div([
|
|
119
|
+
campaignNameInput.root,
|
|
120
|
+
...(options?.extraFormElements ?? []),
|
|
107
121
|
dataInputsDiv,
|
|
108
122
|
...(campaignProps.length ? [campaignPropsForm] : [])]);
|
|
109
123
|
const buttonsDiv = ui.buttonsInput([]); // div for create and cancel buttons
|
|
@@ -117,7 +131,9 @@ export async function newCampaignAccordeon(template: HitTriageTemplate,
|
|
|
117
131
|
}
|
|
118
132
|
const df = fileDf;
|
|
119
133
|
df.name = fileDf.name;
|
|
120
|
-
|
|
134
|
+
const name = campaignNameInput.value?.trim() || undefined;
|
|
135
|
+
const extraData = options?.getExtraData?.();
|
|
136
|
+
resolve({df, type: 'File', campaignProps: campaignPropsObject, friendlyName: name, extraData});
|
|
121
137
|
} else {
|
|
122
138
|
const func = dataSourceFunctionsMap[dataSourceFunctionInput.value!];
|
|
123
139
|
if (!func) {
|
|
@@ -129,7 +145,9 @@ export async function newCampaignAccordeon(template: HitTriageTemplate,
|
|
|
129
145
|
funcCallInputs[key] = value;
|
|
130
146
|
});
|
|
131
147
|
const df: DG.DataFrame = await func.apply(funcCallInputs);
|
|
132
|
-
|
|
148
|
+
const name = campaignNameInput.value?.trim() || undefined;
|
|
149
|
+
const extraData = options?.getExtraData?.();
|
|
150
|
+
resolve({df, type: 'Query', campaignProps: campaignPropsObject, friendlyName: name, extraData});
|
|
133
151
|
};
|
|
134
152
|
};
|
|
135
153
|
const startCampaignButton = ui.bigButton(C.i18n.StartCampaign, () => onOkProxy());
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
import * as ui from 'datagrok-api/ui';
|
|
4
|
+
import * as grok from 'datagrok-api/grok';
|
|
5
|
+
import {_package} from '../../package';
|
|
6
|
+
import {CampaignFieldTypes, HitTriageCampaignField, HitTriageCampaignFieldType,
|
|
7
|
+
IComputeDialogResult, PepTriageTemplate, IngestType, INewTemplateResult} from '../types';
|
|
8
|
+
import * as C from '../consts';
|
|
9
|
+
import '../../../css/hit-triage.css';
|
|
10
|
+
import {chemFunctionsDialog} from '../dialogs/functions-dialog';
|
|
11
|
+
import {ItemType, ItemsGrid} from '@datagrok-libraries/utils/src/items-grid';
|
|
12
|
+
import {HitAppBase} from '../hit-app-base';
|
|
13
|
+
import {getLayoutInput} from './layout-input';
|
|
14
|
+
import {getFuncPackageNameSafe, loadTemplate} from '../utils';
|
|
15
|
+
import {getCampaignFieldEditors, saveTemplate} from './new-template-accordeon';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export async function createPepTriageTemplateAccordeon(app: HitAppBase<any>,
|
|
19
|
+
dataSourceFunctionMap: { [key: string]: DG.Func | DG.DataQuery | DG.Script },
|
|
20
|
+
preset?: PepTriageTemplate,
|
|
21
|
+
): Promise<INewTemplateResult<PepTriageTemplate>> {
|
|
22
|
+
const templatesFolder = `${app.appName}/templates`;
|
|
23
|
+
const availableTemplates = (await _package.files.list(templatesFolder))
|
|
24
|
+
.filter((file) => file.name.endsWith('.json'))
|
|
25
|
+
.map((file) => file.name.slice(0, -5));
|
|
26
|
+
const availableTemplateKeys: string[] = [];
|
|
27
|
+
for (const tn of availableTemplates) {
|
|
28
|
+
const t: PepTriageTemplate = await loadTemplate<PepTriageTemplate>(`${templatesFolder}/${tn}.json`);
|
|
29
|
+
availableTemplateKeys.push(t.key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const availableSubmitFunctions = Array.from(new Set(DG.Func.find({meta: {role: C.HitTriageSubmitTag}}).concat(DG.Func.find({tags: [C.HitTriageSubmitTag]}))));
|
|
33
|
+
const submitFunctionsMap: {[key: string]: DG.Func} = {};
|
|
34
|
+
availableSubmitFunctions.forEach((func) => {
|
|
35
|
+
submitFunctionsMap[func.friendlyName ?? func.name] = func;
|
|
36
|
+
});
|
|
37
|
+
const presetSubmitKey = preset?.submit ? Object.keys(submitFunctionsMap)
|
|
38
|
+
.find((k) => submitFunctionsMap[k]?.name === preset.submit?.fName) : null;
|
|
39
|
+
const submitFunctionInput =
|
|
40
|
+
ui.input.choice('Submit function', {value: presetSubmitKey ?? null, items: [null, ...Object.keys(submitFunctionsMap)]});
|
|
41
|
+
if (!presetSubmitKey)
|
|
42
|
+
submitFunctionInput.value = null;
|
|
43
|
+
submitFunctionInput.nullable = true;
|
|
44
|
+
submitFunctionInput.fireChanged();
|
|
45
|
+
submitFunctionInput.setTooltip('Select function to be called upon submitting');
|
|
46
|
+
const layoutInput = getLayoutInput();
|
|
47
|
+
const errorDiv = ui.divText('Template name is empty or already exists', {classes: 'hit-triage-error-div'});
|
|
48
|
+
const keyErrorDiv = ui.divText('Template key is empty or already exists', {classes: 'hit-triage-error-div'});
|
|
49
|
+
|
|
50
|
+
const templateNameInput = ui.input.string('Name', {value: preset ? `${preset.name} (copy)` : '', onValueChanged: (value) => {
|
|
51
|
+
if (value === '' || availableTemplates.includes(value)) {
|
|
52
|
+
templateNameInput.root.style.borderBottom = '1px solid red';
|
|
53
|
+
errorDiv.style.opacity = '100%';
|
|
54
|
+
} else {
|
|
55
|
+
templateNameInput.root.style.borderBottom = 'none';
|
|
56
|
+
errorDiv.style.opacity = '0%';
|
|
57
|
+
}
|
|
58
|
+
}});
|
|
59
|
+
const templateKeyInput = ui.input.string('Key', {value: preset ? `${preset.key}-copy` : '', onValueChanged: (value) => {
|
|
60
|
+
if (value === '' || availableTemplateKeys.includes(value)) {
|
|
61
|
+
templateKeyInput.root.style.borderBottom = '1px solid red';
|
|
62
|
+
keyErrorDiv.style.opacity = '100%';
|
|
63
|
+
} else {
|
|
64
|
+
templateKeyInput.root.style.borderBottom = 'none';
|
|
65
|
+
keyErrorDiv.style.opacity = '0%';
|
|
66
|
+
}
|
|
67
|
+
}});
|
|
68
|
+
|
|
69
|
+
templateKeyInput.setTooltip('Template key used for campaign prefix');
|
|
70
|
+
templateNameInput.setTooltip('Template name');
|
|
71
|
+
templateNameInput.root.style.borderBottom = 'none';
|
|
72
|
+
templateKeyInput.root.style.borderBottom = 'none';
|
|
73
|
+
errorDiv.style.opacity = '0%';
|
|
74
|
+
keyErrorDiv.style.opacity = '0%';
|
|
75
|
+
|
|
76
|
+
// PepTriage-specific inputs
|
|
77
|
+
const seqColInput = ui.input.string('Sequence Column', {value: preset?.sequenceColumnName ?? '', nullable: false});
|
|
78
|
+
seqColInput.setTooltip('Name of the sequence column in the dataset (mandatory)');
|
|
79
|
+
const seqColErrorDiv = ui.divText('Sequence column name is required', {classes: 'hit-triage-error-div'});
|
|
80
|
+
seqColErrorDiv.style.opacity = '0%';
|
|
81
|
+
seqColInput.onChanged.subscribe(() => {
|
|
82
|
+
if (!seqColInput.value?.trim()) {
|
|
83
|
+
seqColInput.root.style.borderBottom = '1px solid red';
|
|
84
|
+
seqColErrorDiv.style.opacity = '100%';
|
|
85
|
+
} else {
|
|
86
|
+
seqColInput.root.style.borderBottom = 'none';
|
|
87
|
+
seqColErrorDiv.style.opacity = '0%';
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const molColInput = ui.input.string('Molecule Column', {value: preset?.moleculeColumnName ?? '', nullable: true});
|
|
92
|
+
molColInput.setTooltip('Name of the molecule column (optional). If empty, sequences will be auto-converted to molecules.');
|
|
93
|
+
|
|
94
|
+
let funcDialogRes: IComputeDialogResult | null = null;
|
|
95
|
+
const dummyTemplate = preset ? preset : {
|
|
96
|
+
compute: {
|
|
97
|
+
descriptors: {enabled: true, args: []},
|
|
98
|
+
functions: [],
|
|
99
|
+
},
|
|
100
|
+
} as unknown as PepTriageTemplate;
|
|
101
|
+
const funcInput = await chemFunctionsDialog(app, (res) => {funcDialogRes = res;}, () => null,
|
|
102
|
+
dummyTemplate, false);
|
|
103
|
+
funcInput.root.classList.add('hit-triage-new-template-functions-input');
|
|
104
|
+
|
|
105
|
+
if (Object.entries(dataSourceFunctionMap).length === 0) {
|
|
106
|
+
const dataSourceFunctions = Array.from(new Set(
|
|
107
|
+
DG.Func.find({meta: {role: C.PepTriageDataSourceTag}}).concat(DG.Func.find({tags: [C.PepTriageDataSourceTag]}))));
|
|
108
|
+
dataSourceFunctions.forEach((func) => {
|
|
109
|
+
dataSourceFunctionMap[func.friendlyName ?? func.name] = func;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const combinedSourceNames = Object.keys(dataSourceFunctionMap);
|
|
113
|
+
const dataSourceFunctionInput = ui.input.choice(
|
|
114
|
+
C.i18n.dataSourceFunction, {value: combinedSourceNames[0], items: combinedSourceNames});
|
|
115
|
+
const ingestTypeInput = ui.input.choice<IngestType>('Ingest using', {value: preset?.dataSourceType ?? 'Query', items: ['Query', 'File'],
|
|
116
|
+
onValueChanged: (value) => {
|
|
117
|
+
dataSourceFunctionInput.root.style.display = value === 'Query' ? 'block' : 'none';
|
|
118
|
+
}});
|
|
119
|
+
if (preset?.dataSourceType)
|
|
120
|
+
dataSourceFunctionInput.root.style.display = preset.dataSourceType === 'Query' ? 'block' : 'none';
|
|
121
|
+
|
|
122
|
+
const fieldsEditor = getCampaignFieldEditors(preset?.campaignFields);
|
|
123
|
+
|
|
124
|
+
const form = ui.div([
|
|
125
|
+
ui.h2('Details'),
|
|
126
|
+
ui.div([templateNameInput, errorDiv]),
|
|
127
|
+
ui.div([templateKeyInput, keyErrorDiv]),
|
|
128
|
+
ui.h2('Sequence Configuration'),
|
|
129
|
+
ui.div([seqColInput, seqColErrorDiv]),
|
|
130
|
+
molColInput.root,
|
|
131
|
+
ui.h2('Data Ingestion'),
|
|
132
|
+
ingestTypeInput.root,
|
|
133
|
+
dataSourceFunctionInput.root,
|
|
134
|
+
layoutInput.dataFileInput,
|
|
135
|
+
fieldsEditor.fieldsDiv,
|
|
136
|
+
ui.h2('Compute'),
|
|
137
|
+
funcInput.root,
|
|
138
|
+
ui.h2('Submit'),
|
|
139
|
+
submitFunctionInput.root,
|
|
140
|
+
], 'ui-form');
|
|
141
|
+
|
|
142
|
+
const buttonsDiv = ui.buttonsInput([]);
|
|
143
|
+
const buttonsContainerDiv = buttonsDiv.getElementsByClassName('ui-input-editor')?.[0] ?? buttonsDiv;
|
|
144
|
+
form.appendChild(buttonsDiv);
|
|
145
|
+
const cancelPromise = new Promise<void>((resolve) => {
|
|
146
|
+
const cancelButton = ui.button(C.i18n.cancel, () => resolve());
|
|
147
|
+
buttonsContainerDiv.appendChild(cancelButton);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const promise = new Promise<PepTriageTemplate>((resolve) => {
|
|
151
|
+
async function onOkProxy() {
|
|
152
|
+
funcInput.okProxy();
|
|
153
|
+
if (errorDiv.style.opacity === '100%') {
|
|
154
|
+
grok.shell.error('Template name is empty or already exists');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (keyErrorDiv.style.opacity === '100%') {
|
|
158
|
+
grok.shell.error('Template key is empty or already exists');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!seqColInput.value?.trim()) {
|
|
162
|
+
grok.shell.error('Sequence column name is required');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const submitFunction = submitFunctionInput.value ? submitFunctionsMap[submitFunctionInput.value] : undefined;
|
|
166
|
+
const out: PepTriageTemplate = {
|
|
167
|
+
name: templateNameInput.value,
|
|
168
|
+
key: templateKeyInput.value,
|
|
169
|
+
sequenceColumnName: seqColInput.value!.trim(),
|
|
170
|
+
moleculeColumnName: molColInput.value?.trim() || undefined,
|
|
171
|
+
campaignFields: fieldsEditor.getFields(),
|
|
172
|
+
dataSourceType: ingestTypeInput.value ?? 'Query',
|
|
173
|
+
layoutViewState: layoutInput.getLayoutViewState() ?? undefined,
|
|
174
|
+
compute: {
|
|
175
|
+
descriptors: {
|
|
176
|
+
enabled: !!funcDialogRes?.descriptors?.length,
|
|
177
|
+
args: funcDialogRes?.descriptors ?? [],
|
|
178
|
+
},
|
|
179
|
+
functions: Object.entries(funcDialogRes?.externals ?? {}).map(([funcName, args]) => {
|
|
180
|
+
const splitFunc = funcName.split(':');
|
|
181
|
+
return ({name: splitFunc[1], package: splitFunc[0], args: args});
|
|
182
|
+
}),
|
|
183
|
+
scripts: Object.entries(funcDialogRes?.scripts ?? {})
|
|
184
|
+
.filter(([name, _]) => name.startsWith(C.HTScriptPrefix) && name.split(':').length === 3)
|
|
185
|
+
.map(([scriptId, args]) => {
|
|
186
|
+
const scriptNameParts = scriptId.split(':');
|
|
187
|
+
return ({name: scriptNameParts[1] ?? '', id: scriptNameParts[2] ?? '', args: args});
|
|
188
|
+
}),
|
|
189
|
+
queries: Object.entries(funcDialogRes?.queries ?? {})
|
|
190
|
+
.filter(([name, _]) => name.startsWith(C.HTQueryPrefix) && name.split(':').length === 3)
|
|
191
|
+
.map(([queryName, args]) => {
|
|
192
|
+
const queryNameParts = queryName.split(':');
|
|
193
|
+
return ({name: queryNameParts[1] ?? '', id: queryNameParts[2] ?? '', args: args});
|
|
194
|
+
}),
|
|
195
|
+
},
|
|
196
|
+
...(submitFunction ? {submit: {fName: submitFunction.name, package: getFuncPackageNameSafe(submitFunction)}} : {}),
|
|
197
|
+
queryFunctionName: (ingestTypeInput.value === 'Query') ? dataSourceFunctionInput.value ?? undefined : undefined,
|
|
198
|
+
};
|
|
199
|
+
_package.files.writeAsText(`${templatesFolder}/${out.name}.json`, JSON.stringify(out));
|
|
200
|
+
grok.shell.info('Template created successfully');
|
|
201
|
+
resolve(out);
|
|
202
|
+
}
|
|
203
|
+
const createTemplateButton = ui.bigButton(C.i18n.createTemplate, () => onOkProxy());
|
|
204
|
+
buttonsContainerDiv.appendChild(createTemplateButton);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return {root: form, template: promise, cancelPromise};
|
|
208
|
+
}
|
|
@@ -11,16 +11,17 @@ import {chemFunctionsDialog} from '../dialogs/functions-dialog';
|
|
|
11
11
|
import {ItemType, ItemsGrid} from '@datagrok-libraries/utils/src/items-grid';
|
|
12
12
|
import {HitAppBase} from '../hit-app-base';
|
|
13
13
|
import {getLayoutInput} from './layout-input';
|
|
14
|
-
import {getFuncPackageNameSafe} from '../utils';
|
|
14
|
+
import {getFuncPackageNameSafe, loadTemplate} from '../utils';
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
export async function createTemplateAccordeon(app: HitAppBase<any>,
|
|
18
18
|
dataSourceFunctionMap: { [key: string]: DG.Func | DG.DataQuery | DG.Script },
|
|
19
|
+
preset?: HitTriageTemplate,
|
|
19
20
|
): Promise<INewTemplateResult<HitTriageTemplate>> {
|
|
20
21
|
const availableTemplates = (await _package.files.list('Hit Triage/templates')).map((file) => file.name.slice(0, -5));
|
|
21
22
|
const availableTemplateKeys: string[] = [];
|
|
22
23
|
for (const tn of availableTemplates) {
|
|
23
|
-
const t: HitTriageTemplate =
|
|
24
|
+
const t: HitTriageTemplate = await loadTemplate<HitTriageTemplate>(`Hit Triage/templates/${tn}.json`);
|
|
24
25
|
availableTemplateKeys.push(t.key);
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -29,9 +30,12 @@ export async function createTemplateAccordeon(app: HitAppBase<any>,
|
|
|
29
30
|
availableSubmitFunctions.forEach((func) => {
|
|
30
31
|
submitFunctionsMap[func.friendlyName ?? func.name] = func;
|
|
31
32
|
});
|
|
33
|
+
const presetSubmitKey = preset?.submit ? Object.keys(submitFunctionsMap)
|
|
34
|
+
.find((k) => submitFunctionsMap[k]?.name === preset.submit?.fName) : null;
|
|
32
35
|
const submitFunctionInput =
|
|
33
|
-
ui.input.choice('Submit function', {value: null, items: [null, ...Object.keys(submitFunctionsMap)]});
|
|
34
|
-
|
|
36
|
+
ui.input.choice('Submit function', {value: presetSubmitKey ?? null, items: [null, ...Object.keys(submitFunctionsMap)]});
|
|
37
|
+
if (!presetSubmitKey)
|
|
38
|
+
submitFunctionInput.value = null;
|
|
35
39
|
submitFunctionInput.nullable = true;
|
|
36
40
|
submitFunctionInput.fireChanged();
|
|
37
41
|
submitFunctionInput.setTooltip('Select function to be called upon submitting');
|
|
@@ -40,7 +44,7 @@ export async function createTemplateAccordeon(app: HitAppBase<any>,
|
|
|
40
44
|
|
|
41
45
|
const keyErrorDiv = ui.divText('Template key is empty or already exists', {classes: 'hit-triage-error-div'});
|
|
42
46
|
|
|
43
|
-
const templateNameInput = ui.input.string('Name', {value: '', onValueChanged: (value) => {
|
|
47
|
+
const templateNameInput = ui.input.string('Name', {value: preset ? `${preset.name} (copy)` : '', onValueChanged: (value) => {
|
|
44
48
|
if (value === '' || availableTemplates.includes(value)) {
|
|
45
49
|
templateNameInput.root.style.borderBottom = '1px solid red';
|
|
46
50
|
errorDiv.style.opacity = '100%';
|
|
@@ -49,7 +53,7 @@ export async function createTemplateAccordeon(app: HitAppBase<any>,
|
|
|
49
53
|
errorDiv.style.opacity = '0%';
|
|
50
54
|
}
|
|
51
55
|
}});
|
|
52
|
-
const templateKeyInput = ui.input.string('Key', {value: '', onValueChanged: (value) => {
|
|
56
|
+
const templateKeyInput = ui.input.string('Key', {value: preset ? `${preset.key}-copy` : '', onValueChanged: (value) => {
|
|
53
57
|
if (value === '' || availableTemplateKeys.includes(value)) {
|
|
54
58
|
templateKeyInput.root.style.borderBottom = '1px solid red';
|
|
55
59
|
keyErrorDiv.style.opacity = '100%';
|
|
@@ -68,7 +72,7 @@ export async function createTemplateAccordeon(app: HitAppBase<any>,
|
|
|
68
72
|
|
|
69
73
|
let funcDialogRes: IComputeDialogResult | null = null;
|
|
70
74
|
// used just for functions editor
|
|
71
|
-
const dummyTemplate = {
|
|
75
|
+
const dummyTemplate = preset ? preset : {
|
|
72
76
|
compute: {
|
|
73
77
|
descriptors: {
|
|
74
78
|
enabled: true,
|
|
@@ -90,12 +94,14 @@ export async function createTemplateAccordeon(app: HitAppBase<any>,
|
|
|
90
94
|
const combinedSourceNames = Object.keys(dataSourceFunctionMap);
|
|
91
95
|
const dataSourceFunctionInput = ui.input.choice(
|
|
92
96
|
C.i18n.dataSourceFunction, {value: combinedSourceNames[0], items: combinedSourceNames});
|
|
93
|
-
const ingestTypeInput = ui.input.choice<IngestType>('Ingest using', {value: 'Query', items: ['Query', 'File'],
|
|
97
|
+
const ingestTypeInput = ui.input.choice<IngestType>('Ingest using', {value: preset?.dataSourceType ?? 'Query', items: ['Query', 'File'],
|
|
94
98
|
onValueChanged: (value) => {
|
|
95
99
|
dataSourceFunctionInput.root.style.display = value === 'Query' ? 'block' : 'none';
|
|
96
100
|
}});
|
|
101
|
+
if (preset?.dataSourceType)
|
|
102
|
+
dataSourceFunctionInput.root.style.display = preset.dataSourceType === 'Query' ? 'block' : 'none';
|
|
97
103
|
|
|
98
|
-
const fieldsEditor = getCampaignFieldEditors();
|
|
104
|
+
const fieldsEditor = getCampaignFieldEditors(preset?.campaignFields);
|
|
99
105
|
|
|
100
106
|
const form = ui.div([
|
|
101
107
|
ui.h2('Details'),
|
package/src/app/consts.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {HitDesignCampaign} from './types';
|
|
1
|
+
import type {HitDesignCampaign, HitTriageCampaign} from './types';
|
|
2
|
+
|
|
3
|
+
// Re-export shared compute-function constants from the statistics library
|
|
4
|
+
export {funcTypeNames, HTScriptPrefix, HTQueryPrefix, ComputeQueryMolColName} from '@datagrok-libraries/statistics/src/compute-functions/consts';
|
|
2
5
|
|
|
3
6
|
export const CampaignIdKey = 'campaignId';
|
|
4
7
|
export const HitDesignCampaignIdKey = 'campaignId';
|
|
@@ -16,9 +19,6 @@ export const ViDColName = 'V-iD';
|
|
|
16
19
|
export const ViDSemType = 'HIT_DESIGN_VID';
|
|
17
20
|
export const HTcampaignName = 'HTcampaignName';
|
|
18
21
|
export const HDcampaignName = 'HDcampaignName';
|
|
19
|
-
export const HTScriptPrefix = 'HTScript';
|
|
20
|
-
export const HTQueryPrefix = 'HTQuery';
|
|
21
|
-
export const ComputeQueryMolColName = 'molecules';
|
|
22
22
|
export const i18n = {
|
|
23
23
|
startNewCampaign: 'New Campaign',
|
|
24
24
|
createNewCampaign: 'New Campaign',
|
|
@@ -35,12 +35,6 @@ export const i18n = {
|
|
|
35
35
|
noInformation: 'No Information',
|
|
36
36
|
} as const;
|
|
37
37
|
|
|
38
|
-
export const funcTypeNames = {
|
|
39
|
-
script: 'script',
|
|
40
|
-
function: 'function-package',
|
|
41
|
-
query: 'data-query',
|
|
42
|
-
} as const;
|
|
43
|
-
|
|
44
38
|
export const HDCampaignsGroupingLSKey = 'HDCampaignsGrouping';
|
|
45
39
|
export const HDCampaignTableColumnsLSKey = 'HDCampaignTableColumns';
|
|
46
40
|
export const HDCampaignsTableSortingLSKey = 'HDCampaignsTableSorting';
|
|
@@ -67,4 +61,23 @@ export const DefaultCampaignTableInfoGetters = {
|
|
|
67
61
|
|
|
68
62
|
export type CampaignTableColumns = keyof typeof DefaultCampaignTableInfoGetters | `campaignFields.${string}`;
|
|
69
63
|
|
|
64
|
+
export const HTCampaignsGroupingLSKey = 'HTCampaignsGrouping';
|
|
65
|
+
export const HTCampaignTableColumnsLSKey = 'HTCampaignTableColumns';
|
|
66
|
+
export const HTCampaignsTableSortingLSKey = 'HTCampaignsTableSorting';
|
|
67
|
+
|
|
68
|
+
export const HTDefaultCampaignTableInfoGetters = {
|
|
69
|
+
'Name': (info: HitTriageCampaign) => info.friendlyName ?? info.name,
|
|
70
|
+
'Code': (info: HitTriageCampaign) => info.name,
|
|
71
|
+
'Created': (info: HitTriageCampaign) => info.createDate,
|
|
72
|
+
'Author': (info: HitTriageCampaign) => info.authorUserFriendlyName ?? '',
|
|
73
|
+
'Last Modified by': (info: HitTriageCampaign) => info.lastModifiedUserName ?? '',
|
|
74
|
+
'Total': (info: HitTriageCampaign) => (info.rowCount ?? 0).toString(),
|
|
75
|
+
'Selected': (info: HitTriageCampaign) => (info.filteredRowCount ?? 0).toString(),
|
|
76
|
+
'Status': (info: HitTriageCampaign) => info.status,
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export type HTCampaignTableColumns = keyof typeof HTDefaultCampaignTableInfoGetters | `campaignFields.${string}`;
|
|
80
|
+
|
|
81
|
+
export const PepTriageDataSourceTag = 'pepTriageDataSource';
|
|
82
|
+
|
|
70
83
|
export const HTFunctionOrderingLSKey = 'HTFunctionOrderingLS';
|