7
7
Template ,
8
8
getTemplatePathInScope ,
9
9
} from '../lib/utils/templates' ;
10
- import { wait } from '../lib/utils/general' ;
10
+ import { wait , isEmpty } from '../lib/utils/general' ;
11
11
import { invert , removeNumericKeys , consolidate } from '../lib/utils/objects' ;
12
12
import { createDataHarmonizerContainer , createDataHarmonizerTab } from '../web' ;
13
13
import { getExportFormats } from 'schemas' ;
@@ -39,7 +39,7 @@ export default class AppContext {
39
39
$ ( document ) . on ( 'dhTabChange' , ( event , data ) => {
40
40
console . info (
41
41
'dhTabChange' ,
42
- this . getCurrentDataHarmonizer ( ) . class_assignment ,
42
+ this . getCurrentDataHarmonizer ( ) . template_name ,
43
43
'->' ,
44
44
data . specName
45
45
) ;
@@ -278,7 +278,7 @@ export default class AppContext {
278
278
// HoT settings.
279
279
const dh = new DataHarmonizer ( dhSubroot , this , {
280
280
loadingScreenRoot : document . body ,
281
- class_assignment : class_name ,
281
+ template_name : class_name ,
282
282
schema : schema , // assign during constructor so validator can init on it.
283
283
hot_override_settings : {
284
284
minRows : is_child ? 0 : 10 ,
@@ -291,7 +291,9 @@ export default class AppContext {
291
291
} ) ;
292
292
293
293
data_harmonizers [ class_name ] = dh ;
294
- dh . useTemplate ( class_name ) ;
294
+ dh . template = schema . classes [ class_name ] ;
295
+ // Set up the data structure based on LinkML class
296
+ dh . useTemplate ( class_name ) ;
295
297
dh . validator . useTargetClass ( class_name ) ;
296
298
297
299
if ( is_child ) {
@@ -354,6 +356,7 @@ export default class AppContext {
354
356
? this . template . localized . schema
355
357
: this . template . default . schema ;
356
358
359
+ // FUTURE slot_ptr version of self.context.relations for efficiency?
357
360
this . relations = this . crudGetRelations ( schema ) ;
358
361
359
362
// Merges any existing dataharmonizer instances with the ones newly created.
@@ -409,6 +412,13 @@ export default class AppContext {
409
412
[slot_name]: [foreign_slot_name],
410
413
[slot_name_2]: [foreign_slot_name_2]...
411
414
}
415
+ },
416
+ dependent_slots: {
417
+ [slot_name]: {
418
+ [foreign_class]: {foreign_slot_name]}...
419
+ }
420
+ unique_key_slots: {
421
+ [slot_name]: {[unique_key_name]:true, ...
412
422
}
413
423
}
414
424
@@ -431,15 +441,17 @@ export default class AppContext {
431
441
crudGetRelations ( schema ) {
432
442
let relations = { } ;
433
443
Object . entries ( schema . classes ) . forEach ( ( [ class_name , class_obj ] ) => {
444
+ relations [ class_name ] = { } ;
445
+
434
446
Object . entries ( class_obj . attributes ?? { } ) . forEach ( ( [ slot_name , attribute ] ) => {
435
447
if ( attribute . annotations ?. foreign_key ?. value ) {
436
-
437
448
let foreign_class , foreign_slot_name ;
438
449
let key = attribute . annotations . foreign_key . value ;
439
- // Best syntax is that foreign class is dot prefix:
450
+ // FIRST, TRY GETTING PARENT CLASS VIA foreign class with dot prefix:
440
451
if ( key . includes ( '.' ) )
441
452
[ foreign_class , foreign_slot_name ] = key . split ( '.' , 2 ) ;
442
- // UNTESTED: Workaround is that foreign class is in range of slot:
453
+ // ALTERNATELY (UNTESTED) USE range of slot having reference to a class:
454
+ // NOTE: this doesn't handle multiple classes in range.
443
455
else if ( attribute . range in schema . classes ) {
444
456
foreign_slot_name = key ;
445
457
foreign_class = attribute . range ;
@@ -448,11 +460,24 @@ export default class AppContext {
448
460
console . log ( "Class" , class_name , "has slot" , slot_name , "foreign key" , attribute . annotations ?. foreign_key ?. value , "but no target class information in key or slot range." ) ;
449
461
return ;
450
462
}
451
- Object . assign ( relations , { [ class_name ] : { parent : { [ foreign_class ] : { [ slot_name ] : foreign_slot_name } } } } ) ;
452
- //And reverse relation
453
- Object . assign ( relations , { [ foreign_class ] : { child : { [ class_name ] : { [ foreign_slot_name ] : slot_name } } } } ) ;
463
+ Object . assign ( relations [ class_name ] ,
464
+ { parent : { [ foreign_class ] : { [ slot_name ] : foreign_slot_name } } } ) ;
465
+ // And reverse relation
466
+ Object . assign ( relations [ foreign_class ] ,
467
+ { child : { [ class_name ] : { [ foreign_slot_name ] : slot_name } } } ) ;
468
+ // And dependent slots
469
+ Object . assign ( relations [ class_name ] ,
470
+ { dependent_slots : { [ slot_name ] : { parent : foreign_class , slot : foreign_slot_name } } } ) ;
454
471
}
455
472
} ) ;
473
+
474
+ // Now do unique keys in class_obj
475
+ Object . entries ( class_obj . unique_keys ?? { } ) . forEach ( ( [ key_name , key_obj ] ) => {
476
+ Object . entries ( key_obj . unique_key_slots ?? { } ) . forEach ( ( [ index , slot_name ] ) => {
477
+ Object . assign ( relations [ class_name ] ,
478
+ { unique_key_slots : { [ slot_name ] : { [ key_name ] : true } } } ) ;
479
+ } ) ;
480
+ } ) ;
456
481
} ) ;
457
482
return relations ;
458
483
}
@@ -481,27 +506,42 @@ export default class AppContext {
481
506
return this . crudIsAncestor ( class_names , ancestor_name )
482
507
}
483
508
484
- /* Retrieves ordered list of tables that ultimately have given template as
509
+ /** Retrieves ordered list of tables that ultimately have given template as
485
510
* a foreign key. Whether it is to enact cascading visibility, update or
486
511
* deletion events, this provides the order in which to trigger changes.
487
512
* Issue is that intermediate tables need to be checked in order due to
488
513
* dependencies by foreign keys. If a table depended on an intermediate that
489
514
* hadn't been refreshed, we'd get a wrong display.
490
515
* Relying on javascript implicit ordering of dictionary added elements.
516
+ * @param {String } class_names initial value
517
+ * @param {Array } class_names array of relations
518
+ * @return {Object } class_names where each descendent class_name is mentioned.
491
519
*/
492
520
crudGetDependents ( class_names ) {
493
- // Initialization case
494
- if ( typeof class_names == "string" ) {
495
- class_names = { ...this . crudGetChildren ( class_names ) } ;
521
+ // Initialization case gets single class_name
522
+ if ( class_names && typeof class_names == "string" ) {
523
+ class_names = this . crudGetChildren ( class_names ) ;
524
+ console . log ( "class_names" , class_names ) ;
525
+ if ( ! class_names )
526
+ return { } ;
527
+ }
528
+ let children = { } ;
529
+ for ( const dependent_name in class_names ) {
530
+ children = { ...children , ...this . crudGetChildren ( dependent_name ) }
496
531
}
497
- let children = this . crudGetChildren ( class_names ) ;
498
- if ( children ) {
499
- class_names = { ...children } ;
500
- return this . crudGetDependents ( class_names ) ;
532
+ if ( children . length > 0 ) {
533
+ return { ...class_names , ...children , ...this . crudGetDependents ( children ) } ;
501
534
}
502
535
return class_names ;
503
536
}
504
537
538
+ crudCheckDependency ( self , row , dependent_name , changes ) {
539
+ let keyVals = { } ;
540
+ let change_log = '' ;
541
+ let found = false ;
542
+ return [ found , keyVals , change_log ] ;
543
+ }
544
+
505
545
/* For given class, refresh view of all dependent tables that have a direct
506
546
* or indirect foreign key relationship to given class.
507
547
* Performance might show up as an issue later if lots of long dependent
@@ -522,7 +562,10 @@ export default class AppContext {
522
562
* primary key selection has been made!
523
563
*/
524
564
crudFilterByForeignKey ( class_name ) {
525
- const hotInstance = this . dhs [ class_name ] . hot ;
565
+ const hotInstance = this . dhs [ class_name ] ?. hot ;
566
+ // This can happen when one DH is rendered but not other dependents yet.
567
+ if ( ! hotInstance )
568
+ return ;
526
569
const hiddenRowsPlugin = hotInstance . getPlugin ( 'hiddenRows' ) ;
527
570
528
571
// Ensure all rows are visible
@@ -742,4 +785,40 @@ export default class AppContext {
742
785
} ) ;
743
786
}
744
787
788
+ /** Determine if given dh has a complete set of unique key slot values as
789
+ * given by key_name.
790
+ * @param {Object } dh instance
791
+ * @param {String } key_name of
792
+ * @param {Object } changes for a row with slot_name as key.
793
+ * @return {Array } of:
794
+ * @return {Boolean } found boolean true if complete keyVals
795
+ * @return {Object } keyVals set of slots and their values from template or change
796
+ * @return {String } change_log report of changed slot values
797
+ */
798
+ crudHasUniqueKey ( dh , row , key_name , changes , keyVals = { } , change_log = '' ) {
799
+ const key_obj = dh . template . unique_keys [ key_name ] ;
800
+ let found = Object . entries ( key_obj . unique_key_slots ) . every ( ( [ index , slot_name ] ) => {
801
+ // Key has a changed value (incl. null?), so update it.
802
+ if ( slot_name in changes ) {
803
+ let change = changes [ slot_name ] ;
804
+ change_log += `* [${ slot_name } ] change to "${ change . value } "\n` ;
805
+ keyVals [ slot_name ] = change . value ;
806
+ return true ;
807
+ }
808
+ else {
809
+ let col = dh . getColumnIndexByFieldName ( slot_name ) ;
810
+ let value = dh . hot . getDataAtCell ( row , col ) ;
811
+ if ( value != null ) {
812
+ keyVals [ key_name ] = value ;
813
+ return true ;
814
+ }
815
+ }
816
+
817
+ // Key value hasn't changed, and is missing a value, so
818
+ // unique_key is not established, so user's change can go
819
+ // through.
820
+ return false ;
821
+ } ) ;
822
+ return [ found , keyVals , change_log ] ;
823
+ }
745
824
}
0 commit comments