@vitessce/config 3.0.0 → 3.0.1

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/dist/index.js CHANGED
@@ -15751,41 +15751,91 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
15751
15751
  }
15752
15752
  return views;
15753
15753
  }
15754
+ async setMetadataSummaryWithZmetadata(response) {
15755
+ const metadataFile = await response.json();
15756
+ if (!metadataFile.metadata) {
15757
+ throw new Error("Could not generate config: .zmetadata file is not valid.");
15758
+ }
15759
+ const obsmKeys = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obsm/X_")).map((key) => key.split("/.zarray")[0]);
15760
+ const obsKeysArr = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obs/")).map((key) => key.split("/.za")[0]);
15761
+ function uniq(a) {
15762
+ return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
15763
+ }
15764
+ const obsKeys = uniq(obsKeysArr);
15765
+ const X = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("X"));
15766
+ return {
15767
+ // Array of keys in obsm that are found by the fetches above
15768
+ obsm: obsmKeys,
15769
+ // Array of keys in obs that are found by the fetches above
15770
+ obs: obsKeys,
15771
+ // Boolean indicating whether the X array was found by the fetches above
15772
+ X: X.length > 0
15773
+ };
15774
+ }
15775
+ async setMetadataSummaryWithoutZmetadata() {
15776
+ const knownMetadataFileSuffixes = [
15777
+ "/obsm/X_pca/.zarray",
15778
+ "/obsm/X_umap/.zarray",
15779
+ "/obsm/X_tsne/.zarray",
15780
+ "/obsm/X_spatial/.zarray",
15781
+ "/obsm/X_segmentations/.zarray",
15782
+ "/obs/.zattrs",
15783
+ "/X/.zarray"
15784
+ ];
15785
+ const getObsmKey = (url) => {
15786
+ const obsmKeyStartIndex = `${this.fileUrl}/`.length;
15787
+ const obsmKeyEndIndex = url.length - "/.zarray".length;
15788
+ return url.substring(obsmKeyStartIndex, obsmKeyEndIndex);
15789
+ };
15790
+ const promises = knownMetadataFileSuffixes.map((suffix) => fetch(`${this.fileUrl}${suffix}`));
15791
+ const fetchResults = await Promise.all(promises);
15792
+ const okFetchResults = fetchResults.filter((j) => j.ok);
15793
+ const metadataSummary = {
15794
+ // Array of keys in obsm that are found by the fetches above
15795
+ obsm: [],
15796
+ // Array of keys in obs that are found by the fetches above
15797
+ obs: [],
15798
+ // Boolean indicating whether the X array was found by the fetches above
15799
+ X: false
15800
+ };
15801
+ const obsPromiseResult = okFetchResults.find(
15802
+ (r) => r.url === `${this.fileUrl}/obs/.zattrs`
15803
+ );
15804
+ const isObsValid = (obsAttr) => Object.keys(obsAttr).includes("column-order") && Object.keys(obsAttr).includes("encoding-version") && Object.keys(obsAttr).includes("encoding-type") && obsAttr["encoding-type"] === "dataframe" && (obsAttr["encoding-version"] === "0.1.0" || obsAttr["encoding-version"] === "0.2.0");
15805
+ if (obsPromiseResult) {
15806
+ const obsAttrs = await obsPromiseResult.json();
15807
+ if (isObsValid(obsAttrs)) {
15808
+ obsAttrs["column-order"].forEach((key) => metadataSummary.obs.push(`obs/${key}`));
15809
+ } else {
15810
+ throw new Error("Could not generate config: /obs/.zattrs file is not valid.");
15811
+ }
15812
+ }
15813
+ okFetchResults.forEach((r) => {
15814
+ if (r.url.startsWith(`${this.fileUrl}/obsm`)) {
15815
+ const obsmKey = getObsmKey(r.url);
15816
+ if (obsmKey) {
15817
+ metadataSummary.obsm.push(obsmKey);
15818
+ }
15819
+ } else if (r.url.startsWith(`${this.fileUrl}/X`)) {
15820
+ metadataSummary.X = true;
15821
+ }
15822
+ });
15823
+ return metadataSummary;
15824
+ }
15754
15825
  async setMetadataSummary() {
15755
15826
  if (Object.keys(this.metadataSummary).length > 0) {
15756
15827
  return this.metadataSummary;
15757
15828
  }
15758
- const parseMetadataFile = (metadataFile) => {
15759
- if (!metadataFile.metadata) {
15760
- throw new Error("Could not generate config: .zmetadata file is not valid.");
15761
- }
15762
- const obsmKeys = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obsm/X_")).map((key) => key.split("/.zarray")[0]);
15763
- const obsKeysArr = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obs/")).map((key) => key.split("/.za")[0]);
15764
- function uniq(a) {
15765
- return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
15766
- }
15767
- const obsKeys = uniq(obsKeysArr);
15768
- const X = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("X"));
15769
- const out = {
15770
- obsm: obsmKeys,
15771
- obs: obsKeys,
15772
- X: X.length > 0
15773
- };
15774
- return out;
15775
- };
15776
15829
  const metadataExtension = ".zmetadata";
