@datagrok/bio 2.23.2 → 2.24.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.
Files changed (48) hide show
  1. package/.eslintrc.json +0 -2
  2. package/CHANGELOG.md +5 -0
  3. package/README.md +1 -1
  4. package/dist/package-test.js +3 -3
  5. package/dist/package-test.js.map +1 -1
  6. package/dist/package.js +2 -2
  7. package/dist/package.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/package-types.ts +1 -1
  10. package/src/package.ts +9 -3
  11. package/src/tests/activity-cliffs-tests.ts +2 -2
  12. package/src/tests/monomer-libraries-tests.ts +8 -6
  13. package/src/tests/renderers-monomer-placer-tests.ts +1 -1
  14. package/src/tests/scoring.ts +1 -1
  15. package/src/tests/seq-handler-get-helm-tests.ts +1 -1
  16. package/src/tests/splitters-test.ts +2 -2
  17. package/src/tests/substructure-filters-tests.ts +11 -11
  18. package/src/tests/to-atomic-level-tests.ts +2 -2
  19. package/src/tests/to-atomic-level-ui-tests.ts +13 -14
  20. package/src/utils/cell-renderer.ts +1 -1
  21. package/src/utils/helm-to-molfile/converter/converter.ts +1 -1
  22. package/src/utils/helm-to-molfile/converter/mol-bonds.ts +1 -1
  23. package/src/utils/helm-to-molfile/converter/monomer-wrapper.ts +1 -1
  24. package/src/utils/helm-to-molfile/converter/polymer.ts +1 -1
  25. package/src/utils/helm-to-molfile/utils.ts +3 -2
  26. package/src/utils/monomer-cell-renderer-base.ts +1 -2
  27. package/src/utils/monomer-lib/consts.ts +1 -6
  28. package/src/utils/monomer-lib/lib-manager.ts +239 -112
  29. package/src/utils/monomer-lib/library-file-manager/monomers-lib-provider.ts +378 -0
  30. package/src/utils/monomer-lib/library-file-manager/ui.ts +119 -80
  31. package/src/utils/monomer-lib/monomer-colors.ts +37 -39
  32. package/src/utils/monomer-lib/monomer-lib-base.ts +3 -4
  33. package/src/utils/monomer-lib/monomer-lib.ts +7 -7
  34. package/src/utils/monomer-lib/monomer-manager/duplicate-monomer-manager.ts +3 -3
  35. package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +90 -81
  36. package/src/utils/monomer-lib/web-editor-monomer-of-library.ts +2 -1
  37. package/src/utils/seq-helper/seq-handler.ts +16 -3
  38. package/src/utils/seq-helper/seq-helper.ts +1 -2
  39. package/src/utils/sequence-to-mol.ts +1 -1
  40. package/src/viewers/web-logo-viewer.ts +1 -1
  41. package/src/widgets/composition-analysis-widget.ts +1 -1
  42. package/src/widgets/sequence-scrolling-widget.ts +1 -2
  43. package/test-console-output-1.log +672 -720
  44. package/test-record-1.mp4 +0 -0
  45. package/src/utils/monomer-lib/library-file-manager/custom-monomer-lib-handlers.ts +0 -41
  46. package/src/utils/monomer-lib/library-file-manager/event-manager.ts +0 -93
  47. package/src/utils/monomer-lib/library-file-manager/file-manager.ts +0 -317
  48. package/src/utils/monomer-lib/monomer-set.ts +0 -61
