@apollo-annotation/shared 0.3.6 → 0.3.8

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 (94) hide show
  1. package/dist/Changes/AddAssemblyAliasesChange.d.ts +16 -0
  2. package/dist/Changes/AddAssemblyAliasesChange.js +50 -0
  3. package/dist/Changes/AddAssemblyAliasesChange.js.map +1 -0
  4. package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js +1 -1
  5. package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js.map +1 -1
  6. package/dist/Changes/AddAssemblyFromFileChange.js +1 -1
  7. package/dist/Changes/AddAssemblyFromFileChange.js.map +1 -1
  8. package/dist/Changes/AddFeatureChange.d.ts +1 -0
  9. package/dist/Changes/AddFeatureChange.js +5 -25
  10. package/dist/Changes/AddFeatureChange.js.map +1 -1
  11. package/dist/Changes/AddRefSeqAliasesChange.js +2 -4
  12. package/dist/Changes/AddRefSeqAliasesChange.js.map +1 -1
  13. package/dist/Changes/DeleteFeatureChange.d.ts +9 -8
  14. package/dist/Changes/DeleteFeatureChange.js +37 -32
  15. package/dist/Changes/DeleteFeatureChange.js.map +1 -1
  16. package/dist/Changes/FeatureAttributeChange.d.ts +2 -1
  17. package/dist/Changes/FeatureAttributeChange.js +14 -6
  18. package/dist/Changes/FeatureAttributeChange.js.map +1 -1
  19. package/dist/Changes/ImportJBrowseConfigChange.d.ts +3 -0
  20. package/dist/Changes/ImportJBrowseConfigChange.js +17 -13
  21. package/dist/Changes/ImportJBrowseConfigChange.js.map +1 -1
  22. package/dist/Changes/LocationEndChange.js +40 -37
  23. package/dist/Changes/LocationEndChange.js.map +1 -1
  24. package/dist/Changes/LocationStartChange.js +40 -37
  25. package/dist/Changes/LocationStartChange.js.map +1 -1
  26. package/dist/Changes/MergeExonsChange.d.ts +29 -0
  27. package/dist/Changes/MergeExonsChange.js +114 -0
  28. package/dist/Changes/MergeExonsChange.js.map +1 -0
  29. package/dist/Changes/MergeTranscriptsChange.d.ts +34 -0
  30. package/dist/Changes/MergeTranscriptsChange.js +245 -0
  31. package/dist/Changes/MergeTranscriptsChange.js.map +1 -0
  32. package/dist/Changes/SplitExonChange.d.ts +33 -0
  33. package/dist/Changes/SplitExonChange.js +142 -0
  34. package/dist/Changes/SplitExonChange.js.map +1 -0
  35. package/dist/Changes/UndoMergeExonsChange.d.ts +27 -0
  36. package/dist/Changes/UndoMergeExonsChange.js +104 -0
  37. package/dist/Changes/UndoMergeExonsChange.js.map +1 -0
  38. package/dist/Changes/UndoMergeTranscriptsChange.d.ts +27 -0
  39. package/dist/Changes/UndoMergeTranscriptsChange.js +104 -0
  40. package/dist/Changes/UndoMergeTranscriptsChange.js.map +1 -0
  41. package/dist/Changes/UndoSplitExonChange.d.ts +32 -0
  42. package/dist/Changes/UndoSplitExonChange.js +111 -0
  43. package/dist/Changes/UndoSplitExonChange.js.map +1 -0
  44. package/dist/Changes/index.d.ts +21 -0
  45. package/dist/Changes/index.js +21 -0
  46. package/dist/Changes/index.js.map +1 -1
  47. package/dist/Checks/CDSCheck.d.ts +1 -1
  48. package/dist/Checks/CDSCheck.js +8 -9
  49. package/dist/Checks/CDSCheck.js.map +1 -1
  50. package/dist/Checks/TranscriptCheck.d.ts +9 -0
  51. package/dist/Checks/TranscriptCheck.js +109 -0
  52. package/dist/Checks/TranscriptCheck.js.map +1 -0
  53. package/dist/Checks/index.d.ts +1 -0
  54. package/dist/Checks/index.js +1 -0
  55. package/dist/Checks/index.js.map +1 -1
  56. package/dist/GFF3/annotationFeatureToGFF3.js +7 -1
  57. package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -1
  58. package/dist/GFF3/gff3ToAnnotationFeature.test.js +1 -1
  59. package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
  60. package/dist/Validations/ParentChildValidation.js +3 -1
  61. package/dist/Validations/ParentChildValidation.js.map +1 -1
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/dist/util.d.ts +5 -0
  64. package/dist/util.js +46 -0
  65. package/dist/util.js.map +1 -1
  66. package/package.json +7 -7
  67. package/src/Changes/AddAssemblyAliasesChange.ts +69 -0
  68. package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +2 -1
  69. package/src/Changes/AddAssemblyFromFileChange.ts +1 -1
  70. package/src/Changes/AddFeatureChange.ts +5 -29
  71. package/src/Changes/AddRefSeqAliasesChange.ts +2 -4
  72. package/src/Changes/DeleteFeatureChange.ts +42 -36
  73. package/src/Changes/FeatureAttributeChange.ts +16 -7
  74. package/src/Changes/ImportJBrowseConfigChange.ts +33 -18
  75. package/src/Changes/LocationEndChange.ts +41 -50
  76. package/src/Changes/LocationStartChange.ts +41 -50
  77. package/src/Changes/MergeExonsChange.ts +201 -0
  78. package/src/Changes/MergeTranscriptsChange.ts +394 -0
  79. package/src/Changes/SplitExonChange.ts +249 -0
  80. package/src/Changes/UndoMergeExonsChange.ts +149 -0
  81. package/src/Changes/UndoMergeTranscriptsChange.ts +153 -0
  82. package/src/Changes/UndoSplitExonChange.ts +174 -0
  83. package/src/Changes/index.ts +21 -0
  84. package/src/Checks/CDSCheck.ts +10 -10
  85. package/src/Checks/TranscriptCheck.ts +139 -0
  86. package/src/Checks/index.ts +1 -0
  87. package/src/GFF3/annotationFeatureToGFF3.ts +7 -1
  88. package/src/GFF3/gff3ToAnnotationFeature.test.ts +1 -1
  89. package/src/Validations/ParentChildValidation.ts +3 -2
  90. package/src/util.ts +53 -0
  91. package/test_data/example01.json +20 -20
  92. package/test_data/example02.json +38 -38
  93. package/test_data/example04.json +57 -57
  94. package/test_data/gene_representations.gff3 +1054 -276