15777
15830
  const url = [this.fileUrl, metadataExtension].join("/");
15778
15831
  return fetch(url).then((response) => {
15779
15832
  if (response.ok) {
15780
- return response.json();
15833
+ return this.setMetadataSummaryWithZmetadata(response);
15781
15834
  }
15782
- return Promise.reject(response);
15783
- }).then((responseJson) => parseMetadataFile(responseJson)).catch((error) => {
15784
- if (error.status === 404) {
15785
- const errorMssg = `Could not generate config. File ${metadataExtension} not found in supplied file URL. Check docs for more explanation.`;
15786
- return Promise.reject(new Error(errorMssg));
15835
+ if (response.status === 404) {
15836
+ return this.setMetadataSummaryWithoutZmetadata();
15787
15837
  }
15788
- return Promise.reject(error);
15838
+ throw new Error(`Could not generate config: ${response.statusText}`);
15789
15839
  });
15790
15840
  }
15791
15841
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VitessceAutoConfig.d.ts","sourceRoot":"","sources":["../src/VitessceAutoConfig.js"],"names":[],"mappings":"AAqWA,gEAwBC"}
1
+ {"version":3,"file":"VitessceAutoConfig.d.ts","sourceRoot":"","sources":["../src/VitessceAutoConfig.js"],"names":[],"mappings":"AAoaA,gEAwBC"}
@@ -160,46 +160,101 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
160
160
  }
161
161
  return views;
162
162
  }