@@ -0,0 +1,378 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ import {JSONSchemaType} from 'ajv';
7
+ import {MonomerLibFileValidator} from './file-validator';
8
+ import {DEFAULT_FILES_LIB_PROVIDER_NAME, IMonomerLibHelper, IMonomerLibProvider, IMonomerLinkData,
9
+ IMonomerSet, Monomer, MonomerLibData, MonomerSet, MonomerSetPlaceholder}
10
+ from '@datagrok-libraries/bio/src/types/monomer-library';
11
+ import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
12
+ import {
13
+ HELM_REQUIRED_FIELD as REQ,
14
+ } from '@datagrok-libraries/bio/src/utils/const';
15
+ import {MonomerType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
16
+ import {Observable, Subject} from 'rxjs';
17
+
18
+ export class MonomerLibFromFilesProvider implements IMonomerLibProvider {
19
+ protected get LIB_PATH(): string { return 'System:AppData/Bio/monomer-libraries/'; }
20
+ protected get SETS_PATH(): string { return 'System:AppData/Bio/monomer-sets/'; }
21
+ public static readonly HELM_JSON_SCHEMA_PATH = 'System:AppData/Bio/tests/libraries/HELMmonomerSchema.json';
22
+ private static _instance: MonomerLibFromFilesProvider | null = null;
23
+ public filesPromise: Promise<void> = Promise.resolve();
24
+ name: string = DEFAULT_FILES_LIB_PROVIDER_NAME;
25
+ private _onChanged = new Subject<void>();
26
+ get onChanged(): Observable<void> {
27
+ return this._onChanged;
28
+ }
29
+ private constructor(
30
+ private readonly fileValidator: MonomerLibFileValidator,
31
+ private readonly libHelper: IMonomerLibHelper,
32
+ private readonly logger: ILogger,
33
+ ) {
34
+
35
+ }
36
+
37
+ public static async getInstance(
38
+ libHelper: IMonomerLibHelper, logger: ILogger,
39
+ ): Promise<MonomerLibFromFilesProvider> {
40
+ if (this._instance == null) {
41
+ const helmSchemaRaw = await grok.dapi.files.readAsText(MonomerLibFromFilesProvider.HELM_JSON_SCHEMA_PATH);
42
+ const helmSchema = JSON.parse(helmSchemaRaw) as JSONSchemaType<any>;
43
+
44
+ const fileValidator = new MonomerLibFileValidator(helmSchema);
45
+ MonomerLibFromFilesProvider._instance = new MonomerLibFromFilesProvider(fileValidator, libHelper, logger);
46
+ MonomerLibFromFilesProvider._instance.refreshLists();
47
+ }
48
+ return MonomerLibFromFilesProvider._instance!;
49
+ }
50
+
51
+ protected toLog(): string {
52
+ return `MonomerLibFileManager`;
53
+ }
54
+ private async validateAgainstHELM(fileContent: string, fileName: string): Promise<void> {
55
+ const isValid = this.isValidHELMLibrary(fileContent, fileName);
56
+ if (!isValid)
57
+ throw new Error(`File ${fileName} does not satisfy HELM standard`);
58
+ }
59
+
60
+ private isValidHELMLibrary(fileContent: string, fileName: string): boolean {
61
+ return this.fileValidator.validateFile(fileContent, fileName);
62
+ }
63
+
64
+ async refreshLists(): Promise<void> {
65
+ await Promise.all([
66
+ this.updateValidLibList(),
67
+ this.updateValidSetList(),
68
+ ]);
69
+ }
70
+
71
+
72
+ async addOrUpdateLibraryString(fileName: string, contentString: string): Promise<void> {
73
+ fileName = fileName.endsWith('.json') ? fileName : `${fileName}.json`;
74
+ try {
75
+ await this.validateAgainstHELM(contentString, fileName);
76
+ await grok.dapi.files.writeAsText(this.LIB_PATH + `${fileName}`, contentString);
77
+ await this.updateValidLibList();
78
+ const fileExists = await grok.dapi.files.exists(this.LIB_PATH + `${fileName}`);
79
+ if (!fileExists)
80
+ grok.shell.error(`Failed to add ${fileName} library`);
81
+ else
82
+ grok.shell.info(`Added ${fileName} HELM library`);
83
+ } catch (e) {
84
+ this.logger.error(e);
85
+ grok.shell.error(`Failed to add ${fileName} library`);
86
+ }
87
+ }
88
+
89
+ async addOrUpdateLibrary(libraryName: string, monomers: Monomer[]): Promise<void> {
90
+ const contentString = JSON.stringify([...(monomers.map((m) => ({...m, wem: undefined, lib: undefined})))], null, 2);
91
+ await this.addOrUpdateLibraryString(libraryName, contentString);
92
+ }
93
+
94
+ get loadPromise(): Promise<void> {
95
+ return this.filesPromise;
96
+ }
97
+
98
+ async listLibraries(): Promise<string[]> {
99
+ return this.validLibList;
100
+ }
101
+
102
+ async listSets(): Promise<string[]> {
103
+ return this.validSetList;
104
+ }
105
+
106
+ async loadLibraries(names: string[], isFullPath: boolean = false): Promise<MonomerLibData[]> {
107
+ let rawLibData: any[] = [];
108
+ names = names.map((n) => {
109
+ const normalized = n.endsWith('.json') ? n : `${n}.json`;
110
+ return normalized;
111
+ });
112
+ const paths = isFullPath ? names : names.map((n) => this.LIB_PATH + n);
113
+ const libs: MonomerLibData[] = [];
114
+
115
+ for (let i = 0; i < paths.length; i++) {
116
+ const formatedLib: MonomerLibData = {};
117
+ try {
118
+ const file = await grok.dapi.files.readAsText(paths[i]);
119
+ rawLibData = JSON.parse(file);
120
+ rawLibData.forEach((monomer) => {
121
+ const polymerType = monomer[REQ.POLYMER_TYPE];
122
+ const monomerSymbol = monomer[REQ.SYMBOL];
123
+ if (!formatedLib[polymerType])
124
+ formatedLib[polymerType] = {};
125
+ formatedLib[polymerType][monomerSymbol] = monomer as Monomer;
126
+ });
127
+ } catch (err) {
128
+ this.logger.error(`Failed to load monomer library file: ${paths[i]}`);
129
+ this.logger.error(err);
130
+ continue;
131
+ } finally {
132
+ libs.push(formatedLib);
133
+ }
134
+ }
135
+ return libs;
136
+ }
137
+
138
+ async loadSets(names: string[]): Promise<IMonomerSet[]> {
139
+ const sets: IMonomerSet[] = [];
140
+ const lib = this.libHelper.getMonomerLib();
141
+ for (const name of names) {
142
+ const correctedName = name.endsWith('.json') ? name : `${name}.json`;
143
+ const path = this.SETS_PATH + correctedName;
144
+ try {
145
+ const file = await grok.dapi.files.readAsText(path);
146
+ const raw = JSON.parse(file);
147
+ const description = raw['description'];
148
+ const placeholders = Object.entries(raw['placeholders']).map(([k, v]: [string, any]) => {
149
+ const placeholderSymbol = k;
150
+ const polymerType = v['polymerType'] as PolymerType;
151
+ const monomerType = v['monomerType'] as MonomerType;
152
+ const monomerLinks = v['set'] as IMonomerLinkData[];
153
+
154
+ return new MonomerSetPlaceholder(lib, placeholderSymbol, polymerType, monomerType, monomerLinks);
155
+ });
156
+
157
+ sets.push(new MonomerSet(description, placeholders));
158
+ } catch (err) {
159
+ this.logger.error(`Failed to load monomer set file: ${path}`);
160
+ this.logger.error(err);
161
+ continue;
162
+ }
163
+ }
164
+ return sets;
165
+ }
166
+
167
+ async deleteLibrary(name: string): Promise<void> {
168
+ const correctedName = name.endsWith('.json') ? name : `${name}.json`;
169
+ await grok.dapi.files.delete(this.LIB_PATH + correctedName);
170
+ await this.updateValidLibList();
171
+ }
172
+
173
+ async deleteSet(name: string): Promise<void> {
174
+ const correctedName = name.endsWith('.json') ? name : `${name}.json`;
175
+ await grok.dapi.files.delete(this.SETS_PATH + correctedName);
176
+ await this.updateValidSetList();
177
+ }
178
+
179
+ async addOrUpdateSetString(name: string, contentString: string): Promise<void> {
180
+ const correctedName = name.endsWith('.json') ? name : `${name}.json`;
181
+ await grok.dapi.files.writeAsText(this.SETS_PATH + correctedName, contentString);
182
+ await this.updateValidSetList();
183
+ }
184
+
185
+ async addOrUpdateSet(setName: string, monomerSet: IMonomerSet): Promise<void> {
186
+ const contentString = JSON.stringify(monomerSet, null, 2);
187
+ await this.addOrUpdateSetString(setName, contentString);
188
+ }
189
+
190
+ async deleteMonomersFromLibrary(libraryName: string, monomers: ({ polymerType: PolymerType; symbol: string; }[])) {
191
+ const correctedName = libraryName.endsWith('.json') ? libraryName : `${libraryName}.json`;
192
+ const path = this.LIB_PATH + correctedName;
193
+ const file = await grok.dapi.files.readAsText(path);
194
+ let rawLibData: Monomer[] = JSON.parse(file);
195
+ rawLibData = rawLibData.filter((m) => !monomers.some((toDelete) =>
196
+ m.polymerType === toDelete.polymerType && m.symbol === toDelete.symbol));
197
+ const contentString = JSON.stringify(rawLibData, null, 2);
198
+ await grok.dapi.files.writeAsText(path, contentString);
199
+ await this.updateValidLibList();
200
+ }
201
+
202
+
203
+ private validLibList: string[] = [];
204
+ private libListHasChanged(newList: string[]): boolean {
205
+ const currentList = this.validLibList;
206
+ return newList.length !== currentList.length || newList.some((el, i) => el !== currentList[i]);
207
+ }
208
+
209
+ private validSetList: string[] = [];
210
+ private setListHasChanged(newList: string[]): boolean {
211
+ const currentList = this.validSetList;
212
+ return newList.length !== currentList.length || newList.some((el, i) => el !== currentList[i]);
213
+ }
214
+
215
+ async getLibraryAsString(libName: string): Promise<string> {
216
+ const correctedName = libName.endsWith('.json') ? libName : `${libName}.json`;
217
+ return grok.dapi.files.readAsText(this.LIB_PATH + correctedName);
218
+ }
219
+
220
+ async getSingleLibrary(name: string): Promise<MonomerLibData | null> {
221
+ return (await this.loadLibraries([name]))?.[0];
222
+ }
223
+
224
+ async getSingleLibraryWithFullPath(path: string): Promise<MonomerLibData | null> {
225
+ return (await this.loadLibraries([path], true))?.[0];
226
+ }
227
+
228
+ async updateOrAddMonomersInLibrary(libraryName: string, monomers: Monomer[]): Promise<void> {
229
+ const correctedName = libraryName.endsWith('.json') ? libraryName : `${libraryName}.json`;
230
+ const path = this.LIB_PATH + correctedName;
231
+ const file = await grok.dapi.files.readAsText(path);
232
+ const rawLibData: Monomer[] = JSON.parse(file);
233
+ for (const monomer of monomers) {
234
+ const existingIdx = rawLibData.findIndex((m) =>
235
+ m.polymerType === monomer.polymerType && m.symbol === monomer.symbol);
236
+ if (existingIdx !== -1)
237
+ rawLibData[existingIdx] = {...monomer, lib: undefined, wem: undefined};
238
+ else
239
+ rawLibData.push({...monomer, lib: undefined, wem: undefined});
240
+ }
241
+ const contentString = JSON.stringify(rawLibData, null, 2);
242
+ await grok.dapi.files.writeAsText(path, contentString);
243
+ await this.updateValidLibList();
244
+ }
245
+
246
+ private async updateValidLibList(): Promise<void> {
247
+ const logPrefix: string = `${this.toLog()}.updateValidLibList()`;
248
+ this.logger.debug(`${logPrefix}, start`);
249
+ this.filesPromise = this.filesPromise.then(async () => {
250
+ this.logger.debug(`${logPrefix}, IN`);
251
+ const invalidFiles = [] as string[];
252
+ // console.log(`files before validation:`, this.libraryEventManager.getValidFilesPathList());
253
+ const libPathList = await this.getLibFileListAtLocation();
254
+
255
+ if (!this.libListHasChanged(libPathList)) {
256
+ this.logger.debug(`${logPrefix}, end, not changed`);
257
+ return;
258
+ }
259
+
260
+ for (const path of libPathList) {
261
+ if (!path.endsWith('.json')) {
262
+ invalidFiles.push(path);
263
+ continue;
264
+ }
265
+
266
+ const fileContent = await grok.dapi.files.readAsText(this.LIB_PATH + `${path}`);
267
+ if (!this.isValidHELMLibrary(fileContent, path))
268
+ invalidFiles.push(path);
269
+ }
270
+
271
+ const validLibPathList = libPathList.filter((path) => !invalidFiles.includes(path));
272
+
273
+ if (this.libListHasChanged(validLibPathList)) {
274
+ this.validLibList = validLibPathList;
275
+ await this.libHelper.loadMonomerLib(true);
276
+ }
277
+ // console.log(`files after validation:`, this.libraryEventManager.getValidFilesPathList());
278
+
279
+ if (validLibPathList.some((el) => !el.endsWith('.json')))
280
+ this.logger.warning(`Wrong validation: ${validLibPathList}`);
281
+
282
+ if (invalidFiles.length > 0) {
283
+ const message = `Invalid monomer library files in ${this.LIB_PATH}` +
284
+ `, consider fixing or removing them: ${invalidFiles.join(', ')}`;
285
+
286
+ this.logger.warning(message);
287
+ // grok.shell.warning(message);
288
+ }
289
+ this.logger.debug(`${logPrefix}, OUT`);
290
+ this._onChanged.next();
291
+ });
292
+ this.logger.debug(`${logPrefix}, end`);
293
+ return this.filesPromise;
294
+ }
295
+
296
+ private async updateValidSetList(): Promise<void> {
297
+ const logPrefix: string = `${this.toLog()}.updateValidSetList()`;
298
+ this.logger.debug(`${logPrefix}, start`);
299
+ this.filesPromise = this.filesPromise.then(async () => {
300
+ this.logger.debug(`${logPrefix}, IN`);
301
+ const invalidFiles = [] as string[];
302
+ const setPathList: string[] = await this.getSetFileListAtLocation();
303
+
304
+ if (!this.setListHasChanged(setPathList)) {
305
+ this.logger.debug(`${logPrefix}, end, not changed`);
306
+ return;
307
+ }
308
+
309
+ for (const path of setPathList) {
310
+ if (!path.endsWith('.json')) {
311
+ invalidFiles.push(path);
312
+ continue;
313
+ }
314
+
315
+ //const fileContent = await grok.dapi.files.readAsText(this.SETS_PATH + `${path}`);
316
+ // TODO: Validate monomer set
317
+ // if (!this.isValidMonomerSet(fileContent, path))
318
+ // invalidFiles.push(path);
319
+ }
320
+
321
+ const validSetPathList = setPathList.filter((path) => !invalidFiles.includes(path));
322
+ if (this.setListHasChanged(validSetPathList)) {
323
+ this.validSetList = validSetPathList;
324
+ this.libHelper.loadMonomerSets(true);
325
+ }
326
+
327
+ this.logger.debug(`${logPrefix}, OUT`);
328
+ this._onChanged.next();
329
+ });
330
+ this.logger.debug(`${logPrefix}, end`);
331
+ return this.filesPromise;
332
+ }
333
+
334
+ /** Get relative paths for files in LIB_PATH, SET_PATH */
335
+ private async getLibFileListAtLocation(): Promise<string[]> {
336
+ const logPrefix = `${this.toLog()}.getLibFileListAtLocation()`;
337
+ this.logger.debug(`${logPrefix}, start`);
338
+
339
+ const libPaths = await grok.dapi.files.list(this.LIB_PATH)
340
+ .then((l) => l.filter((f) => f.isFile).map((fi) => fi.fullPath));
341
+
342
+ const checkForUi = false;
343
+ const existingLibPaths: string[] = [];
344
+ if (checkForUi) {
345
+ // WARNING: an extra sanity check,
346
+ // caused by unexpected behavior of grok.dapi.files.list() when it returns non-existent paths
347
+ for (const path of libPaths) {
348
+ const exists = await grok.dapi.files.exists(path);
349
+ if (exists)
350
+ existingLibPaths.push(path);
351
+ }
352
+ } else
353
+ existingLibPaths.push(...libPaths);
354
+
355
+ return existingLibPaths.map((p) => /* relative to LIB_PATH */ p.substring(this.LIB_PATH.length));
356
+ }
357
+
358
+ private async getSetFileListAtLocation(): Promise<string[]> {
359
+ const logPrefix = `${this.toLog()}.getSetFileListAtLocation()`;
360
+ this.logger.debug(`${logPrefix}, start`);
361
+
362
+ const setPaths = await grok.dapi.files.list(this.SETS_PATH)
363
+ .then((l) => l.map((fi) => fi.fullPath));
364
+
365
+ const checkForUi = false;
366
+ const existingSetPaths: string[] = [];
367
+ if (checkForUi) {
368
+ for (const path of setPaths) {
369
+ const exists = await grok.dapi.files.exists(path);
370
+ if (exists)
371
+ existingSetPaths.push(path);
372
+ }
373
+ } else
374
+ existingSetPaths.push(...setPaths);
375
+
376
+ return existingSetPaths.map((p) => /* relative to SET_PATH */ p.substring(this.SETS_PATH.length));
377
+ }
378
+ }