package/dist/util.d.ts CHANGED
@@ -1 +1,6 @@
1
+ import { type Feature } from '@apollo-annotation/schemas';
2
+ import { type IKeyValueMap } from 'mobx';
1
3
  export declare function splitStringIntoChunks(input: string, chunkSize: number): string[];
4
+ export declare function getPrintableId(feature: Feature): string;
5
+ export declare function attributesToRecords(attributes: IKeyValueMap<readonly string[] | undefined> | undefined): Record<string, string[] | undefined>;
6
+ export declare function stringifyAttributes(attributes: Record<string, string[] | undefined> | undefined): string;
package/dist/util.js CHANGED
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.splitStringIntoChunks = splitStringIntoChunks;
4
+ exports.getPrintableId = getPrintableId;
5
+ exports.attributesToRecords = attributesToRecords;
6
+ exports.stringifyAttributes = stringifyAttributes;
4
7
  function splitStringIntoChunks(input, chunkSize) {
5
8
  const chunks = [];
6
9
  for (let i = 0; i < input.length; i += chunkSize) {
@@ -9,4 +12,47 @@ function splitStringIntoChunks(input, chunkSize) {
9
12
  }
10
13
  return chunks;
11
14
  }
15
+ function getPrintableId(feature) {
16
+ const ff = feature;
17
+ const gff_id = ff.attributes.get('gff_id')?.join(',');
18
+ if (gff_id) {
19
+ return `ID=${gff_id} (_id: ${feature._id.toString()})`;
20
+ }
21
+ const gff_name = ff.attributes.get('gff_name')?.join(',');
22
+ if (gff_name) {
23
+ return `Name=${gff_name} (_id: ${feature._id.toString()})`;
24
+ }
25
+ return `_id: ${feature._id.toString()}`;
26
+ }
27
+ function attributesToRecords(attributes) {
28
+ const records = {};
29
+ if (!attributes) {
30
+ return records;
31
+ }
32
+ for (const [key, value] of Object.entries(attributes)) {
33
+ records[key] = value?.slice();
34
+ }
35
+ return records;
36
+ }
37
+ function stringifyAttributes(attributes) {
38
+ if (!attributes) {
39
+ return '';
40
+ }
41
+ const str = [];
42
+ for (const [key, value] of Object.entries(attributes)) {
43
+ let attributeName = key;
44
+ if (attributeName.startsWith('gff_')) {
45
+ attributeName = attributeName.slice(4);
46
+ attributeName =
47
+ attributeName.charAt(0).toUpperCase() + attributeName.slice(1);
48
+ }
49
+ if (value) {
50
+ str.push(`${attributeName}=${value.join(',')}`);
51
+ }
52
+ else {
53
+ str.push(attributeName);
54
+ }
55
+ }
56
+ return encodeURIComponent(str.join(';'));
57
+ }
12
58
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAAA,sDAUC;AAVD,SAAgB,qBAAqB,CACnC,KAAa,EACb,SAAiB;IAEjB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAIA,sDAUC;AAED,wCAWC;AAED,kDAWC;AAED,kDAqBC;AA3DD,SAAgB,qBAAqB,CACnC,KAAa,EACb,SAAiB;IAEjB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAgB,cAAc,CAAC,OAAgB;IAC7C,MAAM,EAAE,GAAG,OAAuC,CAAA;IAClD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,MAAM,UAAU,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAA;IACxD,CAAC;IACD,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACzD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,QAAQ,UAAU,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAA;IAC5D,CAAC;IACD,OAAO,QAAQ,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAA;AACzC,CAAC;AAED,SAAgB,mBAAmB,CACjC,UAAmE;IAEnE,MAAM,OAAO,GAAyC,EAAE,CAAA;IACxD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,CAAA;IAC/B,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAgB,mBAAmB,CACjC,UAA4D;IAE5D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,CAAA;IACd,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,IAAI,aAAa,GAAG,GAAG,CAAA;QACvB,IAAI,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACtC,aAAa;gBACX,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAClE,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,GAAG,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjD,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IACD,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo-annotation/shared",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "main": "./dist/index.js",
5
5
  "scripts": {
6
6
  "build": "yarn clean && tsc --build",
@@ -10,12 +10,12 @@
10
10
  "test:ci": "NODE_V8_COVERAGE=./coverage glob -c \"tsx --test --test-reporter spec --experimental-test-coverage \" \"**/*.test.ts\""
11
11
  },
12
12
  "dependencies": {
13
- "@apollo-annotation/common": "^0.3.6",
14
- "@apollo-annotation/mst": "^0.3.6",
15
- "@apollo-annotation/schemas": "^0.3.6",
13
+ "@apollo-annotation/common": "^0.3.8",
14
+ "@apollo-annotation/mst": "^0.3.8",
15
+ "@apollo-annotation/schemas": "^0.3.8",
16
16
  "@gmod/gff": "1.2.0",
17
17
  "@gmod/indexedfasta": "^2.0.4",
18
- "@jbrowse/core": "^3.0.1",
18
+ "@jbrowse/core": "^3.6.5",
19
19
  "bson-objectid": "^2.0.4",
20
20
  "generic-filehandle": "^3.0.0",
21
21
  "jwt-decode": "^3.1.2",
@@ -39,8 +39,8 @@
39
39
  "typescript": "^5.5.3"
40
40
  },
41
41
  "peerDependencies": {
42
- "@mui/material": "^6.0.0",
43
- "@mui/x-data-grid": "^7.0.0",
42
+ "@mui/material": "^7.0.0",
43
+ "@mui/x-data-grid": "^8.0.0",
44
44
  "mobx": "^6.6.1",
45
45
  "mobx-react": "^7.2.1",
46
46
  "mobx-state-tree": "^5.4.0",
@@ -0,0 +1,69 @@
1
+ import {
2
+ AssemblySpecificChange,
3
+ type Change,
4
+ type ChangeOptions,
5
+ type ClientDataStore,
6
+ type LocalGFF3DataStore,
7
+ type SerializedAssemblySpecificChange,
8
+ type ServerDataStore,
9
+ } from '@apollo-annotation/common'
10
+ import { getSession } from '@jbrowse/core/util'
11
+
12
+ export interface SerializedAssemblyAliasesChange
13
+ extends SerializedAssemblySpecificChange {
14
+ typeName: 'AddAssemblyAliasesChange'
15
+ aliases: string[]
16
+ }
17
+
18
+ export class AddAssemblyAliasesChange extends AssemblySpecificChange {
19
+ typeName = 'AddAssemblyAliasesChange' as const
20
+ aliases: string[]
21
+
22
+ constructor(json: SerializedAssemblyAliasesChange, options?: ChangeOptions) {
23
+ super(json, options)
24
+ this.aliases = json.aliases
25
+ }
26
+
27
+ executeOnClient(clientDataStore: ClientDataStore) {
28
+ const { assemblyManager } = getSession(clientDataStore)
29
+ const assembly = assemblyManager.get(this.assembly)
30
+ if (!assembly) {
31
+ throw new Error(`assembly ${this.assembly} not found`)
32
+ }
33
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
34
+ assembly.configuration.aliases.set(this.aliases)
35
+ return Promise.resolve()
36
+ }
37
+
38
+ getInverse(): Change {
39
+ throw new Error('Method not implemented.')
40
+ }
41
+
42
+ toJSON(): SerializedAssemblyAliasesChange {
43
+ const { assembly, aliases, typeName } = this
44
+ return { assembly, typeName, aliases }
45
+ }
46
+
47
+ async executeOnServer(backend: ServerDataStore) {
48
+ const { assemblyModel } = backend
49
+ const { assembly, logger, aliases } = this
50
+ logger.debug?.(
51
+ `Updating assembly aliases for assembly: ${assembly}, aliases: ${JSON.stringify(aliases)}`,
52
+ )
53
+ const asm = await assemblyModel.findById(assembly)
54
+ if (!asm) {
55
+ throw new Error(`Assembly with ID ${assembly} not found`)
56
+ }
57
+ asm.aliases = aliases
58
+ await asm.save()
59
+ }
60
+
61
+ executeOnLocalGFF3(_backend: LocalGFF3DataStore): Promise<unknown> {
62
+ throw new Error('Method not implemented.')
63
+ }
64
+
65
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
66
+ get notification(): string {
67
+ return 'Assembly aliases have been added.'
68
+ }
69
+ }
@@ -90,7 +90,8 @@ export class AddAssemblyAndFeaturesFromFileChange extends FromFileBaseChange {
90
90
  throw new Error(`Assembly "${assemblyName}" already exists`)
91
91
  }
92
92
  // get checks
93
- const checkDocs = await checkModel.find({ default: true }).exec()
93
+ const checkDocs = await checkModel.find({ isDefault: true }).exec()
94
+
94
95
  const checks = checkDocs.map((checkDoc) => checkDoc._id.toHexString())
95
96
  // Add assembly
96
97
  const [newAssemblyDoc] = await assemblyModel.create([
@@ -128,7 +128,7 @@ export class AddAssemblyFromFileChange extends FromFileBaseChange {
128
128
  if (assemblyDoc) {
129
129
  throw new Error(`Assembly "${assemblyName}" already exists`)
130
130
  }
131
- const checkDocs = await checkModel.find({ default: true }).exec()
131
+ const checkDocs = await checkModel.find({ isDefault: true }).exec()
132
132
  const checks = checkDocs.map((checkDoc) => checkDoc._id.toHexString())
133
133
  const [newAssemblyDoc] = await assemblyModel.create([
134
134
  {
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable @typescript-eslint/restrict-template-expressions */
2
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
2
  /* eslint-disable @typescript-eslint/require-await */
4
3
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
5
4
  import {
@@ -46,6 +45,10 @@ export class AddFeatureChange extends FeatureChange {
46
45
  super(json, options)
47
46
  this.changes = 'changes' in json ? json.changes : [json]
48
47
  }
48
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
49
+ get notification() {
50
+ return 'Feature added successfully'
51
+ }
49
52
 
50
53
  toJSON(): SerializedAddFeatureChange {
51
54
  const { assembly, changedIds, changes, typeName } = this
@@ -133,34 +136,7 @@ export class AddFeatureChange extends FeatureChange {
133
136
  `Could not find feature with ID "${parentFeatureId}" in feature "${topLevelFeature._id}"`,
134
137
  )
135
138
  }
136
- if (!parentFeature.children) {
137
- parentFeature.children = new Map()
138
- }
139
- if (!parentFeature.attributes?._id) {
140
- let { attributes } = parentFeature
141
- if (!attributes) {
142
- attributes = {}
143
- }
144
- attributes = {
145
- _id: [parentFeature._id.toString()],
146
- // eslint-disable-next-line unicorn/prefer-structured-clone
147
- ...JSON.parse(JSON.stringify(attributes)),
148
- }
149
- parentFeature.attributes = attributes
150
- }
151
- parentFeature.children.set(_id, {
152
- allIds: [],
153
- ...addedFeature,
154
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
155
- // @ts-expect-error
156
- _id,
157
- })
158
- // Child features should be sorted for click and drag of gene glyphs to work properly
159
- parentFeature.children = new Map(
160
- [...parentFeature.children.entries()].sort(
161
- (a, b) => a[1].min - b[1].min,
162
- ),
163
- )
139
+ this.addChild(parentFeature, addedFeature)
164
140
  const childIds = this.getChildFeatureIds(addedFeature)
165
141
  topLevelFeature.allIds.push(_id, ...childIds)
166
142
  await topLevelFeature.save()
@@ -36,9 +36,8 @@ export class AddRefSeqAliasesChange extends AssemblySpecificChange {
36
36
  throw new Error(`assembly ${this.assembly} not found`)
37
37
  }
38
38
  const sessionAliases = assembly.refNameAliases
39
- const sessionLCAliases = assembly.lowerCaseRefNameAliases
40
39
 
41
- if (!sessionAliases || !sessionLCAliases) {
40
+ if (!sessionAliases) {
42
41
  throw new Error('Session refNameAliases not found in assembly')
43
42
  }
44
43
 
@@ -46,10 +45,9 @@ export class AddRefSeqAliasesChange extends AssemblySpecificChange {
46
45
  const { aliases, refName } = refSeqAlias
47
46
  for (const alias of aliases) {
48
47
  sessionAliases[alias] = refName
49
- sessionLCAliases[alias.toLowerCase()] = refName
50
48
  }
51
49
  }
52
- assembly.setRefNameAliases(sessionAliases, sessionLCAliases)
50
+ assembly.setRefNameAliases(sessionAliases)
53
51
  return Promise.resolve()
54
52
  }
55
53
 
@@ -44,6 +44,10 @@ export class DeleteFeatureChange extends FeatureChange {
44
44
  super(json, options)
45
45
  this.changes = 'changes' in json ? json.changes : [json]
46
46
  }
47
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
48
+ get notification() {
49
+ return 'Feature deleted successfully'
50
+ }
47
51
 
48
52
  toJSON(): SerializedDeleteFeatureChange {
49
53
  const { assembly, changedIds, changes, typeName } = this
@@ -92,9 +96,10 @@ export class DeleteFeatureChange extends FeatureChange {
92
96
  continue
93
97
  }
94
98
 
95
- const deletedIds = this.findAndDeleteChildFeature(
99
+ const deletedIds = findAndDeleteChildFeature(
96
100
  featureDoc,
97
101
  deletedFeature._id,
102
+ this,
98
103
  )
99
104
  deletedIds.push(deletedFeature._id)
100
105
  featureDoc.allIds = featureDoc.allIds.filter(
@@ -115,38 +120,6 @@ export class DeleteFeatureChange extends FeatureChange {
115
120
  }
116
121
  }
117
122
 
118
- /**
119
- * Delete feature's subfeatures that match an ID and return the IDs of any
120
- * sub-subfeatures that were deleted
121
- * @param feature -
122
- * @param featureIdToDelete -
123
- * @returns - list of deleted feature IDs
124
- */
125
- findAndDeleteChildFeature(
126
- feature: Feature,
127
- featureIdToDelete: string,
128
- ): string[] {
129
- if (!feature.children) {
130
- throw new Error(`Feature ${feature._id} has no children`)
131
- }
132
- const { _id, children } = feature
133
- const child = children.get(featureIdToDelete)
134
- if (child) {
135
- const deletedIds = this.getChildFeatureIds(child)
136
- children.delete(featureIdToDelete)
137
- return deletedIds
138
- }
139
- for (const [, childFeature] of children) {
140
- try {
141
- return this.findAndDeleteChildFeature(childFeature, featureIdToDelete)
142
- } catch {
143
- // pass
144
- }
145
- }
146
-
147
- throw new Error(`Feature "${featureIdToDelete}" not found in ${_id}`)
148
- }
149
-
150
123
  async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
151
124
  throw new Error('executeOnLocalGFF3 not implemented')
152
125
  }
@@ -176,9 +149,9 @@ export class DeleteFeatureChange extends FeatureChange {
176
149
  const inverseChangedIds = [...changedIds].reverse()
177
150
  const inverseChanges = [...changes]
178
151
  .reverse()
179
- .map((deleteFeatuerChange) => ({
180
- addedFeature: deleteFeatuerChange.deletedFeature,
181
- parentFeatureId: deleteFeatuerChange.parentFeatureId,
152
+ .map((deleteFeatureChange) => ({
153
+ addedFeature: deleteFeatureChange.deletedFeature,
154
+ parentFeatureId: deleteFeatureChange.parentFeatureId,
182
155
  }))
183
156
  logger.debug?.(`INVERSE CHANGE '${JSON.stringify(inverseChanges)}'`)
184
157
  return new AddFeatureChange(
@@ -193,6 +166,39 @@ export class DeleteFeatureChange extends FeatureChange {
193
166
  }
194
167
  }
195
168
 
169
+ /**
170
+ * Delete feature's subfeatures that match an ID and return the IDs of any
171
+ * sub-subfeatures that were deleted
172
+ * @param feature -
173
+ * @param featureIdToDelete -
174
+ * @returns - list of deleted feature IDs
175
+ */
176
+ export function findAndDeleteChildFeature(
177
+ feature: Feature,
178
+ featureIdToDelete: string,
179
+ change: FeatureChange,
180
+ ): string[] {
181
+ if (!feature.children) {
182
+ throw new Error(`Feature ${feature._id} has no children`)
183
+ }
184
+ const { _id, children } = feature
185
+ const child = children.get(featureIdToDelete)
186
+ if (child) {
187
+ const deletedIds = change.getChildFeatureIds(child)
188
+ children.delete(featureIdToDelete)
189
+ return deletedIds
190
+ }
191
+ for (const [, childFeature] of children) {
192
+ try {
193
+ return findAndDeleteChildFeature(childFeature, featureIdToDelete, change)
194
+ } catch {
195
+ // pass
196
+ }
197
+ }
198
+
199
+ throw new Error(`Feature "${featureIdToDelete}" not found in ${_id}`)
200
+ }
201
+
196
202
  export function isDeleteFeatureChange(
197
203
  change: unknown,
198
204
  ): change is DeleteFeatureChange {
@@ -17,7 +17,8 @@ interface SerializedFeatureAttributeChangeBase extends SerializedFeatureChange {
17
17
 
18
18
  export interface FeatureAttributeChangeDetails {
19
19
  featureId: string
20
- attributes: Record<string, string[]>
20
+ oldAttributes: Record<string, string[]>
21
+ newAttributes: Record<string, string[]>
21
22
  }
22
23
 
23
24
  interface SerializedFeatureAttributeChangeSingle
@@ -45,8 +46,15 @@ export class FeatureAttributeChange extends FeatureChange {
45
46
  toJSON(): SerializedFeatureAttributeChange {
46
47
  const { assembly, changedIds, changes, typeName } = this
47
48
  if (changes.length === 1) {
48
- const [{ attributes, featureId }] = changes
49
- return { typeName, changedIds, assembly, featureId, attributes }
49
+ const [{ oldAttributes, newAttributes, featureId }] = changes
50
+ return {
51
+ typeName,
52
+ changedIds,
53
+ assembly,
54
+ featureId,
55
+ oldAttributes,
56
+ newAttributes,
57
+ }
50
58
  }
51
59
  return { typeName, changedIds, assembly, changes }
52
60
  }
@@ -94,9 +102,9 @@ export class FeatureAttributeChange extends FeatureChange {
94
102
 
95
103
  // Let's update objects
96
104
  for (const [idx, change] of changes.entries()) {
97
- const { attributes } = change
105
+ const { newAttributes } = change
98
106
  const { feature, topLevelFeature } = featuresForChanges[idx]
99
- feature.attributes = attributes
107
+ feature.attributes = newAttributes
100
108
  if (topLevelFeature._id.equals(feature._id)) {
101
109
  topLevelFeature.markModified('attributes') // Mark as modified. Without this save() -method is not updating data in database
102
110
  } else {
@@ -131,7 +139,7 @@ export class FeatureAttributeChange extends FeatureChange {
131
139
  throw new Error(`Could not find feature with identifier "${changedId}"`)
132
140
  }
133
141
  feature.setAttributes(
134
- new Map(Object.entries(this.changes[idx].attributes)),
142
+ new Map(Object.entries(this.changes[idx].newAttributes)),
135
143
  )
136
144
  }
137
145
  }
@@ -141,7 +149,8 @@ export class FeatureAttributeChange extends FeatureChange {
141
149
  const inverseChangedIds = [...changedIds].reverse()
142
150
  const inverseChanges = [...changes].reverse().map((oneChange) => ({
143
151
  featureId: oneChange.featureId,
144
- attributes: oneChange.attributes,
152
+ oldAttributes: oneChange.newAttributes,
153
+ newAttributes: oneChange.oldAttributes,
145
154
  }))
146
155
  return new FeatureAttributeChange(
147
156
  {
@@ -26,6 +26,9 @@ interface JBrowseInternetAccount {
26
26
 
27
27
  export interface JBrowseConfig {
28
28
  assemblies?: JBrowseAssembly[]
29
+ configuration?: {
30
+ ApolloPlugin?: Record<string, unknown>
31
+ }
29
32
  tracks?: JBrowseTrack[]
30
33
  plugins?: JBrowsePlugin[]
31
34
  internetAccounts?: JBrowseInternetAccount[]
@@ -39,28 +42,40 @@ export interface SerializedImportJBrowseConfigChange {
39
42
  }
40
43
 
41
44
  export function filterJBrowseConfig(config: JBrowseConfig): JBrowseConfig {
42
- const { __v, _id, assemblies, internetAccounts, plugins, tracks, ...rest } =
43
- config
44
- const filteredAssemblies = assemblies?.filter(
45
- (a) => a.sequence.adapter.type !== 'ApolloSequenceAdapter',
46
- )
47
- const filteredTracks = tracks?.filter((t) => t.type !== 'ApolloTrack')
48
- const filteredPlugins = plugins?.filter((p) => p.name !== 'Apollo')
49
- const filteredInternetAccounts = internetAccounts?.filter(
50
- (i) => i.type !== 'ApolloInternetAccount',
51
- )
45
+ const {
46
+ __v,
47
+ _id,
48
+ assemblies,
49
+ configuration,
50
+ internetAccounts,
51
+ plugins,
52
+ tracks,
53
+ ...rest
54
+ } = config
55
+ // Need to make sure that configuration.ApolloPlugin.hasRole isn't set
52
56
  const filteredConfig = rest as JBrowseConfig
53
- if (filteredAssemblies) {
54
- filteredConfig.assemblies = filteredAssemblies
57
+ if (assemblies) {
58
+ filteredConfig.assemblies = assemblies.filter(
59
+ (a) => a.sequence.adapter.type !== 'ApolloSequenceAdapter',
60
+ )
55
61
  }
56
- if (filteredTracks) {
57
- filteredConfig.tracks = filteredTracks
62
+ if (configuration?.ApolloPlugin?.hasRole) {
63
+ const { hasRole, ...apolloPluginRest } = configuration.ApolloPlugin
64
+ filteredConfig.configuration = {
65
+ ...configuration,
66
+ ApolloPlugin: apolloPluginRest,
67
+ }
68
+ }
69
+ if (internetAccounts) {
70
+ filteredConfig.internetAccounts = internetAccounts.filter(
71
+ (i) => i.type !== 'ApolloInternetAccount',
72
+ )
58
73
  }
59
- if (filteredPlugins) {
60
- filteredConfig.plugins = filteredPlugins
74
+ if (plugins) {
75
+ filteredConfig.plugins = plugins.filter((p) => p.name !== 'Apollo')
61
76
  }
62
- if (filteredInternetAccounts) {
63
- filteredConfig.internetAccounts = filteredInternetAccounts
77
+ if (tracks) {
78
+ filteredConfig.tracks = tracks.filter((t) => t.type !== 'ApolloTrack')
64
79
  }
65
80
  return filteredConfig
66
81
  }
@@ -60,19 +60,32 @@ export class LocationEndChange extends FeatureChange {
60
60
  async executeOnServer(backend: ServerDataStore) {
61
61
  const { featureModel, session } = backend
62
62
  const { changes, logger } = this
63
- const featuresForChanges: {
64
- feature: Feature
65
- topLevelFeature: FeatureDocument
66
- }[] = []
67
63
  // Let's first check that all features are found and those old values match with expected ones. We do this just to be sure that all changes can be done.
64
+ const topLevelFeatures: FeatureDocument[] = []
68
65
  for (const change of changes) {
69
- const { featureId, oldEnd } = change
70
-
71
- // Search correct feature
72
- const topLevelFeature = await featureModel
73
- .findOne({ allIds: featureId })
74
- .session(session)
75
- .exec()
66
+ const { featureId, oldEnd, newEnd } = change
67
+
68
+ // See if we already have top-level feature for this feature
69
+ let topLevelFeature: FeatureDocument | undefined | null
70
+ let feature: Feature | undefined | null
71
+ for (const tlv of topLevelFeatures) {
72
+ const childFeature = this.getFeatureFromId(tlv, featureId)
73
+ if (childFeature) {
74
+ topLevelFeature = tlv
75
+ feature = childFeature
76
+ break
77
+ }
78
+ }
79
+ if (!topLevelFeature) {
80
+ // Don't already have top-level feature, so let's query it
81
+ topLevelFeature = await featureModel
82
+ .findOne({ allIds: featureId })
83
+ .session(session)
84
+ .exec()
85
+ if (topLevelFeature) {
86
+ topLevelFeatures.push(topLevelFeature)
87
+ }
88
+ }
76
89
 
77
90
  if (!topLevelFeature) {
78
91
  const errMsg = `*** ERROR: The following featureId was not found in database ='${featureId}'`
@@ -84,57 +97,34 @@ export class LocationEndChange extends FeatureChange {
84
97
  `*** TOP level feature found: ${JSON.stringify(topLevelFeature)}`,
85
98
  )
86
99
 
87
- const foundFeature = this.getFeatureFromId(topLevelFeature, featureId)
88
- if (!foundFeature) {
100
+ if (!feature) {
101
+ feature = this.getFeatureFromId(topLevelFeature, featureId)
102
+ }
103
+ if (!feature) {
89
104
  const errMsg = 'ERROR when searching feature by featureId'
90
105
  logger.error(errMsg)
91
106
  throw new Error(errMsg)
92
107
  }
93
- logger.debug?.(`*** Found feature: ${JSON.stringify(foundFeature)}`)
94
- if (foundFeature.max === oldEnd) {
95
- featuresForChanges.push({ feature: foundFeature, topLevelFeature })
96
- } else {
97
- if (foundFeature.children) {
98
- for (const [, childFeature] of foundFeature.children) {
99
- if (childFeature.max === oldEnd) {
100
- logger.debug?.(
101
- `*************** UPDATE CHILD FEATURE ID= ${featureId}, CHILD: ${JSON.stringify(
102
- childFeature,
103
- )}`,
104
- )
105
- featuresForChanges.push({
106
- feature: childFeature,
107
- topLevelFeature,
108
- })
109
- break
110
- }
111
- }
112
- }
108
+ logger.debug?.(`*** Found feature: ${JSON.stringify(feature)}`)
109
+ if (feature.max !== oldEnd) {
110
+ const errMsg = 'Expected previous max does not match'
111
+ logger.error(errMsg)
112
+ throw new Error(errMsg)
113
113
  }
114
- }
115
-
116
- // Let's update objects.
117
- for (const [idx, change] of changes.entries()) {
118
- const { newEnd } = change
119
- const { feature, topLevelFeature } = featuresForChanges[idx]
120
114
  feature.max = newEnd
121
115
  if (topLevelFeature._id.equals(feature._id)) {
122
116
  topLevelFeature.markModified('end') // Mark as modified. Without this save() -method is not updating data in database
123
117
  } else {
124
118
  topLevelFeature.markModified('children') // Mark as modified. Without this save() -method is not updating data in database
125
119
  }
126
-
120
+ }
121
+ for (const tlv of topLevelFeatures) {
127
122
  try {
128
- await topLevelFeature.save()
123
+ await tlv.save()
129
124
  } catch (error) {
130
125
  logger.debug?.(`*** FAILED: ${error}`)
131
126
  throw error
132
127
  }
133
- logger.debug?.(
134
- `*** Object updated in Mongo. New object: ${JSON.stringify(
135
- topLevelFeature,
136
- )}`,
137
- )
138
128
  }
139
129
  }
140
130
 
@@ -146,12 +136,13 @@ export class LocationEndChange extends FeatureChange {
146
136
  if (!dataStore) {
147
137
  throw new Error('No data store')
148
138
  }
149
- for (const [idx, changedId] of this.changedIds.entries()) {
150
- const feature = dataStore.getFeature(changedId)
139
+ for (const change of this.changes) {
140
+ const { featureId, newEnd } = change
141
+ const feature = dataStore.getFeature(featureId)
151
142
  if (!feature) {
152
- throw new Error(`Could not find feature with identifier "${changedId}"`)
143
+ throw new Error(`Could not find feature with identifier "${featureId}"`)
153
144
  }
154
- feature.setMax(this.changes[idx].newEnd)
145
+ feature.setMax(newEnd)
155
146
  }
156
147
  }
157
148