163
+ async setMetadataSummaryWithZmetadata(response) {
164
+ const metadataFile = await response.json();
165
+ if (!metadataFile.metadata) {
166
+ throw new Error('Could not generate config: .zmetadata file is not valid.');
167
+ }
168
+ const obsmKeys = Object.keys(metadataFile.metadata)
169
+ .filter(key => key.startsWith('obsm/X_'))
170
+ .map(key => key.split('/.zarray')[0]);
171
+ const obsKeysArr = Object.keys(metadataFile.metadata)
172
+ .filter(key => key.startsWith('obs/')).map(key => key.split('/.za')[0]);
173
+ function uniq(a) {
174
+ return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
175
+ }
176
+ const obsKeys = uniq(obsKeysArr);
177
+ const X = Object.keys(metadataFile.metadata).filter(key => key.startsWith('X'));
178
+ return {
179
+ // Array of keys in obsm that are found by the fetches above
180
+ obsm: obsmKeys,
181
+ // Array of keys in obs that are found by the fetches above
182
+ obs: obsKeys,
183
+ // Boolean indicating whether the X array was found by the fetches above
184
+ X: X.length > 0,
185
+ };
186
+ }
187
+ async setMetadataSummaryWithoutZmetadata() {
188
+ const knownMetadataFileSuffixes = [
189
+ '/obsm/X_pca/.zarray',
190
+ '/obsm/X_umap/.zarray',
191
+ '/obsm/X_tsne/.zarray',
192
+ '/obsm/X_spatial/.zarray',
193
+ '/obsm/X_segmentations/.zarray',
194
+ '/obs/.zattrs',
195
+ '/X/.zarray',
196
+ ];
197
+ const getObsmKey = (url) => {
198
+ // Get the substring "X_pca" from a URL like
199
+ // http://example.com/foo/adata.zarr/obsm/X_pca/.zarray
200
+ const obsmKeyStartIndex = `${this.fileUrl}/`.length;
201
+ const obsmKeyEndIndex = url.length - '/.zarray'.length;
202
+ return url.substring(obsmKeyStartIndex, obsmKeyEndIndex);
203
+ };
204
+ const promises = knownMetadataFileSuffixes.map(suffix => fetch(`${this.fileUrl}${suffix}`));
205
+ const fetchResults = await Promise.all(promises);
206
+ const okFetchResults = fetchResults.filter(j => j.ok);
207
+ const metadataSummary = {
208
+ // Array of keys in obsm that are found by the fetches above
209
+ obsm: [],
210
+ // Array of keys in obs that are found by the fetches above
211
+ obs: [],
212
+ // Boolean indicating whether the X array was found by the fetches above
213
+ X: false,
214
+ };
215
+ const obsPromiseResult = okFetchResults.find(r => r.url === (`${this.fileUrl}/obs/.zattrs`));
216
+ const isObsValid = obsAttr => Object.keys(obsAttr).includes('column-order')
217
+ && Object.keys(obsAttr).includes('encoding-version')
218
+ && Object.keys(obsAttr).includes('encoding-type')
219
+ && obsAttr['encoding-type'] === 'dataframe'
220
+ && (obsAttr['encoding-version'] === '0.1.0' || obsAttr['encoding-version'] === '0.2.0');
221
+ if (obsPromiseResult) {
222
+ const obsAttrs = await obsPromiseResult.json();
223
+ if (isObsValid(obsAttrs)) {
224
+ obsAttrs['column-order'].forEach(key => metadataSummary.obs.push(`obs/${key}`));
225
+ }
226
+ else {
227
+ throw new Error('Could not generate config: /obs/.zattrs file is not valid.');
228
+ }
229
+ }
230
+ okFetchResults
231
+ .forEach((r) => {
232
+ if (r.url.startsWith(`${this.fileUrl}/obsm`)) {
233
+ const obsmKey = getObsmKey(r.url);
234
+ if (obsmKey) {
235
+ metadataSummary.obsm.push(obsmKey);
236
+ }
237
+ }
238
+ else if (r.url.startsWith(`${this.fileUrl}/X`)) {
239
+ metadataSummary.X = true;
240
+ }
241
+ });
242
+ return metadataSummary;
243
+ }
163
244
  async setMetadataSummary() {
164
245
  if (Object.keys(this.metadataSummary).length > 0) {
165
246
  return this.metadataSummary;
166
247
  }
167
- const parseMetadataFile = (metadataFile) => {
168
- if (!metadataFile.metadata) {
169
- throw new Error('Could not generate config: .zmetadata file is not valid.');
170
- }
171
- const obsmKeys = Object.keys(metadataFile.metadata)
172
- .filter(key => key.startsWith('obsm/X_'))
173
- .map(key => key.split('/.zarray')[0]);
174
- const obsKeysArr = Object.keys(metadataFile.metadata)
175
- .filter(key => key.startsWith('obs/')).map(key => key.split('/.za')[0]);
176
- function uniq(a) {
177
- return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
178
- }
179
- const obsKeys = uniq(obsKeysArr);
180
- const X = Object.keys(metadataFile.metadata).filter(key => key.startsWith('X'));
181
- const out = {
182
- obsm: obsmKeys,
183
- obs: obsKeys,
184
- X: X.length > 0,
185
- };
186
- return out;
187
- };
188
248
  const metadataExtension = '.zmetadata';
189
249
  const url = [this.fileUrl, metadataExtension].join('/');
190
250
  return fetch(url).then((response) => {
191
251
  if (response.ok) {
192
- return response.json();
252
+ return this.setMetadataSummaryWithZmetadata(response);
193
253
  }
194
- return Promise.reject(response);
195
- })
196
- .then(responseJson => parseMetadataFile(responseJson))
197
- .catch((error) => {
198
- if (error.status === 404) {
199
- const errorMssg = `Could not generate config. File ${metadataExtension} not found in supplied file URL. Check docs for more explanation.`;
200
- return Promise.reject(new Error(errorMssg));
254
+ if (response.status === 404) {
255
+ return this.setMetadataSummaryWithoutZmetadata();
201
256
  }
202
- return Promise.reject(error);
257
+ throw new Error(`Could not generate config: ${response.statusText}`);
203
258
  });
204
259
  }
205
260
  }
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import { generateConfigs } from './VitessceAutoConfig.js';
2
3
  describe('src/VitessceAutoConfig.js', () => {
3
4
  it('generates config for OME-TIFF file correctly', async () => {
@@ -150,7 +151,7 @@ describe('src/VitessceAutoConfig.js', () => {
150
151
  expect(config).toEqual(expectedConfig);
151
152
  });
152
153
  it('generates config for Anndata-ZARR file correctly', async () => {
153
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/.anndata.zarr'];
154
+ const urls = ['http://localhost:4204/@fixtures/zarr/partials/.anndata.zarr'];
154
155
  const expectedName = urls[0].split('/').at(-1);
155
156
  const expectedConfig = {
156
157
  version: '1.0.15',
@@ -258,7 +259,7 @@ describe('src/VitessceAutoConfig.js', () => {
258
259
  expect(config).toEqual(expectedConfig);
259
260
  });
260
261
  it('generates empty config for Anndata-ZARR file with empty .zmetadata', async () => {
261
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/emptymeta.h5ad.zarr'];
262
+ const urls = ['http://localhost:4204/@fixtures/zarr/partials/emptymeta.h5ad.zarr'];
262
263
  const expectedName = urls[0].split('/').at(-1);
263
264
  const expectedConfig = {
264
265
  version: '1.0.15',
@@ -299,8 +300,13 @@ describe('src/VitessceAutoConfig.js', () => {
299
300
  expect(config).toEqual(expectedConfig);
300
301
  });
301
302
  it('raises an error for Anndata-ZARR file with misconfigured .zmetadata', async () => {
302
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/invalidmeta.adata.zarr'];
303
- await generateConfigs(urls).catch(e => expect(e.message).toContain('Could not generate config: .zmetadata file is not valid.'));
303
+ const urls = ['http://localhost:4204/@fixtures/zarr/partials/invalidmeta.adata.zarr'];
304
+ // References:
305
+ // - https://vitest.dev/api/expect.html#tothrowerror
306
+ // - https://vitest.dev/api/expect.html#rejects
307
+ await expect(() => generateConfigs(urls))
308
+ .rejects
309
+ .toThrowError('Could not generate config: .zmetadata file is not valid.');
304
310
  });
305
311
  it('generates config for multiple files correctly', async () => {
306
312
  const urls = ['somefile.ome.tif', 'anoterfile.ome.zarr'];
@@ -431,12 +437,77 @@ describe('src/VitessceAutoConfig.js', () => {
431
437
  const config = await generateConfigs(urls);
432
438
  expect(config).toEqual(expectedConfig);
433
439
  });
434
- it('raises an Error when .zmetadata file not present in folder', async () => {
435
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/invalid.adata.zarr'];
436
- await generateConfigs(urls).catch(e => expect(e.message).toContain('Could not generate config. File .zmetadata not found in supplied file URL'));
440
+ it('Does the parsing when .zmetadata file not present in folder', async () => {
441
+ const urls = ['http://localhost:4204/@fixtures/zarr/anndata-0.8/anndata-csr.adata.zarr'];
442
+ const expectedName = urls[0].split('/').at(-1);
443
+ const expectedConfig = {
444
+ version: '1.0.15',
445
+ name: 'An automatically generated config. Adjust values and add layout components if needed.',
446
+ description: 'Populate with text relevant to this visualisation.',
447
+ datasets: [
448
+ {
449
+ uid: 'A',
450
+ name: expectedName,
451
+ files: [
452
+ {
453
+ url: urls[0],
454
+ fileType: 'anndata.zarr',
455
+ coordinationValues: {
456
+ obsType: 'cell',
457
+ featureType: 'gene',
458
+ featureValueType: 'expression',
459
+ },
460
+ options: {
461
+ obsEmbedding: [
462
+ {
463
+ path: 'obsm/X_umap',
464
+ embeddingType: 'UMAP',
465
+ },
466
+ ],
467
+ obsFeatureMatrix: {
468
+ path: 'X',
469
+ },
470
+ obsSets: [
471
+ {
472
+ name: 'Cell Type',
473
+ path: [
474
+ 'obs/leiden',
475
+ ],
476
+ },
477
+ ],
478
+ },
479
+ },
480
+ ],
481
+ },
482
+ ],
483
+ coordinationSpace: {
484
+ dataset: {
485
+ A: 'A',
486
+ },
487
+ embeddingType: {
488
+ A: 'UMAP',
489
+ },
490
+ },
491
+ layout: [
492
+ {
493
+ component: 'scatterplot',
494
+ coordinationScopes: {
495
+ dataset: 'A',
496
+ embeddingType: 'A',
497
+ },
498
+ x: 0,
499
+ y: 0,
500
+ w: 12,
501
+ h: 12,
502
+ },
503
+ ],
504
+ initStrategy: 'auto',
505
+ };
506
+ const config = await generateConfigs(urls);
507
+ expect(config).toEqual(expectedConfig);
437
508
  });
438
509
  it('raises an error when URL with unsupported file format is passed', async () => {
439
- const urls = ['http://localhost:51204/@fixtures/zarr/anndata-0.7/somefile.zarr'];
510
+ const urls = ['http://localhost:4204/@fixtures/zarr/anndata-0.7/somefile.zarr'];
440
511
  await generateConfigs(urls).catch(e => expect(e.message).toContain('This file type is not supported.'));
441
512
  });
442
513
  });
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import { CoordinationType } from '@vitessce/constants-internal';
2
3
  import { VitessceConfig, hconcat, vconcat, } from './VitessceConfig.js';
3
4
  describe('src/api/VitessceConfig.js', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/config",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -16,12 +16,12 @@
16
16
  "dist-tsc"
17
17
  ],
18
18
  "dependencies": {
19
- "@vitessce/constants-internal": "3.0.0",
20
- "@vitessce/utils": "3.0.0"
19
+ "@vitessce/constants-internal": "3.0.1",
20
+ "@vitessce/utils": "3.0.1"
21
21
  },
22
22
  "scripts": {
23
23
  "bundle": "pnpm exec vite build -c ../../scripts/vite.config.js",
24
- "test": "pnpm exec vitest --run -r ../../ --dir ."
24
+ "test": "pnpm exec vitest --run"
25
25
  },
26
26
  "module": "dist/index.js",
27
27
  "exports": {
@@ -185,55 +185,118 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
185
185
  return views;
186
186
  }
187
187
 
188
- async setMetadataSummary() {
189
- if (Object.keys(this.metadataSummary).length > 0) {
190
- return this.metadataSummary;
188
+ async setMetadataSummaryWithZmetadata(response) { /* eslint-disable-line class-methods-use-this */
189
+ const metadataFile = await response.json();
190
+ if (!metadataFile.metadata) {
191
+ throw new Error('Could not generate config: .zmetadata file is not valid.');
191
192
  }
192
193
 
193
- const parseMetadataFile = (metadataFile) => {
194
- if (!metadataFile.metadata) {
195
- throw new Error('Could not generate config: .zmetadata file is not valid.');
196
- }
194
+ const obsmKeys = Object.keys(metadataFile.metadata)
195
+ .filter(key => key.startsWith('obsm/X_'))
196
+ .map(key => key.split('/.zarray')[0]);
197
197
 
198
- const obsmKeys = Object.keys(metadataFile.metadata)
199
- .filter(key => key.startsWith('obsm/X_'))
200
- .map(key => key.split('/.zarray')[0]);
198
+ const obsKeysArr = Object.keys(metadataFile.metadata)
199
+ .filter(key => key.startsWith('obs/')).map(key => key.split('/.za')[0]);
201
200
 
202
- const obsKeysArr = Object.keys(metadataFile.metadata)
203
- .filter(key => key.startsWith('obs/')).map(key => key.split('/.za')[0]);
201
+ function uniq(a) {
202
+ return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
203
+ }
204
+ const obsKeys = uniq(obsKeysArr);
204
205
 
205
- function uniq(a) {
206
- return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
207
- }
208
- const obsKeys = uniq(obsKeysArr);
206
+ const X = Object.keys(metadataFile.metadata).filter(key => key.startsWith('X'));
207
+
208
+ return {
209
+ // Array of keys in obsm that are found by the fetches above
210
+ obsm: obsmKeys,
211
+ // Array of keys in obs that are found by the fetches above
212
+ obs: obsKeys,
213
+ // Boolean indicating whether the X array was found by the fetches above
214
+ X: X.length > 0,
215
+ };
216
+ }
209
217
 
210
- const X = Object.keys(metadataFile.metadata).filter(key => key.startsWith('X'));
218
+ async setMetadataSummaryWithoutZmetadata() {
219
+ const knownMetadataFileSuffixes = [
220
+ '/obsm/X_pca/.zarray',
221
+ '/obsm/X_umap/.zarray',
222
+ '/obsm/X_tsne/.zarray',
223
+ '/obsm/X_spatial/.zarray',
224
+ '/obsm/X_segmentations/.zarray',
225
+ '/obs/.zattrs',
226
+ '/X/.zarray',
227
+ ];
211
228
 
212
- const out = {
213
- obsm: obsmKeys,
214
- obs: obsKeys,
215
- X: X.length > 0,
216
- };
229
+ const getObsmKey = (url) => {
230
+ // Get the substring "X_pca" from a URL like
231
+ // http://example.com/foo/adata.zarr/obsm/X_pca/.zarray
232
+ const obsmKeyStartIndex = `${this.fileUrl}/`.length;
233
+ const obsmKeyEndIndex = url.length - '/.zarray'.length;
234
+ return url.substring(obsmKeyStartIndex, obsmKeyEndIndex);
235
+ };
217
236
 
218
- return out;
237
+ const promises = knownMetadataFileSuffixes.map(suffix => fetch(`${this.fileUrl}${suffix}`));
238
+
239
+ const fetchResults = await Promise.all(promises);
240
+ const okFetchResults = fetchResults.filter(j => j.ok);
241
+ const metadataSummary = {
242
+ // Array of keys in obsm that are found by the fetches above
243
+ obsm: [],
244
+ // Array of keys in obs that are found by the fetches above
245
+ obs: [],
246
+ // Boolean indicating whether the X array was found by the fetches above
247
+ X: false,
219
248
  };
220
249
 
250
+ const obsPromiseResult = okFetchResults.find(
251
+ r => r.url === (`${this.fileUrl}/obs/.zattrs`),
252
+ );
253
+
254
+ const isObsValid = obsAttr => Object.keys(obsAttr).includes('column-order')
255
+ && Object.keys(obsAttr).includes('encoding-version')
256
+ && Object.keys(obsAttr).includes('encoding-type')
257
+ && obsAttr['encoding-type'] === 'dataframe'
258
+ && (obsAttr['encoding-version'] === '0.1.0' || obsAttr['encoding-version'] === '0.2.0');
259
+
260
+ if (obsPromiseResult) {
261
+ const obsAttrs = await obsPromiseResult.json();
262
+ if (isObsValid(obsAttrs)) {
263
+ obsAttrs['column-order'].forEach(key => metadataSummary.obs.push(`obs/${key}`));
264
+ } else {
265
+ throw new Error('Could not generate config: /obs/.zattrs file is not valid.');
266
+ }
267
+ }
268
+
269
+ okFetchResults
270
+ .forEach((r) => {
271
+ if (r.url.startsWith(`${this.fileUrl}/obsm`)) {
272
+ const obsmKey = getObsmKey(r.url);
273
+ if (obsmKey) {
274
+ metadataSummary.obsm.push(obsmKey);
275
+ }
276
+ } else if (r.url.startsWith(`${this.fileUrl}/X`)) {
277
+ metadataSummary.X = true;
278
+ }
279
+ });
280
+
281
+ return metadataSummary;
282
+ }
283
+
284
+ async setMetadataSummary() {
285
+ if (Object.keys(this.metadataSummary).length > 0) {
286
+ return this.metadataSummary;
287
+ }
288
+
221
289
  const metadataExtension = '.zmetadata';
222
290
  const url = [this.fileUrl, metadataExtension].join('/');
223
291
  return fetch(url).then((response) => {
224
292
  if (response.ok) {
225
- return response.json();
293
+ return this.setMetadataSummaryWithZmetadata(response);
226
294
  }
227
- return Promise.reject(response);
228
- })
229
- .then(responseJson => parseMetadataFile(responseJson))
230
- .catch((error) => {
231
- if (error.status === 404) {
232
- const errorMssg = `Could not generate config. File ${metadataExtension} not found in supplied file URL. Check docs for more explanation.`;
233
- return Promise.reject(new Error(errorMssg));
234
- }
235
- return Promise.reject(error);
236
- });
295
+ if (response.status === 404) {
296
+ return this.setMetadataSummaryWithoutZmetadata();
297
+ }
298
+ throw new Error(`Could not generate config: ${response.statusText}`);
299
+ });
237
300
  }
238
301
  }
239
302
 
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import { generateConfigs } from './VitessceAutoConfig.js';
2
3
 
3
4
  describe('src/VitessceAutoConfig.js', () => {
@@ -155,7 +156,7 @@ describe('src/VitessceAutoConfig.js', () => {
155
156
  });
156
157
 
157
158
  it('generates config for Anndata-ZARR file correctly', async () => {
158
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/.anndata.zarr'];
159
+ const urls = ['http://localhost:4204/@fixtures/zarr/partials/.anndata.zarr'];
159
160
  const expectedName = urls[0].split('/').at(-1);
160
161
  const expectedConfig = {
161
162
  version: '1.0.15',
@@ -264,7 +265,7 @@ describe('src/VitessceAutoConfig.js', () => {
264
265
  });
265
266
 
266
267
  it('generates empty config for Anndata-ZARR file with empty .zmetadata', async () => {
267
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/emptymeta.h5ad.zarr'];
268
+ const urls = ['http://localhost:4204/@fixtures/zarr/partials/emptymeta.h5ad.zarr'];
268
269
  const expectedName = urls[0].split('/').at(-1);
269
270
  const expectedConfig = {
270
271
  version: '1.0.15',
@@ -307,11 +308,14 @@ describe('src/VitessceAutoConfig.js', () => {
307
308
  });
308
309
 
309
310
  it('raises an error for Anndata-ZARR file with misconfigured .zmetadata', async () => {
310
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/invalidmeta.adata.zarr'];
311
+ const urls = ['http://localhost:4204/@fixtures/zarr/partials/invalidmeta.adata.zarr'];
311
312
 
312
- await generateConfigs(urls).catch(
313
- e => expect(e.message).toContain('Could not generate config: .zmetadata file is not valid.'),
314
- );
313
+ // References:
314
+ // - https://vitest.dev/api/expect.html#tothrowerror
315
+ // - https://vitest.dev/api/expect.html#rejects
316
+ await expect(() => generateConfigs(urls))
317
+ .rejects
318
+ .toThrowError('Could not generate config: .zmetadata file is not valid.');
315
319
  });
316
320
 
317
321
  it('generates config for multiple files correctly', async () => {
@@ -447,16 +451,79 @@ describe('src/VitessceAutoConfig.js', () => {
447
451
  });
448
452
 
449
453
 
450
- it('raises an Error when .zmetadata file not present in folder', async () => {
451
- const urls = ['http://localhost:51204/@fixtures/zarr/partials/invalid.adata.zarr'];
454
+ it('Does the parsing when .zmetadata file not present in folder', async () => {
455
+ const urls = ['http://localhost:4204/@fixtures/zarr/anndata-0.8/anndata-csr.adata.zarr'];
456
+ const expectedName = urls[0].split('/').at(-1);
457
+ const expectedConfig = {
458
+ version: '1.0.15',
459
+ name: 'An automatically generated config. Adjust values and add layout components if needed.',
460
+ description: 'Populate with text relevant to this visualisation.',
461
+ datasets: [
462
+ {
463
+ uid: 'A',
464
+ name: expectedName,
465
+ files: [
466
+ {
467
+ url: urls[0],
468
+ fileType: 'anndata.zarr',
469
+ coordinationValues: {
470
+ obsType: 'cell',
471
+ featureType: 'gene',
472
+ featureValueType: 'expression',
473
+ },
474
+ options: {
475
+ obsEmbedding: [
476
+ {
477
+ path: 'obsm/X_umap',
478
+ embeddingType: 'UMAP',
479
+ },
480
+ ],
481
+ obsFeatureMatrix: {
482
+ path: 'X',
483
+ },
484
+ obsSets: [
485
+ {
486
+ name: 'Cell Type',
487
+ path: [
488
+ 'obs/leiden',
489
+ ],
490
+ },
491
+ ],
492
+ },
493
+ },
494
+ ],
495
+ },
496
+ ],
497
+ coordinationSpace: {
498
+ dataset: {
499
+ A: 'A',
500
+ },
501
+ embeddingType: {
502
+ A: 'UMAP',
503
+ },
504
+ },
505
+ layout: [
506
+ {
507
+ component: 'scatterplot',
508
+ coordinationScopes: {
509
+ dataset: 'A',
510
+ embeddingType: 'A',
511
+ },
512
+ x: 0,
513
+ y: 0,
514
+ w: 12,
515
+ h: 12,
516
+ },
517
+ ],
518
+ initStrategy: 'auto',
519
+ };
452
520
 
453
- await generateConfigs(urls).catch(
454
- e => expect(e.message).toContain('Could not generate config. File .zmetadata not found in supplied file URL'),
455
- );
521
+ const config = await generateConfigs(urls);
522
+ expect(config).toEqual(expectedConfig);
456
523
  });
457
524
 
458
525
  it('raises an error when URL with unsupported file format is passed', async () => {
459
- const urls = ['http://localhost:51204/@fixtures/zarr/anndata-0.7/somefile.zarr'];
526
+ const urls = ['http://localhost:4204/@fixtures/zarr/anndata-0.7/somefile.zarr'];
460
527
 
461
528
  await generateConfigs(urls).catch(
462
529
  e => expect(e.message).toContain('This file type is not supported.'),
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import { CoordinationType } from '@vitessce/constants-internal';
2
3
  import {
3
4
  VitessceConfig,