@datagrok/helm 1.0.1 → 1.0.4

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/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ src/**/*.d.ts*
2
+ src/seq_align.ts
package/.eslintrc.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true
5
+ },
6
+ "extends": [
7
+ "google"
8
+ ],
9
+ "parser": "@typescript-eslint/parser",
10
+ "parserOptions": {
11
+ "ecmaVersion": 12,
12
+ "sourceType": "module"
13
+ },
14
+ "plugins": [
15
+ "@typescript-eslint"
16
+ ],
17
+ "rules": {
18
+ "indent": [
19
+ "error",
20
+ 2
21
+ ],
22
+ "max-len": [
23
+ "error",
24
+ 120
25
+ ],
26
+ "no-unused-vars": "warn",
27
+ "require-jsdoc": "off",
28
+ "spaced-comment": "off",
29
+ "linebreak-style": "off",
30
+ "curly": [
31
+ "error",
32
+ "multi-or-nest",
33
+ "consistent"
34
+ ],
35
+ "brace-style": [
36
+ "error",
37
+ "1tbs",
38
+ {
39
+ "allowSingleLine": true
40
+ }
41
+ ],
42
+ "block-spacing": [2, "always"],
43
+ "comma-dangle": ["error", {
44
+ "arrays": "only-multiline",
45
+ "functions": "never",
46
+ "objects": "only-multiline",
47
+ "imports": "only-multiline"
48
+ }],
49
+ "guard-for-in": "off"
50
+ }
51
+ }
File without changes
@@ -1,6 +1,6 @@
1
- ID,Test type,HELM string,Valid?,Mol Weight,Mol Formula,SMILES
2
- RNA,Simple RNA ,RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$,yes,1543.98,C48H61N17O34P4,[H]OCC1OC(N2C=CC(=O)NC2=O)C(O)C1OP(=O)(O)OCC3OC(N4C=C(C(=O)NC4=O)C)C(O)C3OP(=O)(O)OCC5OC(N6C=NC=7C(=O)NC(=NC76)N)C(O)C5OP(=O)(O)OCC8OC(N9C=CC(=NC9=O)N)C(O)C8OP(=O)(O)OCC%10OC(N%11C=NC=%12C(=NC=NC%12%11)N)C(O)C%10O[H]
3
- Different arrangements of dots,Only dots beween the backbone monomers,RNA1{P.R(U)P.R(T)}$$$$,yes,644.37,C19H26N4O17P2,OP(O)(OC[C@H]1O[C@@H](N(C2=O)C=CC(N2)=O)[C@H](O)[C@@H]1OP(O)(OC[C@H]3O[C@@H](N(C4=O)C=C(C)C(N4)=O)[C@H](O)[C@@H]3O[H])=O)=O
4
- Different arrangements of dots,Dot after the branch,RNA1{P.R(U).P.R(T)}$$$$,yes,644.37,C19H26N4O17P2,OP(O)(OC[C@H]1O[C@@H](N(C2=O)C=CC(N2)=O)[C@H](O)[C@@H]1OP(O)(OC[C@H]3O[C@@H](N(C4=O)C=C(C)C(N4)=O)[C@H](O)[C@@H]3O[H])=O)=O
5
- Hydrogen bonding,RNA with hydrogen bonding,"RNA1{R(A)P.R(C)P.R(G)P}|RNA2{P.R(C)P.R(G)P.R(T)}$RNA1,RNA2,2:pair-9:pair|RNA1,RNA2,5:pair-6:pair|RNA1,RNA2,8:pair-3:pair$$$",yes,1986.2,C58H77N23O44P6,NA
1
+ ID,Test type,HELM string,Valid?,Mol Weight,Mol Formula,SMILES
2
+ RNA,Simple RNA ,RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$,yes,1543.98,C48H61N17O34P4,[H]OCC1OC(N2C=CC(=O)NC2=O)C(O)C1OP(=O)(O)OCC3OC(N4C=C(C(=O)NC4=O)C)C(O)C3OP(=O)(O)OCC5OC(N6C=NC=7C(=O)NC(=NC76)N)C(O)C5OP(=O)(O)OCC8OC(N9C=CC(=NC9=O)N)C(O)C8OP(=O)(O)OCC%10OC(N%11C=NC=%12C(=NC=NC%12%11)N)C(O)C%10O[H]
3
+ Different arrangements of dots,Only dots beween the backbone monomers,RNA1{P.R(U)P.R(T)}$$$$,yes,644.37,C19H26N4O17P2,OP(O)(OC[C@H]1O[C@@H](N(C2=O)C=CC(N2)=O)[C@H](O)[C@@H]1OP(O)(OC[C@H]3O[C@@H](N(C4=O)C=C(C)C(N4)=O)[C@H](O)[C@@H]3O[H])=O)=O
4
+ Different arrangements of dots,Dot after the branch,RNA1{P.R(U).P.R(T)}$$$$,yes,644.37,C19H26N4O17P2,OP(O)(OC[C@H]1O[C@@H](N(C2=O)C=CC(N2)=O)[C@H](O)[C@@H]1OP(O)(OC[C@H]3O[C@@H](N(C4=O)C=C(C)C(N4)=O)[C@H](O)[C@@H]3O[H])=O)=O
5
+ Hydrogen bonding,RNA with hydrogen bonding,"RNA1{R(A)P.R(C)P.R(G)P}|RNA2{P.R(C)P.R(G)P.R(T)}$RNA1,RNA2,2:pair-9:pair|RNA1,RNA2,5:pair-6:pair|RNA1,RNA2,8:pair-3:pair$$$",yes,1986.2,C58H77N23O44P6,NA
6
6
  Hydrogen bonding,Shorter RNA with H bonding,"RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$",yes,1320.8,C39H52N14O30P4,NA
package/jest.config.js ADDED
@@ -0,0 +1,34 @@
1
+ module.exports = {
2
+ "roots": [
3
+ "<rootDir>/src"
4
+ ],
5
+ "testMatch": [
6
+ "**/__jest__/**/*.test.+(ts|tsx)"
7
+ ],
8
+ moduleFileExtensions: [
9
+ 'ts',
10
+ 'js',
11
+ ],
12
+ "transform": {
13
+ "^.+\\.(ts|tsx)$": "ts-jest"
14
+ },
15
+ transformIgnorePatterns: ['^.+\\.js$'],
16
+ globals: {
17
+ 'ts-jest': {
18
+ 'tsconfig': {
19
+ "target": "es6",
20
+ "module": "es2020",
21
+ }
22
+ }
23
+ },
24
+ reporters: [
25
+ "default",
26
+ [
27
+ "./node_modules/jest-html-reporter",
28
+ {
29
+ "includeConsoleLog": true,
30
+ "includeFailureMsg": true
31
+ }
32
+ ]
33
+ ]
34
+ }
package/package.json CHANGED
@@ -2,16 +2,16 @@
2
2
  "name": "@datagrok/helm",
3
3
  "beta": false,
4
4
  "friendlyName": "Helm",
5
- "version": "1.0.1",
5
+ "version": "1.0.4",
6
6
  "description": "",
7
7
  "dependencies": {
8
- "datagrok-api": "latest",
9
- "cash-dom": "latest",
10
- "dayjs": "latest",
11
- "@datagrok-libraries/utils": "latest",
12
- "@datagrok-libraries/bio": "^2.8.2",
13
- "ts-loader": "^9.2.5",
14
- "typescript": "^4.4.2"
8
+ "datagrok-api": "^1.4.12",
9
+ "cash-dom": "latest",
10
+ "dayjs": "latest",
11
+ "@datagrok-libraries/utils": "latest",
12
+ "@datagrok-libraries/bio": "^2.8.3",
13
+ "ts-loader": "^9.2.5",
14
+ "typescript": "^4.4.2"
15
15
  },
16
16
  "sources": [
17
17
  "css/helm.css",
@@ -21,21 +21,39 @@
21
21
  "helm/JSDraw/Pistoia.HELM-uncompressed.js"
22
22
  ],
23
23
  "devDependencies": {
24
+ "@typescript-eslint/eslint-plugin": "latest",
25
+ "@typescript-eslint/parser": "latest",
26
+ "eslint": "latest",
27
+ "eslint-config-google": "latest",
28
+ "ts-loader": "latest",
29
+ "typescript": "latest",
24
30
  "webpack": "latest",
25
31
  "webpack-cli": "latest",
26
- "ts-loader": "latest",
27
- "typescript": "latest"
32
+ "@types/jest": "^27.0.0",
33
+ "jest": "^27.0.0",
34
+ "jest-html-reporter": "^3.5.0",
35
+ "puppeteer": "^13.7.0",
36
+ "ts-jest": "^27.0.0",
37
+ "@types/js-yaml": "^4.0.5",
38
+ "@types/node-fetch": "^2.6.1"
28
39
  },
29
40
  "scripts": {
30
- "link-all": "npm link datagrok-api @datagrok-libraries/utils",
41
+ "link-all": "npm link datagrok-api @datagrok-libraries/utils @datagrok-libraries/bio",
31
42
  "debug-helm": "webpack && grok publish ",
32
43
  "release-helm": "webpack && grok publish --release",
33
44
  "build-helm": "webpack",
34
45
  "build": "webpack",
46
+ "debug-local": "grok publish local",
47
+ "release-local": "grok publish local --release",
35
48
  "debug-helm-dev": "grok publish dev --rebuild",
36
49
  "release-helm-dev": "grok publish dev --rebuild --release",
37
50
  "debug-helm-public": "grok publish public --rebuild",
38
- "release-helm-public": "grok publish public --rebuild --release"
51
+ "release-helm-public": "grok publish public --rebuild --release",
52
+ "lint": "eslint \"./src/**/*.ts\"",
53
+ "lint-fix": "eslint \"./src/**/*.ts\" --fix",
54
+ "test": "set HOST=dev && jest",
55
+ "test-dev": "set HOST=dev && jest",
56
+ "test-local": "set HOST=localhost && jest"
39
57
  },
40
58
  "canEdit": [
41
59
  "Developers"
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import * as utils from './test-node';
6
+ import puppeteer from 'puppeteer';
7
+
8
+ const P_START_TIMEOUT: number = 100000;
9
+ let browser: puppeteer.Browser;
10
+ let page: puppeteer.Page;
11
+
12
+ beforeAll(async () => {
13
+ let out = await utils.getBrowserPage(puppeteer);
14
+ browser = out.browser;
15
+ page = out.page;
16
+ }, P_START_TIMEOUT);
17
+
18
+ afterAll(async () => {
19
+ await browser?.close();
20
+ });
21
+
22
+ expect.extend({
23
+ checkOutput(received, expected, context) {
24
+ if (received === expected) {
25
+ return {
26
+ message: () => context,
27
+ pass: true
28
+ };
29
+ } else {
30
+ return {
31
+ message: () => context,
32
+ pass: false
33
+ };
34
+ }
35
+ }
36
+ });
37
+
38
+ it('TEST', async () => {
39
+ const targetPackage:string = process.env.TARGET_PACKAGE ?? 'Helm';
40
+ console.log(`Testing ${targetPackage} package`);
41
+
42
+ let r = await page.evaluate((targetPackage):Promise<object> => {
43
+ return new Promise<object>((resolve, reject) => {
44
+ (<any>window).grok.functions.eval(targetPackage + ':test()').then((df: any) => {
45
+ let cStatus = df.columns.byName('success');
46
+ let cMessage = df.columns.byName('result');
47
+ let cCat = df.columns.byName('category');
48
+ let cName = df.columns.byName('name');
49
+ let failed = false;
50
+ let passReport = '';
51
+ let failReport = '';
52
+ for (let i = 0; i < df.rowCount; i++) {
53
+ if (cStatus.get(i)) {
54
+ passReport += `Test result : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
55
+ } else {
56
+ failed = true;
57
+ failReport += `Test result : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
58
+ }
59
+ }
60
+ resolve({failReport, passReport, failed});
61
+ }).catch((e: any) => reject(e));
62
+ });
63
+ }, targetPackage);
64
+ // @ts-ignore
65
+ console.log(r.passReport);
66
+ // @ts-ignore
67
+ expect(r.failed).checkOutput(false, r.failReport);
68
+ }, 100000);
@@ -0,0 +1,95 @@
1
+ import * as path from "path";
2
+ import * as os from "os";
3
+ import * as fs from "fs";
4
+ import * as yaml from 'js-yaml';
5
+ import fetch from 'node-fetch';
6
+
7
+ export async function getToken(url: string, key: string) {
8
+ let response = await fetch(`${url}/users/login/dev/${key}`, {method: 'POST'});
9
+ let json = await response.json() as Record<string, any>;
10
+ if (json.isSuccess == true)
11
+ return json.token;
12
+ else
13
+ throw 'Unable to login to server. Check your dev key';
14
+ }
15
+
16
+ export async function getWebUrl(url: string, token: string) {
17
+ let response = await fetch(`${url}/admin/plugins/admin/settings`, {headers: {Authorization: token}});
18
+ let json = await response.json() as Record<string, any>;
19
+ return json.settings.webRoot;
20
+ }
21
+
22
+ const grokDir = path.join(os.homedir(), '.grok');
23
+ const confPath = path.join(grokDir, 'config.yaml');
24
+
25
+ function mapURL(conf: Config): Indexable {
26
+ let urls: Indexable = {};
27
+ for (let server in conf.servers) {
28
+ urls[conf['servers'][server]['url']] = conf['servers'][server];
29
+ }
30
+ return urls;
31
+ }
32
+
33
+ export function getDevKey(hostKey: string): {url: string, key: string} {
34
+ let config = yaml.load(fs.readFileSync(confPath, 'utf8')) as any;
35
+ let host = hostKey == '' ? config.default : hostKey;
36
+ host = host.trim();
37
+ let urls = mapURL(config);
38
+ let key = '';
39
+ let url = '';
40
+ try {
41
+ let url = new URL(host).href;
42
+ if (url.endsWith('/')) url = url.slice(0, -1);
43
+ if (url in urls) key = config['servers'][urls[url]]['key'];
44
+ } catch (error) {
45
+ if (config['servers'][host] == null)
46
+ throw `Unknown server alias. Please add it to ${confPath}`;
47
+ url = config['servers'][host]['url'];
48
+ key = config['servers'][host]['key'];
49
+ }
50
+ return {url, key};
51
+ }
52
+
53
+ export async function getBrowserPage(puppeteer: any): Promise<{browser: any, page: any}> {
54
+ let url:string = process.env.HOST ?? '';
55
+ let cfg = getDevKey(url);
56
+ url = cfg.url;
57
+
58
+ let key = cfg.key;
59
+ let token = await getToken(url, key);
60
+ url = await getWebUrl(url, token);
61
+ console.log(`Using web root: ${url}`);
62
+
63
+ let browser = await puppeteer.launch({
64
+ args: ['--disable-dev-shm-usage', '--disable-features=site-per-process'],
65
+ ignoreHTTPSErrors: true,
66
+ });
67
+
68
+ let page = await browser.newPage();
69
+ await page.goto(`${url}/oauth/`);
70
+ await page.setCookie({name: 'auth', value: token});
71
+ await page.evaluate((token: any) => {
72
+ window.localStorage.setItem('auth', token);
73
+ }, token);
74
+ await page.goto(url);
75
+ try {
76
+ await page.waitForSelector('.grok-preloader');
77
+ await page.waitForFunction(() => document.querySelector('.grok-preloader') == null, {timeout: 100000});
78
+ } catch (error) {
79
+ throw error;
80
+ }
81
+ return {browser, page};
82
+ }
83
+
84
+
85
+ interface Config {
86
+ servers: {
87
+ [alias: string]: {
88
+ url: string,
89
+ key: string
90
+ }
91
+ },
92
+ default: string,
93
+ }
94
+
95
+ interface Indexable { [key: string]: any }
@@ -0,0 +1,31 @@
1
+ export const jsonSdfMonomerLibDict = {
2
+ "monomerType": null,
3
+ "smiles": null,
4
+ "name": "MonomerName",
5
+ "author": null,
6
+ "molfile": "molecule",
7
+ "naturalAnalog": "MonomerNaturalAnalogCode",
8
+ "rgroups": "MonomerCaps",
9
+ "createDate": null,
10
+ "id": null,
11
+ "polymerType": "MonomerType",
12
+ "symbol": "MonomerCode"
13
+ }
14
+
15
+ export const MONOMER_MANAGER_MAP = {
16
+ 'id': 'symbol',
17
+ 'n': 'name',
18
+ 'na': 'naturalAnalog',
19
+ 'type': 'polymerType',
20
+ 'mt': 'monomerType',
21
+ 'm': 'molfile',
22
+ }
23
+
24
+ export const SMILES = 'smiles';
25
+ export const RGROUPS = "rgroups";
26
+ export const MONOMER_SYMBOL = "symbol";
27
+ export const RGROUP_CAP_GROUP_SMILES = "capGroupSmiles";
28
+ export const RGROUP_ALTER_ID = "alternateId";
29
+ export const RGROUP_CAP_GROUP_NAME = "capGroupName";
30
+ export const RGROUP_LABEL = "label";
31
+ export const SDF_MONOMER_NAME = "MonomerName";
@@ -1,7 +1,7 @@
1
- import * as DG from "datagrok-api/dg";
2
- import * as grok from "datagrok-api/grok";
1
+ import * as DG from 'datagrok-api/dg';
2
+ import * as grok from 'datagrok-api/grok';
3
3
  import {runTests, tests} from '@datagrok-libraries/utils/src/test';
4
- import './tests/helm-tests.ts'
4
+ import './tests/helm-tests.ts';
5
5
 
6
6
  export const _package = new DG.Package();
7
7
  export {tests};
package/src/package.ts CHANGED
@@ -4,21 +4,26 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
  //import {lru} from '../../Bio/src/utils/cell-renderer';
6
6
  import {NotationConverter, NOTATION} from '@datagrok-libraries/bio/src/utils/notation-converter';
7
+ import {WebLogo} from '@datagrok-libraries/bio/src/viewers/web-logo';
8
+ import {createJsonMonomerLibFromSdf} from './utils';
9
+ import {MONOMER_MANAGER_MAP, RGROUPS, RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
7
10
  //import {ConverterFunc, DfReaderFunc} from '../../Bio/src/tests/types';
8
11
 
9
12
  export const _package = new DG.Package();
10
13
 
11
- const lru = new DG.LruCache<any, any>();
12
-
14
+ const lru = new DG.LruCache<any, any>();
15
+ const STORAGE_NAME = 'Libraries';
16
+ let i = 0;
17
+ const LIB_PATH = 'libraries/';
13
18
 
14
19
 
15
20
  //tags: init
16
- export async function initChem(): Promise<void> {
17
- // apparently HELMWebEditor requires dojo to be initialized first
18
- return new Promise((resolve, reject) => {
19
- // @ts-ignore
20
- dojo.ready(function () { resolve(null); });
21
- });
21
+ export async function initHelm(): Promise<void> {
22
+ let grid = grok.shell.tv.grid;
23
+ for (let j = 0; j <= i; j++) {
24
+ await monomerManager(await grok.dapi.userDataStorage.getValue(STORAGE_NAME, j.toString(), true));
25
+ }
26
+ grid.invalidate();
22
27
  }
23
28
 
24
29
  //tags: cellEditor
@@ -27,42 +32,51 @@ export async function initChem(): Promise<void> {
27
32
  export function editMoleculeCell(cell: DG.GridCell): void {
28
33
  let view = ui.div();
29
34
  org.helm.webeditor.MolViewer.molscale = 0.8;
30
- let app = new scil.helm.App(view, { showabout: false, mexfontsize: "90%", mexrnapinontab: true, topmargin: 20, mexmonomerstab: true, sequenceviewonly: false, mexfavoritefirst: true, mexfilter: true });
35
+ let app = new scil.helm.App(view, {
36
+ showabout: false,
37
+ mexfontsize: '90%',
38
+ mexrnapinontab: true,
39
+ topmargin: 20,
40
+ mexmonomerstab: true,
41
+ sequenceviewonly: false,
42
+ mexfavoritefirst: true,
43
+ mexfilter: true
44
+ });
31
45
  var sizes = app.calculateSizes();
32
46
  app.canvas.resize(sizes.rightwidth, sizes.topheight - 210);
33
- var s = { width: sizes.rightwidth + "px", height: sizes.bottomheight + "px" };
47
+ var s = {width: sizes.rightwidth + 'px', height: sizes.bottomheight + 'px'};
34
48
  //@ts-ignore
35
49
  scil.apply(app.sequence.style, s);
36
50
  //@ts-ignore
37
51
  scil.apply(app.notation.style, s);
38
- s = { width: sizes.rightwidth + "px", height: (sizes.bottomheight + app.toolbarheight) + "px" };
52
+ s = {width: sizes.rightwidth + 'px', height: (sizes.bottomheight + app.toolbarheight) + 'px'};
39
53
  //@ts-ignore
40
54
  scil.apply(app.properties.parent.style, s);
41
55
  app.structureview.resize(sizes.rightwidth, sizes.bottomheight + app.toolbarheight);
42
56
  app.mex.resize(sizes.topheight - 80);
43
- if (cell.gridColumn.column.tags[DG.TAGS.UNITS] === "HELM"){
57
+ if (cell.gridColumn.column.tags[DG.TAGS.UNITS] === 'HELM') {
44
58
  setTimeout(function() {
45
- app.canvas.helm.setSequence(cell.cell.value , 'HELM');
59
+ app.canvas.helm.setSequence(cell.cell.value, 'HELM');
46
60
  }, 200);
47
61
  //@ts-ignore
48
- ui.dialog({ showHeader: false, showFooter: true })
49
- .add(view)
50
- .onOK(() => {
51
- cell.cell.value = app.canvas.getHelm(true);
52
- }).show({ modal: true, fullScreen: true});
53
- }
54
- else if (cell.gridColumn.column.tags[DG.TAGS.UNITS] === "fasta:SEQ:PT") {
62
+ ui.dialog({showHeader: false, showFooter: true})
63
+ .add(view)
64
+ .onOK(() => {
65
+ const val = app.canvas.getHelm(true).replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
66
+ cell.cell.value = val;
67
+ }).show({modal: true, fullScreen: true});
68
+ } else if (cell.gridColumn.column.tags[DG.TAGS.UNITS] === 'fasta:SEQ:PT') {
55
69
  const converter = new NotationConverter(cell.gridColumn.column);
56
- const resStr = converter.convertFastaStringToHelm(null, null, cell.cell.value);
70
+ const resStr = converter.convertStringToHelm(cell.cell.value, '-', '*');
57
71
  setTimeout(function() {
58
- app.canvas.helm.setSequence(resStr , 'HELM');
72
+ app.canvas.helm.setSequence(resStr, 'HELM');
59
73
  }, 200);
60
74
  //@ts-ignore
61
- ui.dialog({ showHeader: false, showFooter: true })
62
- .add(view)
63
- .onOK(() => {
64
- cell.cell.value;
65
- }).show({ modal: true, fullScreen: true});
75
+ ui.dialog({showHeader: false, showFooter: true})
76
+ .add(view)
77
+ .onOK(() => {
78
+ cell.cell.value;
79
+ }).show({modal: true, fullScreen: true});
66
80
  }
67
81
  }
68
82
 
@@ -70,9 +84,9 @@ export function editMoleculeCell(cell: DG.GridCell): void {
70
84
  //tags: panel, widgets
71
85
  //input: string helmString {semType: Macromolecule}
72
86
  //output: widget result
73
- export function detailsPanel(helmString: string){
87
+ export function detailsPanel(helmString: string) {
74
88
  //return new DG.Widget(ui.divText(lru.get(helmString)));
75
- var result = lru.get(helmString).split(',');
89
+ const result = lru.get(helmString).split(',');
76
90
  return new DG.Widget(
77
91
  ui.tableFromMap({
78
92
  'formula': result[0].replace(/<sub>/g, '').replace(/<\/sub>/g, ''),
@@ -84,40 +98,54 @@ export function detailsPanel(helmString: string){
84
98
  'smiles': ui.wait(async () => ui.divText(await helmToSmiles(helmString))),
85
99
  //'peptide analogue sequence': ui.wait(async () => ui.divText(await helmToPeptide(helmString))),
86
100
  })
87
- )
101
+ );
88
102
  }
89
103
 
90
104
 
91
- async function loadDialog () {
105
+ async function loadDialog() {
92
106
  //@ts-ignore
93
- let res = await grok.dapi.files.list('System:AppData/Helm', false, '');
107
+ let res = await grok.dapi.files.list('System:AppData/Helm/libraries', false, '');
94
108
  //@ts-ignore
95
109
  res = res.map((e) => e.path);
96
110
  //@ts-ignore
97
111
  let FilesList = await ui.choiceInput('Monomer Libraries', ' ', res);
98
112
  let grid = grok.shell.tv.grid;
99
113
  ui.dialog('Load library from file')
100
- .add(FilesList)
101
- .onOK(async () => {
102
- await monomerManager(FilesList.value);
103
- grid.invalidate()
104
- }).show();
114
+ .add(FilesList)
115
+ .onOK(async () => {
116
+ await monomerManager(FilesList.value);
117
+ grok.dapi.userDataStorage.postValue(STORAGE_NAME, i.toString(), FilesList.value, true);
118
+ i += 1;
119
+ grid.invalidate();
120
+ }).show();
105
121
  };
106
122
 
107
- //name: Library
123
+ //name: Manage Libraries
108
124
  //tags: panel, widgets
109
- //input: string helmString {semType: Macromolecule}
125
+ //input: column helmColumn {semType: Macromolecule}
110
126
  //output: widget result
111
- export function libraryPanel(helmString: string) {
127
+ export async function libraryPanel(helmColumn: DG.Column) {
112
128
  //@ts-ignore
113
129
  let loadButton = ui.button('Load Library');
114
130
  loadButton.addEventListener('click', loadDialog);
115
- return new DG.Widget(ui.divH([loadButton]));
131
+ let list = (await grok.dapi.files.list('System:AppData/Helm/libraries', false, '')).toString().split(',');
132
+ let usedLibraries = [];
133
+ for (let j = 0; j <= i; j++) {
134
+ usedLibraries.push(await grok.dapi.userDataStorage.getValue(STORAGE_NAME, j.toString(), true));
135
+ }
136
+ let unusedLibraries = list.filter(x => !usedLibraries.includes(x));
137
+ return new DG.Widget(
138
+ ui.tableFromMap({
139
+ 'Uploaded libraries': ui.divText(usedLibraries.toString()),
140
+ 'Not used libraries': ui.divText(unusedLibraries.toString()),
141
+ 'Upload new library': ui.divH([loadButton]),
142
+ })
143
+ );
116
144
  }
117
145
 
118
146
  async function accessServer(url: string, key: string) {
119
147
  const params: RequestInit = {
120
- method: 'GET',
148
+ method: 'GET',
121
149
  headers: {
122
150
  'Accept': 'application/json',
123
151
  }
@@ -132,7 +160,7 @@ async function accessServer(url: string, key: string) {
132
160
  //input: string helmString {semType: Macromolecule}
133
161
  //output: string res
134
162
  export async function helmToFasta(helmString: string) {
135
- const url = `http://localhost:8081/WebService/service/Fasta/Produce/${helmString}`
163
+ const url = `http://localhost:8081/WebService/service/Fasta/Produce/${helmString}`;
136
164
  return await accessServer(url, 'FastaFile');
137
165
  }
138
166
 
@@ -141,7 +169,7 @@ export async function helmToFasta(helmString: string) {
141
169
  //input: string helmString {semType: Macromolecule}
142
170
  //output: string res
143
171
  export async function helmToRNA(helmString: string) {
144
- const url = `http://localhost:8081/WebService/service/Fasta/Convert/RNA/${helmString}`
172
+ const url = `http://localhost:8081/WebService/service/Fasta/Convert/RNA/${helmString}`;
145
173
  return await accessServer(url, 'Sequence');
146
174
  }
147
175
 
@@ -151,7 +179,7 @@ export async function helmToRNA(helmString: string) {
151
179
  //input: string helmString {semType: Macromolecule}
152
180
  //output: string res
153
181
  export async function helmToPeptide(helmString: string) {
154
- const url = `http://localhost:8081/WebService/service/Fasta/Convert/PEPTIDE/${helmString}`
182
+ const url = `http://localhost:8081/WebService/service/Fasta/Convert/PEPTIDE/${helmString}`;
155
183
  return await accessServer(url, 'Sequence');
156
184
  }
157
185
 
@@ -160,24 +188,23 @@ export async function helmToPeptide(helmString: string) {
160
188
  //input: string helmString {semType: Macromolecule}
161
189
  //output: string smiles {semType: Molecule}
162
190
  export async function helmToSmiles(helmString: string) {
163
- const url = `http://localhost:8081/WebService/service/SMILES/${helmString}`
191
+ const url = `http://localhost:8081/WebService/service/SMILES/${helmString}`;
164
192
  return await accessServer(url, 'SMILES');
165
193
  }
166
194
 
167
195
  function getRS(smiles: string) {
168
- var new_s = smiles.match(/(?<=\[)[^\][]*(?=])/gm);
169
- var res = {};
170
- var el = '';
171
- var digit;
172
- for (var i = 0; i < new_s.length; i++) {
173
- if (new_s[i] != null){
174
- if (/\d/.test(new_s[i])) {
175
- digit = new_s[i][new_s[i].length - 1];
176
- new_s[i] = new_s[i].replace(/[0-9]/g, '');
177
- for (var j = 0; j < new_s[i].length; j++) {
178
- if (new_s[i][j] != ':'){
179
- el += new_s[i][j];
180
- }
196
+ const newS = smiles.match(/(?<=\[)[^\][]*(?=])/gm);
197
+ const res = {};
198
+ let el = '';
199
+ let digit;
200
+ for (let i = 0; i < newS.length; i++) {
201
+ if (newS[i] != null) {
202
+ if (/\d/.test(newS[i])) {
203
+ digit = newS[i][newS[i].length - 1];
204
+ newS[i] = newS[i].replace(/[0-9]/g, '');
205
+ for (let j = 0; j < newS[i].length; j++) {
206
+ if (newS[i][j] != ':')
207
+ el += newS[i][j];
181
208
  }
182
209
  res['R' + digit] = el;
183
210
  el = '';
@@ -188,22 +215,36 @@ function getRS(smiles: string) {
188
215
  }
189
216
 
190
217
  async function monomerManager(value: string) {
191
- const file = await _package.files.readAsText(value.split('/')[1]);
192
- const df = DG.DataFrame.fromJson(file);
193
- var m;
194
- for (var i = 0; i < df.rowCount; i++){
195
- m = {
196
- 'id': df.get('symbol', i).toString(),
197
- 'n': df.get('name', i).toString(),
198
- 'na': df.get('naturalAnalog', i).toString(),
199
- 'type': df.get('polymerType', i).toString(),
200
- 'mt': df.get('monomerType', i).toString(),
201
- 'm': df.get('molfile', i).toString(),
202
- rs: Object.keys(getRS(df.get('smiles', i).toString())).length,
203
- at: getRS(df.get('smiles', i).toString()),
218
+ let df: any[];
219
+ const libName = value.substring(value.lastIndexOf('/') + 1);
220
+ if (value.endsWith('.sdf')) {
221
+ const file = await _package.files.readAsBytes(`${LIB_PATH}${libName}`);
222
+ const dfSdf = await grok.functions.call('Chem:importSdf', {bytes: file});
223
+ df = createJsonMonomerLibFromSdf(dfSdf[0]);
224
+
225
+ } else {
226
+ const file = await _package.files.readAsText(`${LIB_PATH}${libName}`);
227
+ df = JSON.parse(file);
228
+ }
229
+
230
+ df.forEach(monomer => {
231
+ const m = {};
232
+ Object.keys(MONOMER_MANAGER_MAP).forEach(field => {
233
+ m[field] = monomer[MONOMER_MANAGER_MAP[field]] ?? '';
234
+ });
235
+ if (monomer[RGROUPS]) {
236
+ m['rs'] = monomer[RGROUPS].length;
237
+ const at = {};
238
+ monomer[RGROUPS].forEach(it => {
239
+ at[it[RGROUP_LABEL]] = it[RGROUP_CAP_GROUP_NAME];
240
+ });
241
+ m['at'] = at;
242
+ } else {
243
+ m['rs'] = Object.keys(getRS(df[SMILES].toString())).length;
244
+ m['at'] = getRS(df[SMILES].toString());
204
245
  }
205
246
  org.helm.webeditor.Monomers.addOneMonomer(m);
206
- }
247
+ });
207
248
  }
208
249
 
209
250
  //name: helmColumnToSmiles
@@ -217,36 +258,42 @@ export function helmColumnToSmiles(helmColumn: DG.Column) {
217
258
  export async function findMonomers(helmString: string) {
218
259
  const types = Object.keys(org.helm.webeditor.monomerTypeList());
219
260
  const monomers = [];
220
- const monomer_names = [];
221
- for (var i = 0; i < types.length; i++) {
222
- monomers.push(new scil.helm.Monomers.getMonomerSet(types[i]));
223
- Object.keys(monomers[i]).forEach(k => {
224
- monomer_names.push(monomers[i][k].id);
261
+ const monomerNames = [];
262
+ for (let i = 0; i < types.length; i++) {
263
+ // Synonym to overcome eslint error
264
+ const GetMonomerSet = scil.helm.Monomers.getMonomerSet;
265
+ monomers.push(new GetMonomerSet(types[i]));
266
+ Object.keys(monomers[i]).forEach((k) => {
267
+ monomerNames.push(monomers[i][k].id);
225
268
  });
226
269
  }
227
- const split_string = helmString.match(/[^\s.,\/\\(){}$]+/gim);
228
- return new Set(split_string.filter(val => !monomer_names.includes(val)));
270
+ const splitString = WebLogo.splitterAsHelm(helmString);
271
+ return new Set(splitString.filter((val) => !monomerNames.includes(val)));
229
272
  }
230
273
 
231
274
  class HelmCellRenderer extends DG.GridCellRenderer {
232
-
233
275
  get name() { return 'macromolecule'; }
276
+
234
277
  get cellType() { return 'macromolecule'; }
278
+
235
279
  get defaultWidth(): number | null { return 400; }
280
+
236
281
  get defaultHeight(): number | null { return 100; }
237
282
 
238
- render(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, gridCell: DG.GridCell, cellStyle: DG.GridCellStyle) {
239
- let host = ui.div([], { style: { width: `${w}px`, height: `${h}px`}});
283
+ render(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number,
284
+ gridCell: DG.GridCell, cellStyle: DG.GridCellStyle
285
+ ) {
286
+ const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
240
287
  host.setAttribute('dataformat', 'helm');
241
288
  host.setAttribute('data', gridCell.cell.value);
242
289
 
243
290
  gridCell.element = host;
244
- var canvas = new JSDraw2.Editor(host, { width: w, height: h, skin: "w8", viewonly: true });
245
- var formula = canvas.getFormula(true);
246
- var molWeight = Math.round(canvas.getMolWeight() * 100) / 100;
247
- var coef = Math.round(canvas.getExtinctionCoefficient(true) * 100) / 100;
248
- var molfile = canvas.getMolfile();
249
- var result = formula + ', ' + molWeight + ', ' + coef + ', ' + molfile;
291
+ const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
292
+ const formula = canvas.getFormula(true);
293
+ const molWeight = Math.round(canvas.getMolWeight() * 100) / 100;
294
+ const coef = Math.round(canvas.getExtinctionCoefficient(true) * 100) / 100;
295
+ const molfile = canvas.getMolfile();
296
+ const result = formula + ', ' + molWeight + ', ' + coef + ', ' + molfile;
250
297
  lru.set(gridCell.cell.value, result);
251
298
  }
252
299
  }
@@ -1,43 +1,46 @@
1
1
  import {before, category, expect, test} from '@datagrok-libraries/utils/src/test';
2
- import { helmToFasta, helmToPeptide, helmToRNA } from '../package';
3
- import { _package } from '../package-test';
2
+ import {helmToFasta, helmToPeptide, helmToRNA} from '../package';
3
+ import {_package} from '../package-test';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
  import * as grok from 'datagrok-api/grok';
6
6
 
7
7
 
8
8
  category('Helm', () => {
9
9
 
10
- test('helmToFasta', async () => {
11
- expect(await helmToFasta('RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$'), '>RNA1UTGCA');
12
- expect(await helmToFasta('RNA1{P.R(U).P.R(T)}$$$$'), '>RNA1UT');
13
- expect(await helmToFasta('PEPTIDE1{A.G}$$$$V2.0'), '>PEPTIDE1AG')
14
- });
15
-
16
- test('helmToRNA', async () => {
17
- expect(await helmToRNA('RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$'), 'UTGCA');
18
- expect(await helmToRNA('RNA1{P.R(U).P.R(T)}$$$$'), 'UT');
19
- expect(await helmToRNA('RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$'), 'UT AA');
20
- });
21
-
22
- test('helmToPeptide', async () => {
23
- expect(await helmToPeptide('PEPTIDE1{A.G}$$$$V2.0'), 'AG');
24
- expect(await helmToPeptide('PEPTIDE1{L.V.A}|PEPTIDE2{L.V.A}$$$$'), 'LVA LVA');
25
- expect(await helmToPeptide('PEPTIDE1{A.R.C.A.A.K.T.C.D.A}$PEPTIDE1,PEPTIDE1,8:R3-3:R3$$$'), 'ARCAAKTCDA');
26
- });
10
+ // These tests require webservice that is not present on test stand
11
+ // test('helmToFasta', async () => {
12
+ // expect(await helmToFasta('RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$'), '>RNA1UTGCA');
13
+ // expect(await helmToFasta('RNA1{P.R(U).P.R(T)}$$$$'), '>RNA1UT');
14
+ // expect(await helmToFasta('PEPTIDE1{A.G}$$$$V2.0'), '>PEPTIDE1AG');
15
+ // });
16
+ //
17
+ // test('helmToRNA', async () => {
18
+ // expect(await helmToRNA('RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$'), 'UTGCA');
19
+ // expect(await helmToRNA('RNA1{P.R(U).P.R(T)}$$$$'), 'UT');
20
+ // // eslint-disable-next-line max-len
21
+ // expect(await helmToRNA('RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$'), 'UT AA');
22
+ // });
23
+ //
24
+ // test('helmToPeptide', async () => {
25
+ // expect(await helmToPeptide('PEPTIDE1{A.G}$$$$V2.0'), 'AG');
26
+ // expect(await helmToPeptide('PEPTIDE1{L.V.A}|PEPTIDE2{L.V.A}$$$$'), 'LVA LVA');
27
+ // // eslint-disable-next-line max-len
28
+ // expect(await helmToPeptide('PEPTIDE1{A.R.C.A.A.K.T.C.D.A}$PEPTIDE1,PEPTIDE1,8:R3-3:R3$$$'), 'ARCAAKTCDA');
29
+ // });
27
30
 
28
31
  test('detectMacromolecule', async () => {
29
- const file = await _package.files.readAsText('test.csv')
32
+ const file = await _package.files.readAsText('tests/test.csv');
30
33
  const df = DG.DataFrame.fromCsv(file);
31
- let col = df.columns.byName('HELM string');
34
+ const col = df.columns.byName('HELM string');
32
35
  await grok.data.detectSemanticTypes(df);
33
36
  expect(col.semType, DG.SEMTYPE.MACROMOLECULE);
34
37
  });
35
38
 
36
39
  test('detectHelm', async () => {
37
- const file = await _package.files.readAsText('test.csv')
40
+ const file = await _package.files.readAsText('tests/test.csv');
38
41
  const df = DG.DataFrame.fromCsv(file);
39
- let col = df.columns.byName('HELM string');
42
+ const col = df.columns.byName('HELM string');
40
43
  await grok.data.detectSemanticTypes(df);
41
- expect(col.tags[DG.TAGS.UNITS], "HELM");
44
+ expect(col.tags[DG.TAGS.UNITS], 'HELM');
42
45
  });
43
46
  });
package/src/utils.ts ADDED
@@ -0,0 +1,35 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+ import { RGROUP_CAP_GROUP_NAME, RGROUP_CAP_GROUP_SMILES, jsonSdfMonomerLibDict, MONOMER_SYMBOL, RGROUP_ALTER_ID, RGROUPS, RGROUP_LABEL, SDF_MONOMER_NAME } from "./constants";
3
+
4
+ export function createJsonMonomerLibFromSdf(table: DG.DataFrame): any {
5
+ const resultLib = [];
6
+ for (let i = 0; i < table.rowCount; i++) {
7
+ const monomer: { [key: string]: string | any } = {};
8
+ Object.keys(jsonSdfMonomerLibDict).forEach(key => {
9
+ if (key === MONOMER_SYMBOL) {
10
+ const monomerSymbol = table.get(jsonSdfMonomerLibDict[key], i);
11
+ monomer[key] = monomerSymbol === '.' ? table.get(SDF_MONOMER_NAME, i) : monomerSymbol;
12
+ } else if (key === RGROUPS) {
13
+ const rgroups = table.get(jsonSdfMonomerLibDict[key], i).split('\n');
14
+ const jsonRgroups: any[] = [];
15
+ rgroups.forEach((g: string) => {
16
+ const rgroup: { [key: string]: string | any } = {};
17
+ const altAtom = g.substring(g.lastIndexOf("]") + 1);
18
+ let radicalNum = g.match(/\[R(\d+)\]/)![1];
19
+ rgroup[RGROUP_CAP_GROUP_SMILES] = altAtom === 'H' ? `[*:${radicalNum}][H]` : `O[*:${radicalNum}]`;
20
+ rgroup[RGROUP_ALTER_ID] = altAtom === 'H' ? `R${radicalNum}-H` : `R${radicalNum}-OH`;
21
+ rgroup[RGROUP_CAP_GROUP_NAME] = altAtom === 'H' ? `H` : `OH`;
22
+ rgroup[RGROUP_LABEL] = `R${radicalNum}`;
23
+ jsonRgroups.push(rgroup);
24
+ })
25
+ monomer[key] = jsonRgroups;
26
+ } else {
27
+ if((jsonSdfMonomerLibDict as { [key: string]: string | any })[key]) {
28
+ monomer[key] = table.get((jsonSdfMonomerLibDict as { [key: string]: string | any })[key], i);
29
+ }
30
+ }
31
+ })
32
+ resultLib.push(monomer);
33
+ }
34
+ return resultLib;
35
+ }
@@ -0,0 +1,251 @@
1
+ <html><head><meta charset="utf-8"/><title>Helm Test Report. Datagrok version datagrok/datagrok:latest SHA=34f75e5127b8. Commit 75a1863f.</title><style type="text/css">html,
2
+ body {
3
+ font-family: Arial, Helvetica, sans-serif;
4
+ font-size: 1rem;
5
+ margin: 0;
6
+ padding: 0;
7
+ color: #333;
8
+ }
9
+ body {
10
+ padding: 2rem 1rem;
11
+ font-size: 0.85rem;
12
+ }
13
+ #jesthtml-content {
14
+ margin: 0 auto;
15
+ max-width: 70rem;
16
+ }
17
+ header {
18
+ display: flex;
19
+ align-items: center;
20
+ }
21
+ #title {
22
+ margin: 0;
23
+ flex-grow: 1;
24
+ }
25
+ #logo {
26
+ height: 4rem;
27
+ }
28
+ #timestamp {
29
+ color: #777;
30
+ margin-top: 0.5rem;
31
+ }
32
+
33
+ /** SUMMARY */
34
+ #summary {
35
+ color: #333;
36
+ margin: 2rem 0;
37
+ display: flex;
38
+ font-family: monospace;
39
+ font-size: 1rem;
40
+ }
41
+ #summary > div {
42
+ margin-right: 2rem;
43
+ background: #eee;
44
+ padding: 1rem;
45
+ min-width: 15rem;
46
+ }
47
+ #summary > div:last-child {
48
+ margin-right: 0;
49
+ }
50
+ @media only screen and (max-width: 720px) {
51
+ #summary {
52
+ flex-direction: column;
53
+ }
54
+ #summary > div {
55
+ margin-right: 0;
56
+ margin-top: 2rem;
57
+ }
58
+ #summary > div:first-child {
59
+ margin-top: 0;
60
+ }
61
+ }
62
+
63
+ .summary-total {
64
+ font-weight: bold;
65
+ margin-bottom: 0.5rem;
66
+ }
67
+ .summary-passed {
68
+ color: #4f8a10;
69
+ border-left: 0.4rem solid #4f8a10;
70
+ padding-left: 0.5rem;
71
+ }
72
+ .summary-failed,
73
+ .summary-obsolete-snapshots {
74
+ color: #d8000c;
75
+ border-left: 0.4rem solid #d8000c;
76
+ padding-left: 0.5rem;
77
+ }
78
+ .summary-pending {
79
+ color: #9f6000;
80
+ border-left: 0.4rem solid #9f6000;
81
+ padding-left: 0.5rem;
82
+ }
83
+ .summary-empty {
84
+ color: #999;
85
+ border-left: 0.4rem solid #999;
86
+ }
87
+
88
+ .test-result {
89
+ padding: 1rem;
90
+ margin-bottom: 0.25rem;
91
+ }
92
+ .test-result:last-child {
93
+ border: 0;
94
+ }
95
+ .test-result.passed {
96
+ background-color: #dff2bf;
97
+ color: #4f8a10;
98
+ }
99
+ .test-result.failed {
100
+ background-color: #ffbaba;
101
+ color: #d8000c;
102
+ }
103
+ .test-result.pending {
104
+ background-color: #ffdf61;
105
+ color: #9f6000;
106
+ }
107
+
108
+ .test-info {
109
+ display: flex;
110
+ justify-content: space-between;
111
+ }
112
+ .test-suitename {
113
+ width: 20%;
114
+ text-align: left;
115
+ font-weight: bold;
116
+ word-break: break-word;
117
+ }
118
+ .test-title {
119
+ width: 40%;
120
+ text-align: left;
121
+ font-style: italic;
122
+ }
123
+ .test-status {
124
+ width: 20%;
125
+ text-align: right;
126
+ }
127
+ .test-duration {
128
+ width: 10%;
129
+ text-align: right;
130
+ font-size: 0.75rem;
131
+ }
132
+
133
+ .failureMessages {
134
+ padding: 0 1rem;
135
+ margin-top: 1rem;
136
+ border-top: 1px dashed #d8000c;
137
+ }
138
+ .failureMessages.suiteFailure {
139
+ border-top: none;
140
+ }
141
+ .failureMsg {
142
+ white-space: pre-wrap;
143
+ white-space: -moz-pre-wrap;
144
+ white-space: -pre-wrap;
145
+ white-space: -o-pre-wrap;
146
+ word-wrap: break-word;
147
+ }
148
+
149
+ .suite-container {
150
+ margin-bottom: 2rem;
151
+ }
152
+ .suite-info {
153
+ padding: 1rem;
154
+ background-color: #eee;
155
+ color: #777;
156
+ display: flex;
157
+ align-items: center;
158
+ margin-bottom: 0.25rem;
159
+ }
160
+ .suite-info .suite-path {
161
+ word-break: break-all;
162
+ flex-grow: 1;
163
+ font-family: monospace;
164
+ font-size: 1rem;
165
+ }
166
+ .suite-info .suite-time {
167
+ margin-left: 0.5rem;
168
+ padding: 0.2rem 0.3rem;
169
+ font-size: 0.75rem;
170
+ }
171
+ .suite-info .suite-time.warn {
172
+ background-color: #d8000c;
173
+ color: #fff;
174
+ }
175
+
176
+ /* CONSOLE LOGS */
177
+ .suite-consolelog {
178
+ margin-bottom: 0.25rem;
179
+ padding: 1rem;
180
+ background-color: #efefef;
181
+ }
182
+ .suite-consolelog-header {
183
+ font-weight: bold;
184
+ }
185
+ .suite-consolelog-item {
186
+ padding: 0.5rem;
187
+ }
188
+ .suite-consolelog-item pre {
189
+ margin: 0.5rem 0;
190
+ white-space: pre-wrap;
191
+ white-space: -moz-pre-wrap;
192
+ white-space: -pre-wrap;
193
+ white-space: -o-pre-wrap;
194
+ word-wrap: break-word;
195
+ }
196
+ .suite-consolelog-item-origin {
197
+ color: #777;
198
+ font-weight: bold;
199
+ }
200
+ .suite-consolelog-item-message {
201
+ color: #000;
202
+ font-size: 1rem;
203
+ padding: 0 0.5rem;
204
+ }
205
+
206
+ /* OBSOLETE SNAPSHOTS */
207
+ .suite-obsolete-snapshots {
208
+ margin-bottom: 0.25rem;
209
+ padding: 1rem;
210
+ background-color: #ffbaba;
211
+ color: #d8000c;
212
+ }
213
+ .suite-obsolete-snapshots-header {
214
+ font-weight: bold;
215
+ }
216
+ .suite-obsolete-snapshots-item {
217
+ padding: 0.5rem;
218
+ }
219
+ .suite-obsolete-snapshots-item pre {
220
+ margin: 0.5rem 0;
221
+ white-space: pre-wrap;
222
+ white-space: -moz-pre-wrap;
223
+ white-space: -pre-wrap;
224
+ white-space: -o-pre-wrap;
225
+ word-wrap: break-word;
226
+ }
227
+ .suite-obsolete-snapshots-item-message {
228
+ color: #000;
229
+ font-size: 1rem;
230
+ padding: 0 0.5rem;
231
+ }
232
+ </style></head><body><div id="jesthtml-content"><header><h1 id="title">Helm Test Report. Datagrok version datagrok/datagrok:latest SHA=34f75e5127b8. Commit 75a1863f.</h1></header><div id="metadata-container"><div id="timestamp">Started: 2022-07-15 14:33:36</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><div class="suite-info"><div class="suite-path">/home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts</div><div class="suite-time warn">11.56s</div></div><div class="suite-tests"><div class="test-result failed"><div class="test-info"><div class="test-suitename"> </div><div class="test-title">TEST</div><div class="test-status">failed</div><div class="test-duration">1.013s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: Test result : Helm.Helm.detectMacromolecule : Error: Expected "Macromolecule", got "null"
233
+ Test result : Helm.Helm.detectHelm : Error: Expected "HELM", got "null"
234
+
235
+ at /home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:67:20
236
+ at Generator.next (&lt;anonymous&gt;)
237
+ at fulfilled (/home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:31:58)
238
+ at processTicksAndRejections (internal/process/task_queues.js:97:5)</pre></div></div></div><div class="suite-consolelog"><div class="suite-consolelog-header">Console Log</div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.&lt;anonymous&gt; (/home/runner/work/public/public/packages/Helm/src/__jest__/test-node.ts:61:11)
239
+ at Generator.next (&lt;anonymous&gt;)
240
+ at fulfilled (/home/runner/work/public/public/packages/Helm/src/__jest__/test-node.ts:28:58)
241
+ at processTicksAndRejections (internal/process/task_queues.js:97:5)</pre><pre class="suite-consolelog-item-message">Using web root: http://localhost:8080</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at /home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:40:11
242
+ at Generator.next (&lt;anonymous&gt;)
243
+ at /home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:34:71
244
+ at new Promise (&lt;anonymous&gt;)
245
+ at Object.&lt;anonymous&gt;.__awaiter (/home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:30:12)
246
+ at Object.&lt;anonymous&gt; (/home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:38:23)
247
+ at Promise.then.completed (/home/runner/work/public/public/packages/Helm/node_modules/jest-circus/build/utils.js:391:28)
248
+ at new Promise (&lt;anonymous&gt;)</pre><pre class="suite-consolelog-item-message">Testing Helm package</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at /home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:65:11
249
+ at Generator.next (&lt;anonymous&gt;)
250
+ at fulfilled (/home/runner/work/public/public/packages/Helm/src/__jest__/remote.test.ts:31:58)
251
+ at processTicksAndRejections (internal/process/task_queues.js:97:5)</pre><pre class="suite-consolelog-item-message"/></div></div></div></div></body></html>