From 882b884c5cf62eabc993b502b6c74ef5df048de5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 09:26:52 -0700
Subject: [PATCH 01/30] Bump @types/react from 18.3.5 to 18.3.10 in the react
group (#2551)
Bumps the react group with 1 update: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react).
Updates `@types/react` from 18.3.5 to 18.3.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)
---
updated-dependencies:
- dependency-name: "@types/react"
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: react
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index efbe4d09a..a4ac46939 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
},
"devDependencies": {
"@isrd-isi-edu/ermrest-data-utils": "^0.0.7",
- "@playwright/test": "latest",
+ "@playwright/test": "*",
"@typescript-eslint/eslint-plugin": "~7.18.0",
"@typescript-eslint/parser": "~7.18.0",
"chance": "x",
@@ -2543,9 +2543,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "18.3.5",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
- "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==",
+ "version": "18.3.10",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
+ "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
From f4f08c1df0abdea4da00d4dd12fe02007934f03a Mon Sep 17 00:00:00 2001
From: Josh Chudy
Date: Thu, 3 Oct 2024 10:20:35 -0700
Subject: [PATCH 02/30] Recordedit with association picker when adding rows
using prefill (#2490)
* changes to show and association picker before record edit form input
* add more popup modal
* initialize form provider while showing the modal instead of waiting
* add/remove selected rows when forms removed, input value is updated/removed
* disable set some button, don't clone unique fk data
* move prefill object to the provider instead of creating it each time it was needed
* foreign key dropdown support to update the association selected rows on change and update dropdowns when the set changes to have new values disabled
* changes to add tooltips to modal and dropdown rows that are disabled. clean up code and move some functions around. Proper types added in more places. Change single-select icons when row is selected or disabled. remove color change when a row is disabled
* always initialize prefill in RE
* add test cases for modal and dropdown inputs
* address comments from the PR
---
docs/user-docs/logging.md | 3 +
src/assets/scss/_input-switch.scss | 39 +-
src/assets/scss/_inputs.scss | 4 +-
src/assets/scss/_recordedit.scss | 72 +-
src/assets/scss/_recordset-table.scss | 41 +-
src/assets/scss/_variables.scss | 2 +
src/assets/scss/helpers.scss | 6 +-
src/assets/scss/maps/_color-map.scss | 1 +
.../foreignkey-dropdown-field.tsx | 154 +-
.../input-switch/foreignkey-field.tsx | 35 +-
src/components/input-switch/input-field.tsx | 3 +-
src/components/modals/recordset-modal.tsx | 29 +-
.../record/related-table-actions.tsx | 16 +-
src/components/recordedit/form-container.tsx | 2 +-
src/components/recordedit/form-row.tsx | 40 +-
src/components/recordedit/key-column.tsx | 6 +
src/components/recordedit/recordedit.tsx | 451 ++++-
src/components/recordset/recordset-table.tsx | 85 +-
src/components/recordset/recordset.tsx | 6 +-
.../recordset/saved-query-dropdown.tsx | 1 +
src/components/recordset/table-row.tsx | 64 +-
src/models/log.ts | 6 +-
src/models/recordedit.ts | 25 +-
src/models/recordset.ts | 15 +-
src/pages/recordedit.tsx | 13 +-
src/providers/alerts.tsx | 40 +-
src/providers/recordedit.tsx | 186 +-
src/providers/recordset.tsx | 22 +-
src/providers/viewer.tsx | 7 +-
src/utils/message-map.ts | 5 +-
src/utils/record-utils.ts | 29 +-
src/utils/recordedit-utils.ts | 155 +-
.../association_table_w_static_column.json | 1 +
...iation_table_w_static_column_dropdown.json | 1 +
.../leaf_table_for_static_columns.json | 10 +
...eaf_table_for_static_columns_dropdown.json | 10 +
...roduct-unordered-related-tables-links.json | 1564 ++++++++++++++---
test/e2e/locators/modal.ts | 4 +
test/e2e/locators/recordedit.ts | 23 +
.../all-features/record/related-table.spec.ts | 144 +-
test/e2e/utils/page-utils.ts | 1 -
test/e2e/utils/record-utils.ts | 252 ++-
test/e2e/utils/recordedit-utils.ts | 19 +-
43 files changed, 2900 insertions(+), 692 deletions(-)
create mode 100644 test/e2e/data_setup/data/product/association_table_w_static_column.json
create mode 100644 test/e2e/data_setup/data/product/association_table_w_static_column_dropdown.json
create mode 100644 test/e2e/data_setup/data/product/leaf_table_for_static_columns.json
create mode 100644 test/e2e/data_setup/data/product/leaf_table_for_static_columns_dropdown.json
diff --git a/docs/user-docs/logging.md b/docs/user-docs/logging.md
index bafd383bd..f1c5ca4d8 100644
--- a/docs/user-docs/logging.md
+++ b/docs/user-docs/logging.md
@@ -213,10 +213,13 @@ Where,
- `pcol`: Based on `pcol` stack node `type`.
- `related`, `related-inline`: Based on `related` stack node `type`.
- `related-link-picker`: Used for the association link picker.
+ - `related-unlink-picker`: Used for the association unlink picker.
- `facet`: Based on `facet` stack node `type`.
- `facet-picker`: Used for facet picker.
- `fk`: Based on `fk` stack node `type`.
- `fk-picker`: Used for foreign key picker.
+ - `fk-bulk-picker`: Used for foreign key picker when the selections fill the same foreign key field in multiple recordedit forms
+ - `fk-dropdown`: Used for foreign key inputs when they are a dropdown.
- `saved-query-entity`: Used for saved query create popup.
- `saved-query-picker`: Used for saved query apply picker.
- `annotation-set`: Annotation list displayed on the viewer app.
diff --git a/src/assets/scss/_input-switch.scss b/src/assets/scss/_input-switch.scss
index d099de5b3..c3c89bc90 100644
--- a/src/assets/scss/_input-switch.scss
+++ b/src/assets/scss/_input-switch.scss
@@ -191,6 +191,7 @@
background: map-get(variables.$color-map, 'disabled-background');
opacity: 0.55;
}
+
.spinner-border-sm {
top: 8px;
position: absolute;
@@ -214,19 +215,21 @@
}
// highlight the recordedit input for visual clarity
- .dropdown.show {
+ .dropdown.show {
+
/**
- * Select the input groups that are not disabled. react-bootstrap tends to toggle the `show` class for Dropdown toggles with a custom element type.
+ * Select the input groups that are not disabled. react-bootstrap tends to toggle the `show` class for Dropdown toggles with a custom element type.
* this check avoid unexpected highlighting of disabled elements.
*/
>.chaise-input-group:not([aria-disabled="true"]),
- .dropdown-menu{
+ .dropdown-menu {
border: 2px solid map-get(variables.$color-map, 'primary');
border-radius: 6px;
}
}
- .responsive-dropdown-menu, .dropdown-menu {
+ .responsive-dropdown-menu,
+ .dropdown-menu {
// make sure the menu expands the whole row
width: auto;
min-width: 100%;
@@ -245,11 +248,11 @@
// remove rounded borders for search input components when in a dropdown
.search-row .chaise-search-box {
- .chaise-input-group-prepend > .chaise-input-group-text {
+ .chaise-input-group-prepend>.chaise-input-group-text {
border-bottom-left-radius: 0;
}
- .chaise-input-group-append > .chaise-search-btn {
+ .chaise-input-group-append>.chaise-search-btn {
border-bottom-right-radius: 0;
}
}
@@ -264,6 +267,18 @@
.dropdown-item {
cursor: pointer;
+ &.disabled,
+ &:disabled {
+ pointer-events: unset;
+ cursor: unset;
+
+ // bootstrap stylesheets have :hover defined before :disabled so disabled always takes precedence
+ // redefine with the same hover color to override bootstrap order
+ &:hover {
+ background-color: map-get(variables.$color-map, 'recordedit-dropdown-hover');
+ }
+ }
+
label {
padding-left: 31px;
cursor: inherit;
@@ -328,7 +343,19 @@
&.input-switch-error-danger {
color: map-get(variables.$color-map, 'input-error-message-danger');
}
+
&.input-switch-error-warning {
color: map-get(variables.$color-map, 'input-error-message-warning');
}
}
+
+// used for foreignkey-dropdown-field
+// !important needs to be used here since bootstrap attaches styles directly to the component which override stylesheet styles
+.tooltip.reposition-li-tooltip {
+ left: 10px !important;
+
+ .tooltip-arrow {
+ left: 40px !important;
+ transform: unset !important;
+ }
+}
diff --git a/src/assets/scss/_inputs.scss b/src/assets/scss/_inputs.scss
index 6e61828c5..7bc048df9 100644
--- a/src/assets/scss/_inputs.scss
+++ b/src/assets/scss/_inputs.scss
@@ -237,7 +237,7 @@
cursor: not-allowed;
// We are adding this to fix the issue in firefox where the form is not clickable if its disabled
pointer-events: none;
-
+
// make sure the input is taking the input-disabled styles and not the default bootstrap/browser ones
// (using * so it selects input, textarea, or any other HTML element that we might have
*::placeholder, *:disabled {
@@ -320,7 +320,7 @@
.chaise-input-group-text-sm {
height: variables.$btn-height-sm;
min-height: variables.$btn-height-sm;
- padding: variables.$btn-padding-y variables.$btn-padding-x;
+ padding: variables.$btn-padding-sm-y variables.$btn-padding-sm-x;
}
.chaise-input-control-sm {
diff --git a/src/assets/scss/_recordedit.scss b/src/assets/scss/_recordedit.scss
index 5677cc87b..0e4881786 100644
--- a/src/assets/scss/_recordedit.scss
+++ b/src/assets/scss/_recordedit.scss
@@ -18,9 +18,18 @@
width: auto;
margin-left: auto;
- .add-rows-input {
- width: 50px;
- padding-left: 5px;
+ .chaise-input-group {
+ width: auto;
+
+ .add-rows-input {
+ width: 45px;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ }
+
+ #recordedit-add-more {
+ margin-left: 10px;
}
}
}
@@ -404,61 +413,4 @@
padding-top: 10px;
padding-bottom: 20px;
}
-
- /****************** Upload modal css *****************/
-
- // .progress-percent {
- // font-weight: bold;
- // margin-top: -25px;
- // margin-left: 50%;
- // }
-
- // .inner-progress-percent {
- // margin-top: -18px;
- // font-size: 12px;
- // }
-
- // table.upload-table {
-
- // }
-
- // table.upload-table > tbody {
- // border: none;
- // }
-
- // table.upload-table > tbody > tr > td:first-child {
- // padding-left: 20px;
-
- // }
-
- // table.upload-table > tbody > tr > td:last-child {
- // width: 40%;
- // }
-
- // table.upload-table > tbody > tr > td:nth-child(2) {
- // padding-left: 0px;
- // padding-right: 0px;
- // }
-
- // table.upload-table > tbody > tr:last-child > td {
- // border-bottom: 1px solid $disabled-background-color;
- // background-color: white;
- // }
-
- // table.upload-table > tbody > tr:first-child > td {
- // padding-left:0px;
- // padding-top: 15px;
- // border-bottom: 2px solid gainsboro;
- // font-weight: 600;
- // }
-
- // table.upload-table .progress {
- // height: 20px;
- // }
-
- // .upload-progress-bar {
- // width: 0%;
- // background-color: #8cacc7 !important;
- // }
-
}
diff --git a/src/assets/scss/_recordset-table.scss b/src/assets/scss/_recordset-table.scss
index bbc4b17d9..7c9038931 100644
--- a/src/assets/scss/_recordset-table.scss
+++ b/src/assets/scss/_recordset-table.scss
@@ -21,37 +21,18 @@
display: table-row-group;
vertical-align: middle;
- > tr > td.disabled-cell {
- background-color: map-get(variables.$color-map, 'table-disabled-background');
- }
-
/* table hover */
> tr:hover {
background-color: map-get(variables.$color-map, 'table-highlight-background') !important;
- /* match color from row highlight for disabled cells */
- > td.disabled-cell {
- background-color: map-get(variables.$color-map, 'table-highlight-background') !important;
- }
-
.hover-show {
visibility: visible;
}
}
- /* odd disabled cells need to be darker */
- > tr:nth-child(odd) > td.disabled-cell {
- background-color: map-get(variables.$color-map, 'table-striped-disabled-background');
- }
-
/* odd row hover for striped table */
> tr:nth-child(odd):hover {
background-color: map-get(variables.$color-map, 'table-striped-highlight-background') !important;
-
- /* odd disabled cells need to match odd row hover for striped table above */
- > td.disabled-cell {
- background-color: map-get(variables.$color-map, 'table-striped-highlight-background') !important;
- }
}
}
@@ -194,7 +175,27 @@
}
.chaise-btn.select-action-button {
- border-radius: 10px;
+ border-radius: 12px;
+
+ &.chaise-btn-secondary {
+ border-width: 2px;
+ }
+
+ &[disabled] {
+ min-width: 12px;
+ width: 12px;
+ height: 12px;
+ margin-top: 5px;
+ }
+
+ // change size of single select icon to "fill" the whole button
+ .fa-regular.fa-circle, .fa-circle-check {
+ font-size: 1.6rem;
+ // added here instead of to the button as padding-top to only affect these buttons/icons
+ margin-top: 1px;
+ background-color: map-get(variables.$color-map, 'white') !important;
+ border-radius: inherit;
+ }
}
// make the button smaller (only visible for primary buttons. for others won't make a difference in UI and that's fine)
diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss
index e7d91cb49..94b25a949 100644
--- a/src/assets/scss/_variables.scss
+++ b/src/assets/scss/_variables.scss
@@ -14,6 +14,8 @@ $btn-height-sm: 24px;
$btn-border-width: 1px;
$btn-padding-y: 4px;
$btn-padding-x: 10px;
+$btn-padding-sm-y: 2px;
+$btn-padding-sm-x: 5px;
// input
$input-remove-width: 18px;
diff --git a/src/assets/scss/helpers.scss b/src/assets/scss/helpers.scss
index 33cc1b703..264bd6b34 100644
--- a/src/assets/scss/helpers.scss
+++ b/src/assets/scss/helpers.scss
@@ -88,8 +88,8 @@
-moz-user-select: none;
-ms-user-select: none;
white-space: nowrap;
- /**
- * Fixing button spacing issue. Fix- center aligning the buttons. Adding line-height will make the children of button to
+ /**
+ * Fixing button spacing issue. Fix- center aligning the buttons. Adding line-height will make the children of button to
* use this property to override what bootstrap is defining.
*/
display: inline-flex;
@@ -124,7 +124,7 @@
height: variables.$btn-height-sm;
min-width: variables.$btn-height-sm;
font-size: 1rem;
- padding: 2px 5px;
+ padding: variables.$btn-padding-sm-y variables.$btn-padding-sm-x;
}
// can be used to write media-queries
diff --git a/src/assets/scss/maps/_color-map.scss b/src/assets/scss/maps/_color-map.scss
index 60494cfd8..cfcdd99dc 100644
--- a/src/assets/scss/maps/_color-map.scss
+++ b/src/assets/scss/maps/_color-map.scss
@@ -33,6 +33,7 @@ $color-map: (
'primary': #4674a7,
'primary-hover': #428bca,
'recordedit-border': #888888,
+ 'recordedit-dropdown-hover': #e9ecef, // same color as bootstrap dropdown-item hover
'recordedit-highlighted-row': #f7f0cf,
'recordedit-column-permission-warning': rgb(180, 95, 6),
'spinner': #6e6e6e,
diff --git a/src/components/input-switch/foreignkey-dropdown-field.tsx b/src/components/input-switch/foreignkey-dropdown-field.tsx
index 9794b58c2..a6a03c845 100644
--- a/src/components/input-switch/foreignkey-dropdown-field.tsx
+++ b/src/components/input-switch/foreignkey-dropdown-field.tsx
@@ -1,19 +1,21 @@
// components
import ClearInputBtn from '@isrd-isi-edu/chaise/src/components/clear-input-btn';
+import DisplayValue from '@isrd-isi-edu/chaise/src/components/display-value';
import Dropdown from 'react-bootstrap/Dropdown';
import InputField, { InputFieldProps } from '@isrd-isi-edu/chaise/src/components/input-switch/input-field';
-import DisplayValue from '@isrd-isi-edu/chaise/src/components/display-value';
import SearchInput from '@isrd-isi-edu/chaise/src/components/search-input';
import Spinner from 'react-bootstrap/Spinner';
+import ChaiseTooltip from '@isrd-isi-edu/chaise/src/components/tooltip';
// hooks
import useError from '@isrd-isi-edu/chaise/src/hooks/error';
-import { useLayoutEffect, useRef, useState } from 'react';
+import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useFormContext } from 'react-hook-form';
// models
-import { appModes, RecordeditColumnModel, RecordeditForeignkeyCallbacks } from '@isrd-isi-edu/chaise/src/models/recordedit';
import { LogActions, LogReloadCauses, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
+import { DisabledRow, DisabledRowType, SelectedRow } from '@isrd-isi-edu/chaise/src/models/recordset';
+import { appModes, RecordeditColumnModel, RecordeditForeignkeyCallbacks } from '@isrd-isi-edu/chaise/src/models/recordedit';
// services
import { ConfigService } from '@isrd-isi-edu/chaise/src/services/config';
@@ -21,14 +23,12 @@ import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
// utils
import { RECORDSET_DEFAULT_PAGE_SIZE } from '@isrd-isi-edu/chaise/src/utils/constants';
-import { isStringAndNotEmpty } from '@isrd-isi-edu/chaise/src/utils/type-utils';
-import { makeSafeIdAttr } from '@isrd-isi-edu/chaise/src/utils/string-utils';
import {
- callOnChangeAfterSelection,
- clearForeignKeyData,
- createForeignKeyReference,
- validateForeignkeyValue
+ callOnChangeAfterSelection, clearForeignKeyData, createForeignKeyReference,
+ disabledRowTooltip, validateForeignkeyValue
} from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
+import { makeSafeIdAttr } from '@isrd-isi-edu/chaise/src/utils/string-utils';
+import { isStringAndNotEmpty } from '@isrd-isi-edu/chaise/src/utils/type-utils';
import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref';
type ForeignkeyDropdownFieldProps = InputFieldProps & {
@@ -119,7 +119,7 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
const [dropdownReference, setDropdownReference] = useState(null);
const [currentDropdownPage, setCurrentDropdownPage] = useState(null);
- type DropdownRow = { tuple: any, isDisabled: boolean };
+ type DropdownRow = { tuple: any, isDisabled: boolean, disabledType?: DisabledRowType };
const [dropdownRows, setDropdownRows] = useState([]); // array of page.tuples
const [checkedRow, setCheckedRow] = useState(null); // ERMrest.Tuple
@@ -162,14 +162,42 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
});
}, [triggerDropdownChange]);
+ // if there is a unique association using prefill and this column is the same as the one used in the unique association for the leaf
+ // table of the association, update the rows in the dropdown if the selected rows change
+ useEffect(() => {
+ if (
+ !props.foreignKeyCallbacks?.updateBulkForeignKeySelectedRows ||
+ !dropdownInitialized || !props.foreignKeyCallbacks.bulkForeignKeySelectedRows
+ ) return;
+
+ setShowSpinner(true);
+
+ const currStackNode = LogService.getStackNode(LogStackTypes.FOREIGN_KEY, dropdownReference.table);
+ const stack = LogService.addExtraInfoToStack(LogService.getStackObject(currStackNode), { dropdown: 1 })
+
+ const requestCauses = [LogReloadCauses.BULK_FK_ROWS_CHANGED];
+ const reloadStartTime = ConfigService.ERMrest.getElapsedTime();
+ const logObj = {
+ action: LogService.getActionString(LogActions.RELOAD, stackPath),
+ stack: LogService.addCausesToStack(stack, requestCauses, reloadStartTime)
+ }
+
+ populateDropdownRows(currentDropdownPage, checkedRow, pageLimit, logObj.stack, stackPath, requestCauses, reloadStartTime).then(() => {
+ setShowSpinner(false);
+ }).catch((exception: any) => {
+ setShowSpinner(false);
+ dispatchError({ error: exception });
+ });
+ }, [props.foreignKeyCallbacks?.bulkForeignKeySelectedRows]);
+
/**
* populate the dropdown rows after a request is done.
* this function will take care of calling the getDisabledTuples if it's defined and setting the tuples as disabled
*/
- const populateDropdownRows = (page: any, pageLimit: number, logStack: any,
+ const populateDropdownRows = (page: any, currentRow: SelectedRow | null, pageLimit: number, logStack: any,
logStackPath: string, requestCauses?: any, reloadStartTime?: any): Promise => {
return new Promise((resolve, reject) => {
- type PType = { page: any, disabledRows?: any };
+ type PType = { page: any, disabledRows?: DisabledRow[] };
let p;
if (props.foreignKeyCallbacks && props.foreignKeyCallbacks.getDisabledTuples) {
p = props.foreignKeyCallbacks.getDisabledTuples(page, pageLimit, logStack, logStackPath, requestCauses, reloadStartTime);
@@ -178,15 +206,20 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
}
p.then((result: PType) => {
- const disabledTuplesUniqueIDs: any = {};
+ const disabledTuplesUniqueIDs: any = {}; // { uniqueId: { disabledType: DisabledRowType } }
if (Array.isArray(result.disabledRows)) {
- result.disabledRows.forEach((t: any) => {
- disabledTuplesUniqueIDs[t.uniqueId] = 1;
+ result.disabledRows.forEach((t: DisabledRow) => {
+ disabledTuplesUniqueIDs[t.tuple.uniqueId] = { disabledType: t.disabledType };
})
}
setDropdownRows(page.tuples.map((tuple: any) => {
- return { isDisabled: (tuple.uniqueId in disabledTuplesUniqueIDs), tuple };
+ // only disable if it's in the list AND it's not the current selection
+ const isDisabled = tuple.uniqueId in disabledTuplesUniqueIDs && tuple.uniqueId !== currentRow?.uniqueId
+ const row: DropdownRow = { tuple, isDisabled }
+ if (isDisabled) row.disabledType = disabledTuplesUniqueIDs[tuple.uniqueId].disabledType;
+
+ return row;
}));
resolve(true);
@@ -241,15 +274,19 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
// we don't know which column value is used for the displayname so it's better to check react-hook-form state
const displayedValue = getValues()[props.name];
+ let currentRow = null;
// check if we have a value set for the foreign key input
if (displayedValue) {
// set the checked row if it's present in the page of rows
page.tuples.forEach((tuple: any) => {
- if (tuple.displayname.value === displayedValue) setCheckedRow(tuple);
+ if (tuple.displayname.value === displayedValue) {
+ setCheckedRow(tuple);
+ currentRow = tuple;
+ }
});
}
- return populateDropdownRows(page, pageLimit, logObj.stack, stackPath);
+ return populateDropdownRows(page, currentRow, pageLimit, logObj.stack, stackPath);
}).then(() => {
setShowSpinner(false);
@@ -264,9 +301,15 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
* make sure the underlying raw columns as well as foreignkey data are also emptied.
*/
const onClear = () => {
+ const column = props.columnModel.column;
+
+ if (props.foreignKeyCallbacks?.updateBulkForeignKeySelectedRows) {
+ props.foreignKeyCallbacks.updateBulkForeignKeySelectedRows(usedFormNumber);
+ }
+
clearForeignKeyData(
props.name,
- props.columnModel.column,
+ column,
usedFormNumber,
props.foreignKeyData,
setValue
@@ -306,7 +349,7 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
searchRef.read(pageLimit, logObj).then((page: any) => {
setCurrentDropdownPage(page);
- return populateDropdownRows(page, pageLimit, logObj.stack, stackPath, requestCauses, reloadStartTime);
+ return populateDropdownRows(page, checkedRow, pageLimit, logObj.stack, stackPath, requestCauses, reloadStartTime);
}).then(() => {
setShowSpinner(false);
}).catch((exception: any) => {
@@ -373,13 +416,26 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
onClearFun(e);
}
- const onRowSelected = (selectedRow: any, onChange: any) => {
+ /**
+ *
+ * @param selectedRow tuple object from ERMrestJS that represents the dropdown row
+ * @param onChange
+ */
+ const onRowSelected = (selectedRow: SelectedRow, onChange: any) => {
setCheckedRow(selectedRow);
+
+ const column = props.columnModel.column;
+
+ // if the recordedit page's table is an association table with a unique key pair, track the selected rows
+ if (props.foreignKeyCallbacks?.updateBulkForeignKeySelectedRows) {
+ props.foreignKeyCallbacks.updateBulkForeignKeySelectedRows(usedFormNumber, selectedRow);
+ }
+
callOnChangeAfterSelection(
selectedRow,
onChange,
props.name,
- props.columnModel.column,
+ column,
usedFormNumber,
props.foreignKeyData,
setValue
@@ -413,7 +469,7 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
dropdownReference.read(newPageLimit, logObj).then((page: any) => {
setCurrentDropdownPage(page);
- return populateDropdownRows(page, pageLimit, logObj.stack, stackPath, requestCauses, reloadStartTime);
+ return populateDropdownRows(page, checkedRow, pageLimit, logObj.stack, stackPath, requestCauses, reloadStartTime);
}).then(() => {
setShowSpinner(false);
@@ -423,9 +479,27 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
})
}
+
const renderDropdownOptions = (onChange: any) => {
if (!dropdownInitialized) return;
+ const renderOptionRow = (row: any) => (
+ onRowSelected(row.tuple, onChange)}
+ disabled={row.isDisabled}
+ >
+ {row.tuple.uniqueId === checkedRow?.uniqueId && }
+
+
+
+
+
+ )
+
if (dropdownRows.length === 0) {
// return a special row that doesn't use Dropdown.Item so it won't be selectable
return (
@@ -442,24 +516,22 @@ const ForeignkeyDropdownField = (props: ForeignkeyDropdownFieldProps): JSX.Eleme
)
}
- return dropdownRows.map((row: DropdownRow) => {
- return (
- onRowSelected(row.tuple, onChange)}
- disabled={row.isDisabled}
- >
- {row.tuple.uniqueId === checkedRow?.uniqueId && }
-
-
-
-
- )
- })
+
+ return dropdownRows.map((row: DropdownRow) => (
+
+ {row.disabledType ?
+ // only add tooltip if single select and disabled
+
+ {renderOptionRow(row)}
+
+ : renderOptionRow(row)
+ }
+
+ ))
}
const rules: any = {};
diff --git a/src/components/input-switch/foreignkey-field.tsx b/src/components/input-switch/foreignkey-field.tsx
index e49d307e4..5b9346ce0 100644
--- a/src/components/input-switch/foreignkey-field.tsx
+++ b/src/components/input-switch/foreignkey-field.tsx
@@ -26,10 +26,7 @@ import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
// utils
import { RECORDSET_DEFAULT_PAGE_SIZE } from '@isrd-isi-edu/chaise/src/utils/constants';
import {
- callOnChangeAfterSelection,
- clearForeignKeyData,
- createForeignKeyReference,
- validateForeignkeyValue
+ callOnChangeAfterSelection, clearForeignKeyData, createForeignKeyReference, validateForeignkeyValue
} from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
import { makeSafeIdAttr } from '@isrd-isi-edu/chaise/src/utils/string-utils';
import { isStringAndNotEmpty } from '@isrd-isi-edu/chaise/src/utils/type-utils';
@@ -85,7 +82,6 @@ const ForeignkeyField = (props: ForeignkeyFieldProps): JSX.Element => {
const { setValue, getValues } = useFormContext();
const [recordsetModalProps, setRecordsetModalProps] = useState(null);
-
const [showSpinner, setShowSpinner] = useState(false);
const ellipsisRef = useRef(null);
@@ -101,9 +97,15 @@ const ForeignkeyField = (props: ForeignkeyFieldProps): JSX.Element => {
* make sure the underlying raw columns as well as foreignkey data are also emptied.
*/
const onClear = () => {
+ const column = props.columnModel.column;
+
+ if (props.foreignKeyCallbacks?.updateBulkForeignKeySelectedRows) {
+ props.foreignKeyCallbacks.updateBulkForeignKeySelectedRows(usedFormNumber);
+ }
+
clearForeignKeyData(
props.name,
- props.columnModel.column,
+ column,
usedFormNumber,
props.foreignKeyData,
setValue
@@ -146,7 +148,20 @@ const ForeignkeyField = (props: ForeignkeyFieldProps): JSX.Element => {
getValues
);
+ let currentSelectedRow;
+ const inputFKData = props.foreignKeyData?.current[`c_${usedFormNumber}-${props.columnModel.column.RID}`];
+ if (inputFKData) {
+ currentSelectedRow = {
+ displayname: {
+ value: getValues(props.name),
+ isHTML: false
+ },
+ uniqueId: props.columnModel.column.generateUniqueId(inputFKData)
+ }
+ }
+
setRecordsetModalProps({
+ initialSelectedRows: currentSelectedRow ? [currentSelectedRow] : undefined,
parentReference: props.parentReference,
parentTuple: props.parentTuple,
initialReference: ref.contextualize.compactSelectForeignKey,
@@ -170,12 +185,18 @@ const ForeignkeyField = (props: ForeignkeyFieldProps): JSX.Element => {
hideRecordsetModal();
const selectedRow = selectedRows[0];
+ const column = props.columnModel.column;
+
+ // if the recordedit page's table is an association table with a unique key pair, track the selected rows
+ if (props.foreignKeyCallbacks?.updateBulkForeignKeySelectedRows) {
+ props.foreignKeyCallbacks.updateBulkForeignKeySelectedRows(usedFormNumber, selectedRow);
+ }
callOnChangeAfterSelection(
selectedRow,
onChange,
props.name,
- props.columnModel.column,
+ column,
usedFormNumber,
props.foreignKeyData,
setValue
diff --git a/src/components/input-switch/input-field.tsx b/src/components/input-switch/input-field.tsx
index 55b846437..0a401e0a9 100644
--- a/src/components/input-switch/input-field.tsx
+++ b/src/components/input-switch/input-field.tsx
@@ -131,7 +131,7 @@ const InputField = ({
additionalControllerRules,
}: InputFieldCompProps): JSX.Element => {
- const { setValue, control, clearErrors ,trigger} = useFormContext();
+ const { setValue, control, clearErrors, trigger} = useFormContext();
controllerRules = isObjectAndNotNull(controllerRules) ? controllerRules : {};
if (requiredInput) {
@@ -191,6 +191,7 @@ const InputField = ({
if (handleChange && !handleChange(e)) {
return;
}
+
field.onChange(e);
field.onBlur();
};
diff --git a/src/components/modals/recordset-modal.tsx b/src/components/modals/recordset-modal.tsx
index 9dad5d04f..c813800d9 100644
--- a/src/components/modals/recordset-modal.tsx
+++ b/src/components/modals/recordset-modal.tsx
@@ -49,7 +49,7 @@ export type RecordestModalProps = {
* and instead is just going to call this function. This is done this way
* so we can apply the logic to disable the submit button
*/
- onSelectedRowsChanged?: (SelectedRow: SelectedRow[]) => boolean,
+ onSelectedRowsChanged?: (SelectedRow: SelectedRow[]) => boolean | string,
/**
* The function that will be called on submit
* Note: the modal won't close on submit and if that's the expected behavior,
@@ -126,7 +126,7 @@ const RecordsetModal = ({
* This will also allow us to set the state of submit button
*/
const [submittedRows, setSubmittedRows] = useState(() => (
- Array.isArray(recordsetProps.initialSelectedRows) ? recordsetProps.initialSelectedRows : []
+ (Array.isArray(recordsetProps.initialSelectedRows) && selectMode !== RecordsetSelectMode.SINGLE_SELECT) ? recordsetProps.initialSelectedRows : []
));
/**
@@ -156,8 +156,7 @@ const RecordsetModal = ({
// against it.
if (submittedRows.length === 0) return;
submit();
- }
- else {
+ } else {
let cannotSubmit = false;
if (onSelectedRowsChanged) {
cannotSubmit = onSelectedRowsChanged(submittedRows) === false;
@@ -251,6 +250,16 @@ const RecordsetModal = ({
>
)
break;
+ case RecordsetDisplayMode.FK_POPUP_BULK_CREATE:
+ submitText = 'Continue';
+ submitTooltip = (
+ <>
+ Submit the selected records to fill in
+
+ forms .
+ >
+ )
+ break;
}
let uiContextTitles: TitleProps[] | undefined, // the ui contexts that should be passed to recordset for the next level
@@ -291,6 +300,18 @@ const RecordsetModal = ({
);
break;
+ case RecordsetDisplayMode.FK_POPUP_BULK_CREATE:
+ titleEl = (
+
+ Select a set of
+
+
+ for
+
+
+
+ );
+ break;
case RecordsetDisplayMode.PURE_BINARY_POPUP_ADD:
uiContextTitles = [{ displayname: displayname }];
titleEl = (
diff --git a/src/components/record/related-table-actions.tsx b/src/components/record/related-table-actions.tsx
index 57459ab0d..de9fa5fbe 100644
--- a/src/components/record/related-table-actions.tsx
+++ b/src/components/record/related-table-actions.tsx
@@ -17,11 +17,9 @@ import { CommentDisplayModes } from '@isrd-isi-edu/chaise/src/models/displayname
import { LogActions, LogParentActions, LogReloadCauses, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
import { RecordRelatedModel } from '@isrd-isi-edu/chaise/src/models/record';
import {
- RecordsetConfig,
- RecordsetDisplayMode,
- RecordsetProps,
- RecordsetSelectMode,
- SelectedRow,
+ DisabledRow, RecordsetConfig,
+ RecordsetDisplayMode, RecordsetProps,
+ RecordsetSelectMode, SelectedRow
} from '@isrd-isi-edu/chaise/src/models/recordset';
// services
@@ -347,9 +345,9 @@ const RelatedTableActions = ({
logStackPath: string,
requestCauses?: any,
reloadStartTime?: any
- ): Promise<{ page: any, disabledRows?: any }> => {
+ ): Promise<{ page: any, disabledRows?: DisabledRow[] }> => {
return new Promise((resolve, reject) => {
- const disabledRows: any = [];
+ const disabledRows: DisabledRow[] = [];
let action = LogActions.LOAD,
newStack = logStack;
@@ -369,9 +367,9 @@ const RelatedTableActions = ({
.then(function (newPage: any) {
newPage.tuples.forEach(function (newTuple: any) {
const index = page.tuples.findIndex(function (tuple: any) {
- return tuple.uniqueId == newTuple.uniqueId;
+ return tuple.uniqueId === newTuple.uniqueId;
});
- if (index > -1) disabledRows.push(page.tuples[index]);
+ if (index > -1) disabledRows.push({tuple: page.tuples[index] });
});
resolve({ disabledRows: disabledRows, page: page });
diff --git a/src/components/recordedit/form-container.tsx b/src/components/recordedit/form-container.tsx
index 15ed9e993..b71aefd11 100644
--- a/src/components/recordedit/form-container.tsx
+++ b/src/components/recordedit/form-container.tsx
@@ -29,7 +29,6 @@ const FormContainer = ({
columnModels, config, forms, onSubmitValid, onSubmitInvalid, removeForm
} = useRecordedit();
-
const { handleSubmit } = useFormContext();
const formContainer = useRef(null);
@@ -77,6 +76,7 @@ const FormContainer = ({
const handleRemoveForm = (formIndex: number, formNumber: number) => {
setRemoveFormIndex(formNumber);
setRemoveClicked(true);
+
removeForm([formIndex]);
};
diff --git a/src/components/recordedit/form-row.tsx b/src/components/recordedit/form-row.tsx
index ec4d4eca6..3e9b685a1 100644
--- a/src/components/recordedit/form-row.tsx
+++ b/src/components/recordedit/form-row.tsx
@@ -13,6 +13,7 @@ import { CommentDisplayModes } from '@isrd-isi-edu/chaise/src/models/displayname
// utils
import { getDisabledInputValue } from '@isrd-isi-edu/chaise/src/utils/input-utils';
+import { disabledTuplesPromise } from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
import ResizeSensor from 'css-element-queries/src/ResizeSensor';
import { isObjectAndKeyDefined } from '@isrd-isi-edu/chaise/src/utils/type-utils';
import { makeSafeIdAttr } from '@isrd-isi-edu/chaise/src/utils/string-utils';
@@ -63,6 +64,8 @@ const FormRow = ({
columnPermissionErrors,
foreignKeyData,
waitingForForeignKeyData,
+ bulkForeignKeySelectedRows,
+ updateBulkForeignKeySelectedRows,
getRecordeditLogStack,
getRecordeditLogAction,
showCloneSpinner,
@@ -161,10 +164,10 @@ const FormRow = ({
* NOTE: it appears this useEffect is triggering after the "full repaint" even if there was a delay
*/
if (columnModelIndex === 0) {
- // only run this on the first form row to keep track of total forms visible
- if (!formsRef || !formsRef.current || !showCloneSpinner) return;
+ // only run this on the first form row to keep track of total forms visible
+ if (!formsRef || !formsRef.current || !showCloneSpinner) return;
- if (formsRef.current.children.length === forms.length) setShowCloneSpinner(false);
+ if (formsRef.current.children.length === forms.length) setShowCloneSpinner(false);
}
}, [forms, removeClicked]);
@@ -291,15 +294,16 @@ const FormRow = ({
};
const renderInput = (formNumber: number, formIndex?: number) => {
- const colName = columnModel.column.name;
- const colRID = columnModel.column.RID;
+ const column = columnModel.column;
+ const colName = column.name;
+ const colRID = column.RID;
const isDisabled = getIsDisabled(formNumber, formNumber === MULTI_FORM_INPUT_FORM_VALUE);
let placeholder = '';
let permissionError = '';
if (isDisabled) {
- placeholder = getDisabledInputValue(columnModel.column);
+ placeholder = getDisabledInputValue(column);
// TODO: extend this for edit mode
// if value is empty string and we are in edit mode, use the previous value
@@ -312,7 +316,27 @@ const FormRow = ({
permissionError = columnPermissionErrors[colName];
}
- const safeClassNameId = `${formNumber}-${makeSafeIdAttr(columnModel.column.displayname.value)}`;
+ const safeClassNameId = `${formNumber}-${makeSafeIdAttr(column.displayname.value)}`;
+
+ const tempForeignKeyCallbacks = { ...foreignKeyCallbacks };
+ const bulkFKObject = reference.bulkCreateForeignKeyObject;
+ /**
+ * add foreignkey callbacks to generated input if:
+ * - there is a bulkCreateForeignKeyObject defined
+ * - there is a pair of columns that create a unique assocation that use the prefill behavior
+ * - the column is a foreignkey
+ * - and the column is the one used for associating to the leaf table of the association
+ */
+ if (columnModel.isLeafInUniqueBulkForeignKeyCreate) {
+ tempForeignKeyCallbacks.getDisabledTuples = disabledTuplesPromise(
+ column.reference.contextualize.compactSelectBulkForeignKey,
+ bulkFKObject.disabledRowsFilter(),
+ bulkForeignKeySelectedRows
+ );
+
+ tempForeignKeyCallbacks.updateBulkForeignKeySelectedRows = updateBulkForeignKeySelectedRows;
+ tempForeignKeyCallbacks.bulkForeignKeySelectedRows = bulkForeignKeySelectedRows;
+ }
return (
<>
@@ -343,7 +367,7 @@ const FormRow = ({
parentLogStackPath={getRecordeditLogAction(true)}
foreignKeyData={foreignKeyData}
waitingForForeignKeyData={waitingForForeignKeyData}
- foreignKeyCallbacks={foreignKeyCallbacks}
+ foreignKeyCallbacks={tempForeignKeyCallbacks}
/>
{typeof formIndex === 'number' && formIndex in showPermissionError &&
{permissionError}
diff --git a/src/components/recordedit/key-column.tsx b/src/components/recordedit/key-column.tsx
index a8ac71a05..1701f549b 100644
--- a/src/components/recordedit/key-column.tsx
+++ b/src/components/recordedit/key-column.tsx
@@ -75,6 +75,12 @@ const KeyColumn = ({
const canShowMultiFormBtn = (columnIndex: number) => {
const cm = columnModels[columnIndex];
+ // hide the button if the foreign key values for this column are part of a unique key
+ // and the other part of that key is the prefiiled column that triggered the bulkForeignKey UI
+ if (cm.isLeafInUniqueBulkForeignKeyCreate) {
+ return false
+ }
+
// if we're already showing the multi form UI, then we have to show the button
if (activeMultiForm === columnIndex) {
return true;
diff --git a/src/components/recordedit/recordedit.tsx b/src/components/recordedit/recordedit.tsx
index 2a7dd94fa..0542c5a70 100644
--- a/src/components/recordedit/recordedit.tsx
+++ b/src/components/recordedit/recordedit.tsx
@@ -9,13 +9,14 @@ import DeleteConfirmationModal, { DeleteConfirmationModalTypes } from '@isrd-isi
import FormContainer from '@isrd-isi-edu/chaise/src/components/recordedit/form-container';
import Footer from '@isrd-isi-edu/chaise/src/components/footer';
import KeyColumn from '@isrd-isi-edu/chaise/src/components/recordedit/key-column';
-import Title from '@isrd-isi-edu/chaise/src/components/title';
+import RecordsetModal from '@isrd-isi-edu/chaise/src/components/modals/recordset-modal';
import ResultsetTable from '@isrd-isi-edu/chaise/src/components/recordedit/resultset-table';
import ResultsetTableHeader from '@isrd-isi-edu/chaise/src/components/recordedit/resultset-table-header';
+import Title from '@isrd-isi-edu/chaise/src/components/title';
import UploadProgressModal from '@isrd-isi-edu/chaise/src/components/modals/upload-progress-modal';
// hooks
-import { useEffect, useLayoutEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import useAlert from '@isrd-isi-edu/chaise/src/hooks/alerts';
import useAuthn from '@isrd-isi-edu/chaise/src/hooks/authn';
import useError from '@isrd-isi-edu/chaise/src/hooks/error';
@@ -24,11 +25,15 @@ import { FormProvider, useForm } from 'react-hook-form';
import ViewerAnnotationFormContainer from '@isrd-isi-edu/chaise/src/components/recordedit/viewer-annotation-form-container';
// models
-import { LogActions, LogReloadCauses } from '@isrd-isi-edu/chaise/src/models/log';
+import { LogActions, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
import {
- RecordeditConfig, RecordeditDisplayMode,
- RecordeditModalOptions, RecordeditProps
+ appModes, RecordeditColumnModel,
+ RecordeditDisplayMode, RecordeditProps
} from '@isrd-isi-edu/chaise/src/models/recordedit';
+import {
+ RecordsetConfig, RecordsetDisplayMode,
+ RecordsetProps, RecordsetSelectMode, SelectedRow
+} from '@isrd-isi-edu/chaise/src/models/recordset';
// providers
import AlertsProvider, { ChaiseAlertType } from '@isrd-isi-edu/chaise/src/providers/alerts';
@@ -39,12 +44,14 @@ import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
import { ConfigService } from '@isrd-isi-edu/chaise/src/services/config';
// utils
-import { attachContainerHeightSensors, attachMainContainerPaddingSensor } from '@isrd-isi-edu/chaise/src/utils/ui-utils';
-import { appModes, RecordeditColumnModel } from '@isrd-isi-edu/chaise/src/models/recordedit';
+import { RECORDEDIT_MAX_ROWS, RECORDSET_DEFAULT_PAGE_SIZE } from '@isrd-isi-edu/chaise/src/utils/constants';
+import { simpleDeepCopy } from '@isrd-isi-edu/chaise/src/utils/data-utils';
import { MESSAGE_MAP } from '@isrd-isi-edu/chaise/src/utils/message-map';
+import {
+ copyOrClearValue, disabledTuplesPromise, populateCreateInitialValues
+} from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
+import { attachContainerHeightSensors, attachMainContainerPaddingSensor } from '@isrd-isi-edu/chaise/src/utils/ui-utils';
import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref';
-import { simpleDeepCopy } from '@isrd-isi-edu/chaise/src/utils/data-utils';
-import { copyOrClearValue } from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
const Recordedit = ({
appMode,
@@ -78,7 +85,7 @@ const Recordedit = ({
onSubmitSuccess={onSubmitSuccess}
onSubmitError={onSubmitError}
>
-
+
);
@@ -92,24 +99,34 @@ const Recordedit = ({
export type RecordeditInnerProps = {
parentContainer?: HTMLElement;
+ prefillRowData?: any[];
}
const RecordeditInner = ({
- parentContainer
+ parentContainer,
+ prefillRowData
}: RecordeditInnerProps): JSX.Element => {
const { validateSessionBeforeMutation } = useAuthn();
const { errors, dispatchError } = useError();
- const { addAlert } = useAlert();
+ const { addTooManyFormsAlert } = useAlert();
const {
- appMode, columnModels, config, foreignKeyData, initialized, modalOptions, reference, tuples, waitingForForeignKeyData,
- addForm, getInitialFormValues, getPrefilledDefaultForeignKeyData, forms, MAX_ROWS_TO_ADD, removeForm,
- showCloneSpinner, setShowCloneSpinner, showApplyAllSpinner, showSubmitSpinner, resultsetProps, uploadProgressModalProps, logRecordeditClientAction
+ appMode, columnModels, config, foreignKeyData, initialized, modalOptions,
+ prefillObject, bulkForeignKeySelectedRows, setBulkForeignKeySelectedRows,
+ reference, tuples, waitingForForeignKeyData, addForm, getInitialFormValues,
+ getPrefilledDefaultForeignKeyData, forms, MAX_ROWS_TO_ADD, removeForm, showCloneSpinner, setShowCloneSpinner,
+ showApplyAllSpinner, showSubmitSpinner, resultsetProps, uploadProgressModalProps, logRecordeditClientAction
} = useRecordedit()
const [formProviderInitialized, setFormProviderInitialized] = useState(false);
const [addFormsEffect, setAddFormsEffect] = useState(false);
+ // the next 3 state variables are used when there is a prefill object for starting recordedit with more than one form to associate on creation
+ const [showBulkForeignKeyModal, setShowBulkForeignKeyModal] = useState(false);
+ const [bulkForeignKeyRecordsetProps, setBulkForeignKeyRecordsetProps] = useState(null);
+ // when initializing the page, the selections in the modal that appears first should fill the first form
+ const [bulkForeignKeySelectionsFillFirstForm, setBulkForeignKeySelectionsFillFirstForm] = useState(true);
+
/**
* The following state variable and function for modifying the state are defined here instead of the recordedit context for the reason
* stated below from linked article. These properties are passed as props to the components so they only rerender when they need to instead
@@ -251,7 +268,7 @@ const RecordeditInner = ({
// data is an object of key/value pairs for each piece of key information
// { keycol1: val, keycol2: val2, ... }
// TODO should be adjusted if we changed how we're tracking the tuples
- const idx = tuples.findIndex(function (tuple: any) {
+ const idx = tuples.findIndex((tuple: any) => {
return Object.keys(data).every(function (key) {
return tuple.data[key] === data[key];
});
@@ -261,6 +278,7 @@ const RecordeditInner = ({
removedForms.push(idx);
}
});
+
removeForm(removedForms, true);
};
/**
@@ -292,20 +310,31 @@ const RecordeditInner = ({
windowRef.location.reload();
};
- // once data is fetched, initialize the form data with react hook form
+ // once data is fetched, initialize the form data with RHF
useEffect(() => {
if (!initialized) return;
+ /**
+ * used to trigger recordset select view when selecting multiple foreign key values
+ *
+ * trigger the bulk foreign key modal when there are 2 foreign keys and
+ * we know the leaf column for the relation is visible in create mode
+ *
+ * if `bulkCreateForeignKeyObject` is defined on `reference`, we know the above is true
+ */
+ if (reference.bulkCreateForeignKeyObject) openBulkForeignKeyModal();
+
const initialValues = getInitialFormValues(forms, columnModels);
methods.reset(initialValues);
// in create mode, we need to fetch the foreignkey data
// for prefilled and foreignkeys that have default values
if (appMode === appModes.CREATE) {
+ // updates React hook form state with `setValue`
getPrefilledDefaultForeignKeyData(initialValues, methods.setValue);
}
- setFormProviderInitialized(true)
+ setFormProviderInitialized(true);
}, [initialized]);
/**
@@ -334,7 +363,7 @@ const RecordeditInner = ({
setAddFormsEffect(false);
callAddForm();
- }, [addFormsEffect])
+ }, [addFormsEffect]);
const callAddForm = () => {
// converts to number type. If NaN is returned, 1 is used instead
@@ -350,56 +379,297 @@ const RecordeditInner = ({
// refactor so provider manages the forms
const numberForms = forms.length;
if ((numberFormsToAdd + numberForms) > MAX_ROWS_TO_ADD) {
- const alertMessage = `Cannot add ${numberFormsToAdd} records. Please input a value between 1 and ${MAX_ROWS_TO_ADD - numberForms}, inclusive.`;
- addAlert(alertMessage, ChaiseAlertType.ERROR);
+ // calculate the number of forms the user can still add
+ const numberFormsAllowed = MAX_ROWS_TO_ADD - numberForms
+ let alertMessage = `Cannot add ${numberFormsToAdd} records. Please input a value between 1 and ${numberFormsAllowed}, inclusive.`;
+ if (numberFormsAllowed === 0) alertMessage = `Cannot add ${numberFormsToAdd} records. Maximum number of forms already added.`;
+ addTooManyFormsAlert(alertMessage, ChaiseAlertType.ERROR);
setShowCloneSpinner(false);
return true;
}
+ const newFormsObj: { tempFormValues: any, lastFormValue: number } = createNewForms(methods.getValues(), numberFormsToAdd);
+
+ /**
+ * NOTE: This might be able to be optimized to use setValue for each value in the new forms instead of resetting EVERY form in react hook form
+ * for instance, 4 forms exist and 1 new form is added, this will call "reset" on all 5 forms
+ *
+ * Is it possible for this change to cause longer scripting time? For instance, iterating over every single cell for each new form
+ * could end up taking longer using setValue (and whatever happens in react-hook-form) vs no iteration and instead leaving it up to
+ * react-hook-form and how `methods.reset()` works
+ *
+ * A contradicting note, since each new form being added needs to render new input fields, form-row component will rerender. This
+ * means all input fields (already existing and new ones) will be rendered when new forms are added. Refactoring this might not change
+ * rendering performance at all. Maybe to prevent previous input fields from rerendering, the input-switch component should be memoized?
+ */
+ methods.reset(newFormsObj.tempFormValues);
+ };
+
+ /**
+ * creates new forms by copying values from previous form
+ *
+ * @param formValues values for ALL forms
+ * @param numberFormsToAdd the number of forms to copy values for
+ * @returns an object with the new formValues and the form number for the last form
+ */
+ const createNewForms = (formValues: any, numberFormsToAdd: number) => {
// the indices used for tracking input values in react-hook-form
const newFormValues: number[] = addForm(numberFormsToAdd);
// the index for the data from last form being cloned
const lastFormValue = newFormValues[0] - 1;
- const tempFormValues: any = methods.getValues();
+ let tempFormValues = { ...formValues };
// add data to tempFormValues to initailize new forms
for (let i = 0; i < newFormValues.length; i++) {
const formValue = newFormValues[i];
columnModels.forEach((cm: RecordeditColumnModel) => {
+ // don't copy the value for the leaf column for an assoication that is unique
+ if (cm.isLeafInUniqueBulkForeignKeyCreate) return;
+
copyOrClearValue(cm, tempFormValues, foreignKeyData.current, formValue, lastFormValue, false, true);
});
// the code above is just copying the displayed rowname for foreignkeys,
// we still need to copy the raw values
- // but we cannot go basd on visible columns since some of these data might be for invisible fks.
- reference.activeList.allOutBounds.forEach((col: any) => {
- // copy the foreignKeyData (used for domain-filter support in foreignkey-field.tsx)
- foreignKeyData.current[`c_${formValue}-${col.RID}`] = simpleDeepCopy(foreignKeyData.current[`c_${lastFormValue}-${col.RID}`]);
-
- // copy the raw data (submitted to ermrestjs)
- col.foreignKey.colset.columns.forEach((col: any) => {
- const val = tempFormValues[`c_${lastFormValue}-${col.RID}`];
- if (val === null || val === undefined) return;
- tempFormValues[`c_${formValue}-${col.RID}`] = val;
+ // but we cannot go based on visible columns since some of this data might be for invisible fks.
+ tempFormValues = setOutboundForeignKeyValues(tempFormValues, formValue, lastFormValue);
+ }
+
+ return { tempFormValues, lastFormValue };
+ }
+
+ /**
+ * set values in foreignkey data and formValues for all out foreign key columns
+ *
+ * @param formValues the existing values in the form
+ * @param formNumber the form number we are setting values for
+ * @param lastFormValue the last form number that values are copied from
+ * @param checkPrefill if prefill should be checked for copying
+ * @returns updated form values to set in react hook form
+ */
+ const setOutboundForeignKeyValues = (formValues: any, formNumber: number, lastFormValue: number, checkPrefill?: boolean) => {
+ const tempFormValues = { ...formValues };
+ reference.activeList.allOutBounds.forEach((col: any) => {
+ const bulkFKObject = reference.bulkCreateForeignKeyObject;
+ // don't copy the value if the column is the leaf column in a unique key for bullk foreign key create
+ if (bulkFKObject?.isUnique && col.name === bulkFKObject.leafColumn.name) return;
+
+ // copy the foreignKeyData (used for domain-filter support in foreignkey-field.tsx)
+ foreignKeyData.current[`c_${formNumber}-${col.RID}`] = simpleDeepCopy(foreignKeyData.current[`c_${lastFormValue}-${col.RID}`]);
+
+ if (checkPrefill) {
+ // check prefill object for the columns that are being prefilled to update the new forms since we aren't calling getPrefilledDefaultForeignKeyData()
+ if (prefillObject?.fkColumnNames.indexOf(col.name) !== -1) {
+ tempFormValues[`c_${formNumber}-${col.RID}`] = formValues[`c_${lastFormValue}-${col.RID}`];
+ }
+ }
+
+ // copy the raw data (submitted to ermrestjs)
+ col.foreignKey.colset.columns.forEach((col: any) => {
+ const val = formValues[`c_${lastFormValue}-${col.RID}`];
+ if (val === null || val === undefined) return;
+
+ tempFormValues[`c_${formNumber}-${col.RID}`] = val;
+ });
+ });
+
+ return tempFormValues;
+ }
+
+ // show the prefill bulk foreign key modal if we have a prefill object and bulk foreign key recordset props
+ const openBulkForeignKeyModal = () => {
+ const bulkFKObject = reference.bulkCreateForeignKeyObject;
+ // check for bulkCreateForeignKeyObject being defined since a malformed prefillObject should be ignored
+ // and the `bulkCreateForeignKeyObject` constructor will handle those malformed cases
+ if (!bulkFKObject) return;
+
+ const domainRef: any = bulkFKObject.leafColumn.reference;
+ const andFilters: any[] = bulkFKObject.andFiltersForLeaf();
+
+ const modalReference = domainRef.addFacets(andFilters).contextualize.compactSelectBulkForeignKey;
+
+ const recordsetConfig: RecordsetConfig = {
+ viewable: false,
+ editable: false,
+ deletable: false,
+ sortable: true,
+ selectMode: RecordsetSelectMode.MULTI_SELECT,
+ disableFaceting: false,
+ displayMode: RecordsetDisplayMode.FK_POPUP_BULK_CREATE
+ };
+
+ const stackElement = LogService.getStackNode(
+ LogStackTypes.FOREIGN_KEY,
+ domainRef.table,
+ { source: domainRef.compressedDataSource, entity: true, picker: 1 }
+ );
+
+ const logInfo = {
+ logStack: [stackElement],
+ logStackPath: LogService.getStackPath(null, LogStackPaths.FOREIGN_KEY_BULK_POPUP),
+ };
+
+ let getDisabledTuples;
+ if (bulkFKObject.isUnique) {
+ /**
+ * The existing rows in this table must be disabled so users doesn't resubmit them.
+ *
+ * set getDisabledTuples again since the selected rows could have changed since the last time the modal was opened
+ * selected rows can be changed by updating a single foreign key input, removing the value, or removing a form entirely
+ */
+ getDisabledTuples = disabledTuplesPromise(
+ domainRef.contextualize.compactSelectBulkForeignKey,
+ bulkFKObject.disabledRowsFilter(),
+ bulkForeignKeySelectedRows
+ );
+ }
+
+ const pageSize = modalReference.display.defaultPageSize ? modalReference.display.defaultPageSize : RECORDSET_DEFAULT_PAGE_SIZE;
+
+ // set recordset select view then set selected rows on "submit"
+ setBulkForeignKeyRecordsetProps({
+ initialReference: modalReference,
+ initialPageLimit: pageSize,
+ config: recordsetConfig,
+ logInfo: logInfo,
+ parentReference: reference,
+ getDisabledTuples
+ });
+
+ setShowBulkForeignKeyModal(true);
+ }
+
+ const onBulkForeignKeyModalRowsChanged = (rows: SelectedRow[]) => {
+ // return "false" to disable submit button in modal
+ let numForms = forms.length;
+
+ // if we fill the first form, then reduce our calculation by 1
+ // NOTE: forms.length "should" be 1 at this point before subtracting 1
+ if (bulkForeignKeySelectionsFillFirstForm) numForms--;
+
+ if (rows.length + numForms < RECORDEDIT_MAX_ROWS) {
+ return true;
+ }
+
+ // there are too many forms trying to be added
+ const numberFormsAllowed = RECORDEDIT_MAX_ROWS - numForms;
+ let alertMessage = `Cannot select ${rows.length} records. Please input a value between 1 and ${numberFormsAllowed}, inclusive.`;
+ if (numberFormsAllowed === 0) alertMessage = `Cannot select ${rows.length} records. Maximum number of forms already added.`;
+
+ return alertMessage;
+ }
+
+ // user closes the modal without making any selections
+ const closeBulkForeignKeyCB = () => {
+ // if the page was loaded with a modal showing and it is dismissed, update app state variable and do nothing else
+ // ensure `bulkForeignKeySelectedRows` is initialized with an empty value
+ if (bulkForeignKeySelectionsFillFirstForm) {
+ setBulkForeignKeySelectionsFillFirstForm(false);
+ setBulkForeignKeySelectedRows([null]);
+ }
+
+ setShowBulkForeignKeyModal(false);
+ }
+
+ /**
+ * user makes selections in the multi select foreign key modal and clicks submit
+ * this function updates the selected rows (if the foreign keys are part of a unique key) and fills in the new forms based
+ * on the state of the app and the number of selected rows
+ *
+ * if the first modal is submitted after load of app page, one of the selected values will
+ * fill in the first form. After that, the selections will copy the last form's values or use default
+ * values based on what is set in the annotation (pending annotation implementation)
+ *
+ * NOTE: This should only be called if reference.bulkCreateForeignKeyObject is defined
+ *
+ * @param modalSelectedRows the selected rows from the foreign key modal
+ */
+ const submitBulkForeignKeyCB = (modalSelectedRows: SelectedRow[]) => {
+ setShowBulkForeignKeyModal(false);
+
+ // should not happen since submit button is greyed out
+ if (!modalSelectedRows || modalSelectedRows.length === 0) return;
+
+ const bulkFKObject = reference.bulkCreateForeignKeyObject
+ if (bulkFKObject.isUnique) {
+ /**
+ * copy modalSelectedRows 2nd to preserve indexes in bulkForeignKeySelectedRows
+ *
+ * this function does 2 different things:
+ * - fills the first form and adds new forms
+ * - OR only adds new forms
+ *
+ * in both cases, the selected rows are added to the forms in the same order that
+ * the rows were selected in the modal. As we are adding new forms, we copy the
+ * values from the modalSelectedRows in the same index order
+ **/
+ const newRows = [...bulkForeignKeySelectedRows, ...modalSelectedRows]
+ setBulkForeignKeySelectedRows(newRows);
+ }
+
+ // recordedit has already been initialized so start adding new forms
+ const tempFormValues = methods.getValues();
+ let initialValues = tempFormValues,
+ startFormNumber: number;
+
+ if (bulkForeignKeySelectionsFillFirstForm) {
+ if (modalSelectedRows.length > 1) {
+ initialValues = createNewForms(tempFormValues, modalSelectedRows.length - 1).tempFormValues;
+ }
+
+ startFormNumber = 1;
+
+ setBulkForeignKeySelectionsFillFirstForm(false);
+ } else {
+ // use default values to fill new forms
+ const newFormValues: number[] = addForm(modalSelectedRows.length);
+ const newRowsModel = populateCreateInitialValues(columnModels, newFormValues, prefillObject, prefillRowData);
+ const newValues = newRowsModel.values;
+
+ foreignKeyData.current = {
+ ...foreignKeyData.current,
+ ...newRowsModel.foreignKeyData
+ };
+
+ // NOTE: should we call getPrefilledDefaultForeignKeyData here instead of checking the prefillObject?
+
+ startFormNumber = newFormValues[0];
+ newFormValues.forEach((formNumber: number) => {
+ // copy values to object we want to use for RHF
+ Object.keys(newValues).forEach((key: string) => {
+ // we want to make sure we are only copying the data for newly created rows
+ if (key.startsWith(`c_${formNumber}-`)) {
+ initialValues[key] = newValues[key];
+ }
});
+
+ initialValues = setOutboundForeignKeyValues(initialValues, formNumber, startFormNumber - 1, true);
});
}
- /**
- * NOTE: This might be able to be optimized to use setValue for each value in the new forms instead of resetting EVERY form in react hook form
- * for instance, 4 forms exist and 1 new form is added, this will call "reset" on all 5 forms
- *
- * Is it possible for this change to cause longer scripting time? For instance, iterating over every single cell for each new form
- * could end up taking longer using setValue (and whatever happens in react-hook-form) vs no iteration and instead leaving it up to
- * react-hook-form and how `methods.reset()` works
- *
- * A contradicting note, since each new form being added needs to render new input fields, form-row component will rerender. This
- * means all input fields (already existing and new ones) will be rendered when new forms are added. Refactoring this might not change
- * rendering performance at all. Maybe to prevent previous input fields from rerendering, the input-switch component should be memoized?
- */
- methods.reset(tempFormValues);
- };
+ // iterate selectedRows to fill in the fkey information
+ modalSelectedRows.forEach((row: SelectedRow, index: number) => {
+ if (foreignKeyData && foreignKeyData.current) {
+ foreignKeyData.current[`c_${startFormNumber + index}-${bulkFKObject.leafColumn.RID}`] = row.data;
+ }
+
+ // find the raw value of the fk columns that correspond to the selected row
+ // since we've already added a not-null hidden filter, the values will be not-null.
+ bulkFKObject.leafColumn.foreignKey.colset.columns.forEach((col: any) => {
+ const referencedCol = bulkFKObject.leafColumn.foreignKey.mapping.get(col);
+
+ // setFunction(`c_${formNumber}-${col.RID}`, selectedRow.data[referencedCol.name]);
+ initialValues[`c_${startFormNumber + index}-${col.RID}`] = row.data[referencedCol.name];
+ });
+
+ // update "display" value
+ initialValues[`c_${startFormNumber + index}-${bulkFKObject.leafColumn.RID}`] = row.displayname.value;
+ });
+
+ // required to set values in all new forms in the RHF model
+ methods.reset(initialValues);
+ }
const renderSpinner = () => {
if (errors.length === 0 && (showDeleteSpinner || showSubmitSpinner || showCloneSpinner || showApplyAllSpinner)) {
@@ -490,7 +760,7 @@ const RecordeditInner = ({
const renderBulkDeleteButton = () => {
if (!canShowBulkDelete) return;
- const tooltip = canEnableBulkDelete ? 'Delete the displayed set of records.': 'None of the displayed records can be deleted.';
+ const tooltip = canEnableBulkDelete ? 'Delete the displayed set of records.' : 'None of the displayed records can be deleted.';
return
@@ -578,6 +848,16 @@ const RecordeditInner = ({
onCancel={uploadProgressModalProps.onCancel}
/>
}
+ {showBulkForeignKeyModal && bulkForeignKeyRecordsetProps &&
+
+ }
>);
}
@@ -705,42 +985,63 @@ const RecordeditInner = ({
:
-
-
- Qty
-
-
-
+ <>
+
+
+ Qty
+
+
+
+
+ {
+ setShowCloneSpinner(true);
+ setAddFormsEffect(true);
+ }}
+ type='button'
+ disabled={!allFormDataLoaded}
+ >
+ Clone
+
+
+
+
+ {bulkForeignKeyRecordsetProps &&
+ // only show bulk foreign key modal button if we started with a bulk foreing key picker
+ // bulkForeignKeyRecordsetProps only get set if there is a `reference.bulkCreateForeignKeyObject` defined when the recordedit app loads
{
- setShowCloneSpinner(true);
- setAddFormsEffect(true);
- }}
+ id='recordedit-add-more'
+ className='chaise-btn chaise-btn-sm chaise-btn-secondary'
+ onClick={openBulkForeignKeyModal}
type='button'
- disabled={!allFormDataLoaded}
>
- Clone
+
+ Add more
-
-
+ }
+ >
}
}
diff --git a/src/components/recordset/recordset-table.tsx b/src/components/recordset/recordset-table.tsx
index 80f0e1056..44e7d595e 100644
--- a/src/components/recordset/recordset-table.tsx
+++ b/src/components/recordset/recordset-table.tsx
@@ -14,7 +14,11 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
// models
import useRecordset from '@isrd-isi-edu/chaise/src/hooks/recordset';
import { LogActions, LogReloadCauses } from '@isrd-isi-edu/chaise/src/models/log';
-import { RecordsetConfig, RecordsetDisplayMode, RecordsetSelectMode, SelectedRow, SortColumn } from '@isrd-isi-edu/chaise/src/models/recordset';
+import {
+ DisabledRow, DisabledRowType, RecordsetConfig,
+ RecordsetDisplayMode, RecordsetSelectMode,
+ SelectedRow, SortColumn
+} from '@isrd-isi-edu/chaise/src/models/recordset';
// utils
import { CUSTOM_EVENTS } from '@isrd-isi-edu/chaise/src/utils/constants';
@@ -59,25 +63,62 @@ const RecordsetTable = ({
// used for related tables to fire an event when the content has loaded to scroll back to the top of the related table
const [pagingSuccess, setPagingSuccess] = useState(false);
+ type RowConfig = {
+ isSelected: boolean;
+ isDisabled: boolean;
+ disabledType: DisabledRowType | undefined;
+ }
/**
* capture the state of selected and disabled of rows in here so
* we don't have to populate this multiple times
*/
- let isRowSelected = Array(page ? page.length : 0).fill(false);
- if (page && page.length && Array.isArray(selectedRows) && selectedRows.length > 0) {
- isRowSelected = page.tuples.map((tuple: any) => (
- // ermrestjs always returns a string for uniqueId, but internally we don't
- // eslint-disable-next-line eqeqeq
- selectedRows.some((obj) => obj.uniqueId == tuple.uniqueId)
- ));
- }
- let isRowDisabled = Array(page ? page.length : 0).fill(false);
- if (page && page.length && Array.isArray(disabledRows) && disabledRows.length > 0) {
- isRowDisabled = page.tuples.map((tuple: any) => (
- // ermrestjs always returns a string for uniqueId, but internally we don't
- // eslint-disable-next-line eqeqeq
- disabledRows.some((obj) => obj.uniqueId == tuple.uniqueId)
- ));
+ let rowDetails: RowConfig[] = Array(page ? page.length : 0).fill({
+ isSelected: false,
+ isDisabled: false,
+ disabledType: undefined
+ });
+
+ const hasSelectedRows = Array.isArray(selectedRows) && selectedRows.length > 0,
+ hasDisabledRows = Array.isArray(disabledRows) && disabledRows.length > 0;
+
+ if (page && page.length && (hasSelectedRows || hasDisabledRows)) {
+ const tempRowDetails: RowConfig[] = []
+ for (let i = 0; i < page.tuples.length; i++) {
+ const tuple = page.tuples[i];
+ const rowConfig: RowConfig = {
+ isSelected: false,
+ isDisabled: false,
+ disabledType: undefined
+ };
+ // page.tuples.forEach((tuple: any, index: number) => {
+ if (hasSelectedRows) {
+ const row = selectedRows.find((obj: SelectedRow) => {
+ // ermrestjs always returns a string for uniqueId, but internally we don't
+ // eslint-disable-next-line eqeqeq
+ return obj.uniqueId == tuple.uniqueId
+ });
+
+ if (row) rowConfig.isSelected = true;
+ }
+
+ if (hasDisabledRows) {
+ const row = disabledRows.find((obj: DisabledRow) => {
+ // ermrestjs always returns a string for uniqueId, but internally we don't
+ // eslint-disable-next-line eqeqeq
+ return obj.tuple.uniqueId == tuple.uniqueId
+ });
+
+ if (row) {
+ rowConfig.isDisabled = true;
+ rowConfig.disabledType = row.disabledType;
+ }
+ }
+
+ tempRowDetails[i] = rowConfig;
+ // });
+ }
+
+ rowDetails = tempRowDetails;
}
/**
@@ -156,8 +197,8 @@ const RecordsetTable = ({
const res: SelectedRow[] = Array.isArray(currRows) ? [...currRows] : [];
if (!page) return res;
page.tuples.forEach((tuple: any, index: number) => {
- if (isRowDisabled[index]) return;
- if (!isRowSelected[index]) {
+ if (rowDetails[index].isDisabled) return;
+ if (!rowDetails[index].isSelected) {
res.push({
displayname: tuple.displayname,
uniqueId: tuple.uniqueId,
@@ -192,7 +233,8 @@ const RecordsetTable = ({
*/
const onSelectChange = (tuple: any) => {
setSelectedRows((currRows: SelectedRow[]) => {
- const res: SelectedRow[] = Array.isArray(currRows) ? [...currRows] : [];
+ // since single select can have a row selected when the modal loads (foreign key input), we want to make sure the set is empty before adding to it
+ const res: SelectedRow[] = (Array.isArray(currRows) && config.selectMode !== RecordsetSelectMode.SINGLE_SELECT) ? [...currRows] : [];
// see if the tuple is list of selected rows or not
const rowIndex = res.findIndex((obj: SelectedRow) => obj.uniqueId === tuple.uniqueId);
// if it's currently selected, then we should deselect (and vice versa)
@@ -393,9 +435,10 @@ const RecordsetTable = ({
rowValues={rowValues}
tuple={tuple}
showActionButtons={showActionButtons}
- selected={isRowSelected[index]}
+ selected={rowDetails[index].isSelected}
onSelectChange={onSelectChange}
- disabled={isRowDisabled[index]}
+ disabled={rowDetails[index].isDisabled}
+ disabledType={rowDetails[index].disabledType}
/>)
})
}
diff --git a/src/components/recordset/recordset.tsx b/src/components/recordset/recordset.tsx
index c1c9f7885..88dcc6c0e 100644
--- a/src/components/recordset/recordset.tsx
+++ b/src/components/recordset/recordset.tsx
@@ -303,15 +303,15 @@ const RecordsetInner = ({
})
};
- const config: any = {
+ const requestConfig: any = {
skipHTTP401Handling: true,
headers: {}
};
- config.headers[windowRef.ERMrest.contextHeaderName] = logObj;
+ requestConfig.headers[windowRef.ERMrest.contextHeaderName] = logObj;
// attributegroup/CFDE:saved_query/RID;last_execution_status
const updateSavedQueryUrl = windowRef.location.origin + savedQueryConfig.ermrestAGPath + '/RID;' + lastExecutedColumnName;
- ConfigService.http.put(updateSavedQueryUrl, rows, config).then(() => {
+ ConfigService.http.put(updateSavedQueryUrl, rows, requestConfig).then(() => {
// do nothing
}).catch((error: any) => {
$log.warn('saved query last executed time could not be updated');
diff --git a/src/components/recordset/saved-query-dropdown.tsx b/src/components/recordset/saved-query-dropdown.tsx
index bd061eafb..462203a7a 100644
--- a/src/components/recordset/saved-query-dropdown.tsx
+++ b/src/components/recordset/saved-query-dropdown.tsx
@@ -193,6 +193,7 @@ const SavedQueryDropdown = ({
const columnModels: any[] = [];
const rows: any[] = [];
const tempSavedQueryReference = savedQueryReference.contextualize.entryCreate;
+ tempSavedQueryReference.computeBulkCreateForeignKeyObject(null);
// set columns list
tempSavedQueryReference.columns.forEach((col: any) => {
diff --git a/src/components/recordset/table-row.tsx b/src/components/recordset/table-row.tsx
index 7c9757f42..13bb40a2e 100644
--- a/src/components/recordset/table-row.tsx
+++ b/src/components/recordset/table-row.tsx
@@ -13,7 +13,7 @@ import useRecordset from '@isrd-isi-edu/chaise/src/hooks/recordset';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
// models
-import { RecordsetConfig, RecordsetDisplayMode, RecordsetSelectMode } from '@isrd-isi-edu/chaise/src/models/recordset';
+import { DisabledRowType, RecordsetConfig, RecordsetDisplayMode, RecordsetSelectMode } from '@isrd-isi-edu/chaise/src/models/recordset';
import { LogActions, LogParentActions, LogReloadCauses, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
// services
@@ -22,11 +22,12 @@ import $log from '@isrd-isi-edu/chaise/src/services/logger';
import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
// utils
-import { addQueryParamsToURL } from '@isrd-isi-edu/chaise/src/utils/uri-utils';
-import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref';
+import { CLASS_NAMES, CUSTOM_EVENTS } from '@isrd-isi-edu/chaise/src/utils/constants';
import { getRandomInt } from '@isrd-isi-edu/chaise/src/utils/math-utils';
+import { disabledRowTooltip } from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
import { fireCustomEvent } from '@isrd-isi-edu/chaise/src/utils/ui-utils';
-import { CLASS_NAMES, CUSTOM_EVENTS } from '@isrd-isi-edu/chaise/src/utils/constants';
+import { addQueryParamsToURL } from '@isrd-isi-edu/chaise/src/utils/uri-utils';
+import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref';
type TableRowProps = {
config: RecordsetConfig,
@@ -39,7 +40,8 @@ type TableRowProps = {
showActionButtons: boolean,
selected: boolean,
onSelectChange: (tuple: any) => void,
- disabled: boolean
+ disabled: boolean,
+ disabledType?: DisabledRowType
}
type ReadMoreStateProps = {
@@ -56,7 +58,8 @@ const TableRow = ({
showActionButtons,
selected,
onSelectChange,
- disabled
+ disabled,
+ disabledType
}: TableRowProps): JSX.Element => {
/**
@@ -120,7 +123,13 @@ const TableRow = ({
* - the parent says that it should be disabled
* - we're waiting for the delete request
*/
- const rowDisabled = disabled || waitingForDelete;
+ const rowDisabled = (disabled && !selected) || waitingForDelete;
+ let singleSelectIconTooltip = `Select${selected ? 'ed' : ''}`;
+
+ if (rowDisabled && disabledType) {
+ // disabled will be a small grey button without an icon
+ singleSelectIconTooltip = disabledRowTooltip(disabledType);
+ }
// TODO: logging
const initializeOverflows = () => {
@@ -179,7 +188,7 @@ const TableRow = ({
// attach an onload function that updates how many have loaded
const imgTags = Array.from(rowContainer.current.querySelectorAll(
`img.${CLASS_NAMES.CONTENT_LOADED}, .${CLASS_NAMES.CONTENT_LOADED} img`
- )).filter(img => !img.complete);
+ )).filter(img => !img.complete);
if (imgTags.length > numImages.current) numImages.current = imgTags.length
const onImageLoad = () => {
@@ -409,24 +418,21 @@ const TableRow = ({
}
const renderActionButtons = () => {
-
switch (config.selectMode) {
case RecordsetSelectMode.SINGLE_SELECT:
- return (
-
+ onSelectChange(tuple)}
>
- onSelectChange(tuple)}
- >
-
-
-
- )
+ {selected && }
+
+ );
case RecordsetSelectMode.MULTI_SELECT:
return (
@@ -564,13 +570,11 @@ const TableRow = ({
ref={rowContainer}
style={{ 'position': 'relative' }}
>
- {showActionButtons &&
-
-
- {renderActionButtons()}
-
-
- }
+ {showActionButtons &&
+
+ {renderActionButtons()}
+
+ }
{renderCells()}
{showDeleteConfirmationModal &&
diff --git a/src/models/log.ts b/src/models/log.ts
index 3daa07d97..acfdde9cd 100644
--- a/src/models/log.ts
+++ b/src/models/log.ts
@@ -189,12 +189,13 @@ export enum LogStackPaths {
ADD_PB_POPUP= 'related-link-picker',
UNLINK_PB_POPUP= 'related-unlink-picker',
FOREIGN_KEY_POPUP= 'fk-picker',
+ FOREIGN_KEY_BULK_POPUP= 'fk-bulk-picker',
FOREIGN_KEY_DROPDOWN= 'fk-dropdown',
FACET_POPUP= 'facet-picker',
SAVED_QUERY_CREATE_POPUP= 'saved-query-entity',
SAVED_QUERY_SELECT_POPUP= 'saved-query-picker',
// these two have been added to the tables that recordedit is showing
- // (but not used in logs technically since we're not showing any controls he)
+ // (but not used in logs technically since we're not showing any controls)
RESULT_SUCCESFUL_SET= 'result-successful-set',
RESULT_FAILED_SET= 'result-failed-set',
RESULT_DISABLED_SET= 'result-disabled-set',
@@ -225,6 +226,7 @@ export enum LogParentActions {
// why we had to reload a request
export enum LogReloadCauses {
+ BULK_FK_ROWS_CHANGED= 'bulk-foreignkey-selected-rows', // selected rows for bulk foreign key picker have changed
CLEAR_ALL= 'clear-all', // clear all button
CLEAR_CFACET= 'clear-cfacet',
CLEAR_CUSTOM_FILTER= 'clear-custom-filter',
@@ -253,5 +255,5 @@ export enum LogReloadCauses {
RELATED_INLINE_DELETE= 'related-inline-delete', // a row in one of the related (inline) tables has been deleted
RELATED_INLINE_UPDATE= 'related-inline-update', // a row in one of the related (inline) tables has been edited
SORT= 'sort', // sort changed
- SEARCH_BOX= 'search-box', // search box value changed
+ SEARCH_BOX= 'search-box' // search box value changed
}
diff --git a/src/models/recordedit.ts b/src/models/recordedit.ts
index bf26b36f2..a48f14c81 100644
--- a/src/models/recordedit.ts
+++ b/src/models/recordedit.ts
@@ -1,5 +1,5 @@
import {
- RecordsetProviderGetDisabledTuples
+ RecordsetProviderGetDisabledTuples, SelectedRow
} from '@isrd-isi-edu/chaise/src/models/recordset';
export enum appModes {
@@ -88,8 +88,27 @@ export type RecordeditModalOptions = {
onClose: () => void;
}
+export type UpdateBulkForeignKeyRowsCallback = (formNumber: number, newRow?: SelectedRow) => void;
export type RecordeditForeignkeyCallbacks = {
+ /**
+ * if defined, called before loading the foreign key picker or association modal
+ *
+ * This will disable the rows in the modal popup that are already associated with the main
+ * record we are associating more rows with. This will only occur when there is a prefillObject
+ * and the association is unique
+ */
getDisabledTuples?: RecordsetProviderGetDisabledTuples,
+ /**
+ * if defined, will be called after closing the modal selector
+ *
+ * This will call a function in recordedit provider to update the selected rows for the
+ * association popup if we have a prefillObject and the association is unique
+ */
+ updateBulkForeignKeySelectedRows?: UpdateBulkForeignKeyRowsCallback,
+ /**
+ * if defined, will be used in foreign key & foreign key dropdown fields
+ */
+ bulkForeignKeySelectedRows?: (SelectedRow | null)[],
/**
* if defined, will be used for validating the foreign key value.
*
@@ -129,6 +148,10 @@ export interface RecordeditColumnModel {
* (used in viewer app to hide the columns)
*/
isHidden: boolean;
+ /**
+ * whether to trigger using the unqiue features of bulk foreign key create
+ */
+ isLeafInUniqueBulkForeignKeyCreate: boolean;
}
export interface TimestampOptions {
diff --git a/src/models/recordset.ts b/src/models/recordset.ts
index 705b558c4..963a14757 100644
--- a/src/models/recordset.ts
+++ b/src/models/recordset.ts
@@ -8,9 +8,9 @@ import { TitleProps } from '@isrd-isi-edu/chaise/src/components/title';
export type RecordsetProviderGetDisabledTuples = (
page: any, pageLimit: number, logStack: any,
logStackPath: string, requestCauses?: any, reloadStartTime?: any
-) => Promise<{ page: any, disabledRows?: any }>;
+) => Promise<{ page: any, disabledRows?: DisabledRow[] }>;
-export type RecordsetProviderOnSelectedRowsChanged = (selectedRows: SelectedRow[]) => boolean
+export type RecordsetProviderOnSelectedRowsChanged = (selectedRows: SelectedRow[]) => boolean | string
export type RecordsetProps = {
@@ -70,6 +70,7 @@ export enum RecordsetDisplayMode {
POPUP = 'popup',
FACET_POPUP = 'popup/facet',
FK_POPUP = 'popup/foreignkey',
+ FK_POPUP_BULK_CREATE = 'popup/foreignkey/bulk',
FK_POPUP_CREATE = 'popup/foreignkey/create',
FK_POPUP_EDIT = 'popup/foreignkey/edit',
PURE_BINARY_POPUP_ADD = 'popup/purebinary/add',
@@ -183,6 +184,16 @@ export type SelectedRow = {
// cannotBeRemoved?: boolean;
}
+export enum DisabledRowType {
+ ASSOCIATED= 'associated', // a row that is already associated
+ SELECTED= 'selected' // a row that is already selected in another recordedit form
+}
+
+export type DisabledRow = {
+ disabledType?: DisabledRowType;
+ tuple: any;
+}
+
export type RecordsetProviderAddUpdateCauses = (
/**
* an array of strings that will be logged with the request
diff --git a/src/pages/recordedit.tsx b/src/pages/recordedit.tsx
index fa4c21b2f..1942bd24e 100644
--- a/src/pages/recordedit.tsx
+++ b/src/pages/recordedit.tsx
@@ -22,12 +22,13 @@ import { ConfigService, ConfigServiceSettings } from '@isrd-isi-edu/chaise/src/s
import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
// utils
+import { APP_NAMES, ID_NAMES } from '@isrd-isi-edu/chaise/src/utils/constants';
+import { addAppContainerClasses } from '@isrd-isi-edu/chaise/src/utils/head-injector';
+import { MESSAGE_MAP } from '@isrd-isi-edu/chaise/src/utils/message-map';
+import { getPrefillObject } from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
import { isObjectAndKeyDefined } from '@isrd-isi-edu/chaise/src/utils/type-utils';
import { chaiseURItoErmrestURI, createRedirectLinkFromPath } from '@isrd-isi-edu/chaise/src/utils/uri-utils';
import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref';
-import { addAppContainerClasses, updateHeadTitle } from '@isrd-isi-edu/chaise/src/utils/head-injector';
-import { APP_NAMES, ID_NAMES } from '@isrd-isi-edu/chaise/src/utils/constants';
-import { MESSAGE_MAP } from '@isrd-isi-edu/chaise/src/utils/message-map';
const recordeditSettings : ConfigServiceSettings = {
appName: APP_NAMES.RECORDEDIT,
@@ -85,17 +86,23 @@ const RecordeditApp = (): JSX.Element => {
}
+ let prefillObj = null;
let logAppMode = LogAppModes.EDIT;
if (appMode === appModes.COPY) {
logAppMode = LogAppModes.CREATE_COPY;
} else if (appMode === appModes.CREATE) {
if (res.queryParams.invalidate && res.queryParams.prefill) {
logAppMode = LogAppModes.CREATE_PRESELECT;
+
+ prefillObj = getPrefillObject(res.queryParams);
} else {
logAppMode = LogAppModes.CREATE;
}
}
+ // initialize `ERMrest.BulkCreateForeignKeyObject` object on reference even if we don't have a prefillObj
+ reference.computeBulkCreateForeignKeyObject(prefillObj);
+
const logStack = [
LogService.getStackNode(
LogStackTypes.SET,
diff --git a/src/providers/alerts.tsx b/src/providers/alerts.tsx
index e9345ce10..e65924e72 100644
--- a/src/providers/alerts.tsx
+++ b/src/providers/alerts.tsx
@@ -47,6 +47,8 @@ export const AlertsContext = createContext<{
removeAlert: RemoveAlertFunction,
addURLLimitAlert: () => void,
removeURLLimitAlert: () => void,
+ addTooManyFormsAlert:(message: string, type: ChaiseAlertType) => void,
+ removeTooManyFormsAlert:() => void,
removeAllAlerts: () => void,
} |
// NOTE: since it can be null, to make sure the context is used properly with
@@ -66,8 +68,9 @@ type AlertsProviderProps = {
*/
export default function AlertsProvider({ children }: AlertsProviderProps): JSX.Element {
const [alerts, setAlerts] = useState
([]);
- // const [urlLimitAlert, setURLLimitAlert] = useState(null);
+
const urlLimitAlert = useRef(null);
+ const tooManyFormsAlert = useRef(null);
/**
* create add an alert
@@ -108,11 +111,9 @@ export default function AlertsProvider({ children }: AlertsProviderProps): JSX.E
* (we want to ensure only one alert is displayed at the time)
*/
const addURLLimitAlert = () => {
- // if (urlLimitAlert) return;
if (urlLimitAlert.current) return;
- // setURLLimitAlert(
- urlLimitAlert.current = addAlert(MESSAGE_MAP.URLLimitMessage, ChaiseAlertType.WARNING, () => urlLimitAlert.current = null)
- // );
+
+ urlLimitAlert.current = addAlert(MESSAGE_MAP.URLLimitMessage, ChaiseAlertType.WARNING, () => urlLimitAlert.current = null)
};
/**
@@ -120,9 +121,32 @@ export default function AlertsProvider({ children }: AlertsProviderProps): JSX.E
*/
const removeURLLimitAlert = () => {
if (!urlLimitAlert.current) return;
+
removeAlert(urlLimitAlert.current);
urlLimitAlert.current = null;
- // setURLLimitAlert(null);
+ }
+
+ /**
+ * display the too many forms alert
+ * (we want to ensure only one alert is displayed at the time)
+ *
+ * @param message the alert message to show. this can differ depending on how many forms can still be added
+ * @param type the type of alert to show
+ */
+ const addTooManyFormsAlert = (message: string, type: ChaiseAlertType) => {
+ if (tooManyFormsAlert.current) return;
+
+ tooManyFormsAlert.current = addAlert(message, type, () => tooManyFormsAlert.current = null)
+ }
+
+ /**
+ * remove too many forms alert
+ */
+ const removeTooManyFormsAlert = () => {
+ if (!tooManyFormsAlert.current) return;
+
+ removeAlert(tooManyFormsAlert.current);
+ tooManyFormsAlert.current = null;
}
@@ -133,7 +157,9 @@ export default function AlertsProvider({ children }: AlertsProviderProps): JSX.E
removeAlert,
removeAllAlerts,
addURLLimitAlert,
- removeURLLimitAlert
+ removeURLLimitAlert,
+ addTooManyFormsAlert,
+ removeTooManyFormsAlert
}
}, [alerts]);
diff --git a/src/providers/recordedit.tsx b/src/providers/recordedit.tsx
index fd6b3e4a5..d73a9b0b5 100644
--- a/src/providers/recordedit.tsx
+++ b/src/providers/recordedit.tsx
@@ -8,11 +8,12 @@ import useStateRef from '@isrd-isi-edu/chaise/src/hooks/state-ref';
// models
import {
appModes, LastChunkMap, PrefillObject, RecordeditColumnModel,
- RecordeditConfig, RecordeditDisplayMode, RecordeditForeignkeyCallbacks, RecordeditModalOptions
+ RecordeditConfig, RecordeditDisplayMode, RecordeditForeignkeyCallbacks,
+ RecordeditModalOptions, UpdateBulkForeignKeyRowsCallback, UploadProgressProps
} from '@isrd-isi-edu/chaise/src/models/recordedit';
import { LogActions, LogReloadCauses, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
import { NoRecordError } from '@isrd-isi-edu/chaise/src/models/errors';
-import { UploadProgressProps } from '@isrd-isi-edu/chaise/src/models/recordedit';
+import { SelectedRow } from '@isrd-isi-edu/chaise/src/models/recordset';
// providers
import { ChaiseAlertType } from '@isrd-isi-edu/chaise/src/providers/alerts';
@@ -30,8 +31,7 @@ import { updateHeadTitle } from '@isrd-isi-edu/chaise/src/utils/head-injector';
import { MESSAGE_MAP } from '@isrd-isi-edu/chaise/src/utils/message-map';
import { URL_PATH_LENGTH_LIMIT } from '@isrd-isi-edu/chaise/src/utils/constants'
import {
- allForeignKeyColumnsPrefilled,
- columnToColumnModel, getPrefillObject,
+ allForeignKeyColumnsPrefilled, columnToColumnModel, getPrefillObject,
populateCreateInitialValues, populateEditInitialValues, populateSubmissionRow
} from '@isrd-isi-edu/chaise/src/utils/recordedit-utils';
import { isObjectAndKeyDefined, isObjectAndNotNull } from '@isrd-isi-edu/chaise/src/utils/type-utils';
@@ -45,13 +45,19 @@ type ResultsetProps = {
}
export const RecordeditContext = createContext<{
- /* which mode of recordedit we are in */
+ /**
+ * which mode of recordedit we are in
+ */
appMode: string,
config: RecordeditConfig,
modalOptions?: RecordeditModalOptions,
- /* the main entity reference */
+ /**
+ * the main entity reference
+ */
reference: any,
- /* the tuples correspondeing to the displayed form */
+ /**
+ * the tuples correspondeing to the displayed form
+ */
tuples: any,
/**
* the raw data of outbound foreign keys. used in foreignkey-field to support domain-filter
@@ -60,27 +66,49 @@ export const RecordeditContext = createContext<{
*/
foreignKeyData: any,
waitingForForeignKeyData: boolean,
- /* the created column models from reference.columns */
+ /**
+ * the created column models from reference.columns
+ */
columnModels: RecordeditColumnModel[],
- /* whether a value can be updated or not (key-value pair where key is the same structure as form values ) */
+ /**
+ * whether a value can be updated or not (key-value pair where key is the same structure as form values )
+ */
canUpdateValues: { [key: string]: boolean };
- /** precomputed column permission error that should be displayed to the users */
+ /**
+ * precomputed column permission error that should be displayed to the users
+ */
columnPermissionErrors: { [columnName: string]: string };
- /* Whether the data for the main entity is fetched and the model is initialized */
+ /**
+ * Whether the data for the main entity is fetched and the model is initialized
+ */
initialized: boolean,
- /* Array of numbers for initalizing form data */
+ /**
+ * Array of numbers for initalizing form data
+ */
forms: number[],
- /* callback to add form(s) to the forms array */
+ /**
+ * callback to add form(s) to the forms array
+ */
addForm: (count: number) => number[],
- /* callback to remove from(s) from the forms array */
+ /**
+ * callback to remove from(s) from the forms array
+ */
removeForm: (indexes: number[], skipLogging?: boolean) => void,
- /* returns the initial values for all forms to display */
+ /**
+ * returns the initial values for all forms to display
+ */
getInitialFormValues: (forms: number[], columnModels: RecordeditColumnModel[]) => any,
- /* initiate the process of handling prefilled and default foreignkeys (in create mode) */
+ /**
+ * initiate the process of handling prefilled and default foreignkeys (in create mode)
+ */
getPrefilledDefaultForeignKeyData: (initialValues: any, setValue: any) => void,
- /* callback for react-hook-form to call when forms are valid */
+ /**
+ * callback for react-hook-form to call when forms are valid
+ */
onSubmitValid: (data: any) => void,
- /* callback for react-hook-form to call when forms are NOT valid */
+ /**
+ * callback for react-hook-form to call when forms are NOT valid
+ */
onSubmitInvalid: (errors: any, e?: any) => void,
/**
* whether we should show the spinner indicating cloning form data
@@ -98,12 +126,31 @@ export const RecordeditContext = createContext<{
showSubmitSpinner: boolean,
resultsetProps?: ResultsetProps,
uploadProgressModalProps?: UploadProgressProps,
- /* for updating the last contiguous chunk tracking info */
+ /**
+ * for updating the last contiguous chunk tracking info
+ */
setLastContiguousChunk: (arg0: any) => void,
- /* useRef react hook to current value */
+ /**
+ * useRef react hook to current value
+ */
lastContiguousChunkRef: any,
- /* max rows allowed to add constant */
+ /**
+ * max rows allowed to add constant
+ */
MAX_ROWS_TO_ADD: number,
+ /**
+ * the prefill object from cookie storage based on prefill query param
+ */
+ prefillObject: PrefillObject | null,
+ /**
+ * the rows that are already in use in recoredit if we have a prefill object and the association is unique
+ */
+ bulkForeignKeySelectedRows: (SelectedRow | null)[],
+ setBulkForeignKeySelectedRows: (val: (SelectedRow | null)[]) => void,
+ /**
+ * function for foreign key inputs to update the rows that are already in use in recoredit if we have a prefill object and the association is unique
+ */
+ updateBulkForeignKeySelectedRows: UpdateBulkForeignKeyRowsCallback,
/**
* log client actions
* Notes:
@@ -256,6 +303,9 @@ export default function RecordeditProvider({
// an array of unique keys to for referencing each form
const [forms, setForms] = useState([1]);
+ const [prefillObject, setPrefillObject] = useState(null);
+ const [bulkForeignKeySelectedRows, setBulkForeignKeySelectedRows] = useState<(SelectedRow | null)[]>([]);
+
/**
* NOTE the current assumption is that foreignKeyData is used only in
* foreignkey-field.tsx for domain-filter support.
@@ -276,10 +326,12 @@ export default function RecordeditProvider({
if (!reference || setupStarted.current) return;
setupStarted.current = true;
+ // should only be available in create mode
+ const prefillObj = getPrefillObject(queryParams);
const tempColumnModels: RecordeditColumnModel[] = [];
reference.columns.forEach((column: any) => {
const isHidden = Array.isArray(hiddenColumns) && hiddenColumns.indexOf(column.name) !== -1;
- const cm = columnToColumnModel(column, isHidden, queryParams);
+ const cm = columnToColumnModel(column, isHidden, prefillObj, reference.bulkCreateForeignKeyObject);
tempColumnModels.push(cm);
})
setColumnModels([...tempColumnModels]);
@@ -401,6 +453,8 @@ export default function RecordeditProvider({
updateHeadTitle('Create new ' + reference.displayname.value);
}
+ if (prefillObj) setPrefillObject(prefillObj);
+
setInitialized(true);
} else if (session) {
const errMessage = MESSAGE_MAP.unauthorizedMessage + MESSAGE_MAP.reportErrorToAdmin;
@@ -769,11 +823,28 @@ export default function RecordeditProvider({
return newFormValues;
};
+ /**
+ *
+ * @param indexes array of indexes to remove from forms array (and tuples array)
+ * @param skipLogging boolean to skip logging the remove action
+ */
const removeForm = (indexes: number[], skipLogging?: boolean) => {
if (!skipLogging) {
logRecordeditClientAction(LogActions.FORM_REMOVE);
}
+ // bulkForeignKeySelectedRows is only used when there is a prefill object and there is a unique association
+ if (reference.bulkCreateForeignKeyObject?.isUnique) {
+ const tempSelectedRows = [...bulkForeignKeySelectedRows];
+
+ indexes.forEach((index: number) => {
+ // use splice to remove the element from the array and shift all array values after this element forward
+ tempSelectedRows.splice(index, 1);
+ });
+
+ setBulkForeignKeySelectedRows(tempSelectedRows);
+ }
+
// remove the forms based on the given indexes
setForms((previous: number[]) => previous.filter(({ }, i: number) => !indexes.includes(i)));
@@ -783,18 +854,42 @@ export default function RecordeditProvider({
// if reading the data for submission is done based on formValue (instead of index) this shouldn't matter
}
+ /**
+ * when a single foreignkey input field value is changed or removed, removes old row from association selected rows
+ * and adds the new one if a new value was selected. Used when there is a prefill object and the association is unique
+ *
+ * @param formNumber the form number from forms array to remove
+ * @param newRow the new row to keep track of, if not defined removes the previous row
+ */
+ const updateBulkForeignKeySelectedRows = (formNumber: number, newRow?: SelectedRow) => {
+ if (!reference.bulkCreateForeignKeyObject) return;
+
+ const tempSelectedRows = [...bulkForeignKeySelectedRows];
+
+ // find the index in forms for the form number
+ const indexToChange = forms.indexOf(formNumber);
+
+ if (newRow) {
+ // change the value at 'formNumber'
+ tempSelectedRows[indexToChange] = newRow
+ } else {
+ // remove value at form number without shifting other array values
+ // leaves an `empty` or `undefined` value at `indexToChange` in array
+ delete tempSelectedRows[indexToChange];
+ }
+
+ setBulkForeignKeySelectedRows(tempSelectedRows);
+ }
+
const getInitialFormValues = (forms: number[], columnModels: RecordeditColumnModel[]) => {
let initialModel: any = { values: {} };
if (appMode === appModes.CREATE) {
// NOTE: should only be 1 form for create...
- initialModel = populateCreateInitialValues(columnModels, forms, queryParams, prefillRowData);
+ initialModel = populateCreateInitialValues(columnModels, forms, prefillObject, prefillRowData);
setWaitingForForeignKeyData(initialModel.shouldWaitForForeignKeyData);
shouldFetchForeignKeyData.current = initialModel.shouldWaitForForeignKeyData;
-
} else if (appMode === appModes.EDIT || appMode === appModes.COPY) {
-
-
// using page.tuples here instead of forms
initialModel = populateEditInitialValues(reference, columnModels, forms, tuplesRef.current, appMode);
@@ -823,14 +918,12 @@ export default function RecordeditProvider({
return;
}
- const prefillObj = getPrefillObject(queryParams);
-
columnModels.forEach((colModel: RecordeditColumnModel, index: number) => {
const column = colModel.column;
if (!column.isForeignKey) return;
// if it's a prefilled foreignkey, the value is going to be set by processPrefilledForeignKeys
- if (prefillObj && prefillObj.fkColumnNames.indexOf(column.name) !== -1) {
+ if (prefillObject && prefillObject.fkColumnNames.indexOf(column.name) !== -1) {
return;
}
@@ -838,8 +931,8 @@ export default function RecordeditProvider({
const defaultValue = initialValues[`c_1-${column.RID}`];
// if all the columns of the foreignkey are prefilled, use that instead of default
- if (prefillObj && allForeignKeyColumnsPrefilled(column, prefillObj)) {
- const defaultDisplay = column.getDefaultDisplay(prefillObj.keys);
+ if (prefillObject && allForeignKeyColumnsPrefilled(column, prefillObject)) {
+ const defaultDisplay = column.getDefaultDisplay(prefillObject.keys);
// if the data is missing, ermrestjs will return null
// although the previous allPrefilled should already guard against this.
@@ -961,17 +1054,17 @@ export default function RecordeditProvider({
}
/**
- * In case of prefill and default we only have a reference to the foreignkey,
- * we should do extra reads to get the actual data.
- *
- * NOTE for default we don't want to send the raw data to the ermrestjs request,
- * that's why after fetching the data we're only changing the displayed rowname
- * and the foreignKeyData, not the raw values sent to ermrestjs.
- * @param formValue which form it is
- * @param colRIDs the columns RIDs that will use this data
- * @param fkRef the foreignkey reference that should be used for fetching data
- * @param logObject
- */
+ * In case of prefill and default we only have a reference to the foreignkey,
+ * we should do extra reads to get the actual data.
+ *
+ * NOTE for default we don't want to send the raw data to the ermrestjs request,
+ * that's why after fetching the data we're only changing the displayed rowname
+ * and the foreignKeyData, not the raw values sent to ermrestjs.
+ * @param formValue which form it is
+ * @param colRIDs the columns RIDs that will use this data
+ * @param fkRef the foreignkey reference that should be used for fetching data
+ * @param logObject
+ */
function fetchForeignKeyData(colRIDs: string[], fkRef: any, logObject: any, setValue: any) {
// NOTE since this is create mode and we're disabling the addForm,
// we can assume this is the first form
@@ -1094,6 +1187,12 @@ export default function RecordeditProvider({
lastContiguousChunkRef,
MAX_ROWS_TO_ADD: maxRowsToAdd,
+ // prefill association modal
+ prefillObject,
+ bulkForeignKeySelectedRows,
+ setBulkForeignKeySelectedRows,
+ updateBulkForeignKeySelectedRows,
+
// log related:
logRecordeditClientAction,
getRecordeditLogAction,
@@ -1102,7 +1201,8 @@ export default function RecordeditProvider({
}, [
// main entity:
columnModels, columnPermissionErrors, initialized, reference, tuples, waitingForForeignKeyData,
- forms, showCloneSpinner, showApplyAllSpinner, showSubmitSpinner, resultsetProps
+ forms, showCloneSpinner, showApplyAllSpinner, showSubmitSpinner, resultsetProps,
+ prefillObject, bulkForeignKeySelectedRows
]);
return (
diff --git a/src/providers/recordset.tsx b/src/providers/recordset.tsx
index 8a1b92a3e..9eeb9e3c5 100644
--- a/src/providers/recordset.tsx
+++ b/src/providers/recordset.tsx
@@ -6,9 +6,11 @@ import useAlert from '@isrd-isi-edu/chaise/src/hooks/alerts';
import useStateRef from '@isrd-isi-edu/chaise/src/hooks/state-ref';
// models
+import { ChaiseAlert, ChaiseAlertType } from '@isrd-isi-edu/chaise/src/providers/alerts';
import { LogActions, LogStackPaths } from '@isrd-isi-edu/chaise/src/models/log';
import { FlowControlQueueInfo } from '@isrd-isi-edu/chaise/src/models/flow-control';
import {
+ DisabledRow,
RecordsetConfig, RecordsetDisplayMode,
RecordsetProviderAddUpdateCauses,
RecordsetProviderFetchSecondaryRequests,
@@ -25,7 +27,7 @@ import $log from '@isrd-isi-edu/chaise/src/services/logger';
import RecordsetFlowControl from '@isrd-isi-edu/chaise/src/services/recordset-flow-control';
// utils
-import { RECORDSET_DEFAULT_PAGE_SIZE, URL_PATH_LENGTH_LIMIT } from '@isrd-isi-edu/chaise/src/utils/constants';
+import { RECORDEDIT_MAX_ROWS,RECORDSET_DEFAULT_PAGE_SIZE, URL_PATH_LENGTH_LIMIT } from '@isrd-isi-edu/chaise/src/utils/constants';
import { getColumnValuesFromPage } from '@isrd-isi-edu/chaise/src/utils/data-utils';
import { isObjectAndKeyDefined } from '@isrd-isi-edu/chaise/src/utils/type-utils';
import { createRedirectLinkFromPath } from '@isrd-isi-edu/chaise/src/utils/uri-utils';
@@ -126,7 +128,7 @@ export const RecordsetContext = createContext<{
/**
* The rows that should be disabled
*/
- disabledRows: any,
+ disabledRows: DisabledRow[],
/**
* The rows that are selected
*/
@@ -286,7 +288,10 @@ export default function RecordsetProvider({
savedQueryConfig,
}: RecordsetProviderProps): JSX.Element {
const { dispatchError } = useError();
- const { addURLLimitAlert, removeURLLimitAlert } = useAlert();
+ const {
+ addTooManyFormsAlert, removeTooManyFormsAlert,
+ addURLLimitAlert, removeURLLimitAlert
+ } = useAlert();
const [reference, setReference, referenceRef] = useStateRef(initialReference);
@@ -326,7 +331,7 @@ export default function RecordsetProvider({
const [totalRowCount, setTotalRowCount] = useState(null);
- const [disabledRows, setDisabledRows] = useState([]);
+ const [disabledRows, setDisabledRows] = useState([]);
/**
* The selected rows
@@ -334,6 +339,7 @@ export default function RecordsetProvider({
const [selectedRows, setStateSelectedRows] = useState(() => {
return Array.isArray(initialSelectedRows) ? initialSelectedRows : [];
});
+
/**
* A wrapper for the set state function to first call the onSelectedRowsChanged
*
@@ -352,6 +358,12 @@ export default function RecordsetProvider({
} else {
removeURLLimitAlert();
}
+ } else if (config.displayMode === RecordsetDisplayMode.FK_POPUP_BULK_CREATE) {
+ if (typeof temp === 'string') {
+ addTooManyFormsAlert(temp, ChaiseAlertType.WARNING);
+ } else {
+ removeTooManyFormsAlert();
+ }
} else {
return temp === false ? prevRows : res;
}
@@ -751,7 +763,7 @@ export default function RecordsetProvider({
} else {
return { page: result.page };
}
- }).then((result: { page: any, disabledRows?: any }) => {
+ }).then((result: { page: any, disabledRows?: DisabledRow[] }) => {
if (current !== flowControl.current.queue.counter) {
defer.resolve({ success: false, page: null });
return defer.promise;
diff --git a/src/providers/viewer.tsx b/src/providers/viewer.tsx
index efc2832ff..b7e2ef688 100644
--- a/src/providers/viewer.tsx
+++ b/src/providers/viewer.tsx
@@ -12,6 +12,7 @@ import { CustomError, DifferentUserConflictError, LimitedBrowserSupport, Multipl
import { LogActions, LogAppModes, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
import { ViewerAnnotationModal } from '@isrd-isi-edu/chaise/src/models/viewer';
import { RecordeditDisplayMode, RecordeditProps, appModes } from '@isrd-isi-edu/chaise/src/models/recordedit';
+import { DisabledRow } from '@isrd-isi-edu/chaise/src/models/recordset';
// providers
import { ChaiseAlertType } from '@isrd-isi-edu/chaise/src/providers/alerts';
@@ -870,7 +871,7 @@ export default function ViewerProvider({
const getAnnotatedTermDisabledTuples = (
page: any, pageLimit: number, logStack: any,
logStackPath: string, requestCauses?: any, reloadStartTime?: any
- ): Promise<{ page: any, disabledRows?: any }> => {
+ ): Promise<{ page: any, disabledRows?: DisabledRow[] }> => {
return new Promise((resolve, reject) => {
const annotConfig = ViewerConfigService.annotationConfig;
@@ -920,7 +921,7 @@ export default function ViewerProvider({
return ref.contextualize.compactSelect.setSamePaging(page).read(pageLimit, logObj, false, true);
}).then((disabeldPage: any) => {
- const disabledRows: any = [];
+ const disabledRows: DisabledRow[] = [];
disabeldPage.tuples.forEach((disabledTuple: any) => {
// currently selected value should not be disabled
@@ -930,7 +931,7 @@ export default function ViewerProvider({
const index = page.tuples.findIndex((tuple: any) => {
return tuple.uniqueId === disabledTuple.uniqueId;
});
- if (index > -1) disabledRows.push(page.tuples[index]);
+ if (index > -1) disabledRows.push({tuple: page.tuples[index]});
});
resolve({ page, disabledRows });
diff --git a/src/utils/message-map.ts b/src/utils/message-map.ts
index 2249aca89..b93708b62 100644
--- a/src/utils/message-map.ts
+++ b/src/utils/message-map.ts
@@ -69,7 +69,10 @@ export const MESSAGE_MAP = {
showDetails: 'Click to show more details about the filters',
saveQuery: 'Click to save the current search criteria',
export: 'Click to choose an export format.',
- liveData: 'You are viewing snapshotted data. Click here to return to the live data catalog.'
+ liveData: 'You are viewing snapshotted data. Click here to return to the live data catalog.',
+ // tooltips for disabled rows in recordset single select modal for foreignkey-field and foreignkey-dropdown-field
+ selectedDisabledRow: 'This row is selected in another input in the form',
+ associatedDisabledRow: 'This row is already associated'
},
URLLimitMessage: 'Maximum URL length reached. Cannot perform the requested action.',
queryTimeoutList: 'Reduce the number of facet constraints. Minimize the use of \'No value\' and \'All Records with Value\' filters. ',
diff --git a/src/utils/record-utils.ts b/src/utils/record-utils.ts
index a41e70c58..2af2ff42e 100644
--- a/src/utils/record-utils.ts
+++ b/src/utils/record-utils.ts
@@ -1,7 +1,7 @@
// models
-import { Displayname } from '@isrd-isi-edu/chaise/src/models/displayname';
import { LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
import { RecordColumnModel, RecordRelatedModel } from '@isrd-isi-edu/chaise/src/models/record';
+import { PrefillObject } from '@isrd-isi-edu/chaise/src/models/recordedit';
import { RecordsetDisplayMode, RecordsetSelectMode } from '@isrd-isi-edu/chaise/src/models/recordset';
// services
@@ -142,6 +142,7 @@ export function generateRelatedRecordModel(ref: any, index: number, isInline: bo
ref.table,
{ source: ref.compressedDataSource, entity: true }
);
+
return {
index,
isInline,
@@ -312,33 +313,11 @@ function canRelatedForeignKeyBePrefilled(fk: any, origFKR: any) {
* @param mainTuple the main tuple
* @returns
*/
-export function getPrefillCookieObject(ref: any, mainTuple: any): {
- /**
- * the displayed value in the form
- */
- rowname: Displayname,
- /**
- * used for reading the actual foreign key data
- */
- origUrl: string,
- /**
- * the foreignkey columns that should be prefilled
- */
- fkColumnNames: string[],
- /**
- * raw values of the foreign key columns keyed by column name
- */
- keys: { [key: string]: any },
- /**
- * map of column names as keys to column RIDs as values
- */
- columnNameToRID: { [key: string]: string }
-} {
+export function getPrefillCookieObject(ref: any, mainTuple: any): PrefillObject {
let origTable;
if (ref.derivedAssociationReference) {
- // add association relies on the object that this returns for
- // prefilling the data.
+ // add association relies on the object that this returns for prefilling the data.
origTable = ref.derivedAssociationReference.table;
} else {
// we should contextualize to make sure the same table is shown in create mode
diff --git a/src/utils/recordedit-utils.ts b/src/utils/recordedit-utils.ts
index e870b1a1a..0a1cd5a8a 100644
--- a/src/utils/recordedit-utils.ts
+++ b/src/utils/recordedit-utils.ts
@@ -6,12 +6,12 @@
import { dataFormats } from '@isrd-isi-edu/chaise/src/utils/constants';
// models
-import { LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
+import { LogActions, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
import {
- appModes, PrefillObject, RecordeditColumnModel, RecordeditForeignkeyCallbacks,
- MULTI_FORM_INPUT_FORM_VALUE, TimestampOptions
-
-} from '@isrd-isi-edu/chaise/src/models/recordedit'
+ appModes, MULTI_FORM_INPUT_FORM_VALUE, PrefillObject, RecordeditColumnModel,
+ RecordeditForeignkeyCallbacks, TimestampOptions
+} from '@isrd-isi-edu/chaise/src/models/recordedit';
+import { DisabledRow, DisabledRowType, SelectedRow } from '@isrd-isi-edu/chaise/src/models/recordset';
// services
import { CookieService } from '@isrd-isi-edu/chaise/src/services/cookie';
@@ -19,19 +19,25 @@ import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
import $log from '@isrd-isi-edu/chaise/src/services/logger';
// utilities
+import { simpleDeepCopy } from '@isrd-isi-edu/chaise/src/utils/data-utils';
import {
formatDatetime, formatFloat, formatInt, getInputType,
replaceNullOrUndefined, isDisabled
} from '@isrd-isi-edu/chaise/src/utils/input-utils';
+import { MESSAGE_MAP } from '@isrd-isi-edu/chaise/src/utils/message-map';
import { isNonEmptyObject, isObjectAndNotNull } from '@isrd-isi-edu/chaise/src/utils/type-utils';
-import { simpleDeepCopy } from '@isrd-isi-edu/chaise/src/utils/data-utils';
import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref';
/**
* Create a columnModel based on the given column that can be used in a recordedit form
* @param column the column object from ermrestJS
*/
-export function columnToColumnModel(column: any, isHidden?: boolean, queryParams?: any): RecordeditColumnModel {
+export function columnToColumnModel(
+ column: any,
+ isHidden?: boolean,
+ prefillObject?: PrefillObject | null,
+ bulkFKObject?: any
+): RecordeditColumnModel {
const isInputDisabled: boolean = isDisabled(column);
const logStackNode = LogService.getStackNode(
column.isForeignKey ? LogStackTypes.FOREIGN_KEY : LogStackTypes.COLUMN,
@@ -54,22 +60,26 @@ export function columnToColumnModel(column: any, isHidden?: boolean, queryParams
}
- const prefillObj = getPrefillObject(queryParams ? queryParams : {});
let isPrefilled = false, hasDomainFilter = false;
if (column.isForeignKey) hasDomainFilter = column.hasDomainFilter;
- if (prefillObj) {
+ let isLeafInUniqueBulkForeignKeyCreate = false;
+ if (prefillObject) {
if (column.isForeignKey) {
if (
// whether the fk is already marked as prefilled
- prefillObj.fkColumnNames.indexOf(column.name) !== -1 ||
+ prefillObject.fkColumnNames.indexOf(column.name) !== -1 ||
// or all the columns have the prefilled value, and therefore it should be marked as prefilled.
- allForeignKeyColumnsPrefilled(column, prefillObj)
+ allForeignKeyColumnsPrefilled(column, prefillObject)
) {
isPrefilled = true;
}
- } else if (column.name in prefillObj.keys) {
+ if (bulkFKObject?.isUnique && bulkFKObject.leafColumn.name === column.name) {
+ isLeafInUniqueBulkForeignKeyCreate = true
+ }
+
+ } else if (column.name in prefillObject.keys) {
isPrefilled = true;
}
}
@@ -82,7 +92,8 @@ export function columnToColumnModel(column: any, isHidden?: boolean, queryParams
logStackNode, // should not be used directly, take a look at getColumnModelLogStack
logStackPathChild, // should not be used directly, use getColumnModelLogAction getting the action string
hasDomainFilter,
- isHidden: !!isHidden
+ isHidden: !!isHidden,
+ isLeafInUniqueBulkForeignKeyCreate
};
}
@@ -243,7 +254,7 @@ export function copyOrClearValue(
export function populateCreateInitialValues(
columnModels: RecordeditColumnModel[],
forms: number[],
- queryParams?: any,
+ prefillObject?: PrefillObject | null,
prefillRowData?: any[]
) {
const values: any = {};
@@ -251,13 +262,7 @@ export function populateCreateInitialValues(
// only 1 row in the case of create
if (prefillRowData) initialValues = prefillRowData[0];
- let shouldWaitForForeignKeyData = false;
-
- // get the prefilled values
- const prefillObj = getPrefillObject(queryParams);
- if (prefillObj) {
- shouldWaitForForeignKeyData = true;
- }
+ let shouldWaitForForeignKeyData = prefillObject ? true : false;
// the data associated with the foreignkeys
const foreignKeyData: any = {};
@@ -280,13 +285,13 @@ export function populateCreateInitialValues(
}
// if it's a prefilled foreignkey, the value is going to be set by processPrefilledForeignKeys
- if (column.isForeignKey && prefillObj && prefillObj.fkColumnNames.indexOf(column.name) !== -1) {
+ if (column.isForeignKey && prefillObject && prefillObject.fkColumnNames.indexOf(column.name) !== -1) {
continue;
}
// if the column is prefilled, get the prefilled value instead of default
- if (prefillObj && column.name in prefillObj.keys) {
- defaultValue = prefillObj.keys[column.name];
+ if (prefillObject && column.name in prefillObject.keys) {
+ defaultValue = prefillObject.keys[column.name];
}
const tsOptions: TimestampOptions = { outputMomentFormat: '' };
@@ -339,7 +344,7 @@ export function populateCreateInitialValues(
} else if (column.isForeignKey) {
// if all the columns of the foreignkey are prefilled, use that instead of default
- const allPrefilled = prefillObj && allForeignKeyColumnsPrefilled(column.foreignKey, prefillObj);
+ const allPrefilled = prefillObject && allForeignKeyColumnsPrefilled(column.foreignKey, prefillObject);
// if all the columns of the foreignkey are initialized, use that instead of default
const allInitialized = isNonEmptyObject(initialValues) && column.foreignKey.colset.columns.every((col: any) => {
@@ -347,7 +352,7 @@ export function populateCreateInitialValues(
});
if (allPrefilled || allInitialized) {
- const defaultDisplay = column.getDefaultDisplay(allPrefilled ? prefillObj.keys : initialValues);
+ const defaultDisplay = column.getDefaultDisplay((allPrefilled && prefillObject) ? prefillObject.keys : initialValues);
// display the initial value
initialModelValue = defaultDisplay.rowname.value;
@@ -741,8 +746,7 @@ export function getPrefillObject(queryParams: any): null | PrefillObject {
// make sure all the keys are in the object
if (!(
- ('keys' in cookie) && ('columnNameToRID' in cookie) &&
- ('fkColumnNames' in cookie) &&
+ ('keys' in cookie) && ('columnNameToRID' in cookie) && ('fkColumnNames' in cookie) &&
('origUrl' in cookie) && ('rowname' in cookie)
)) {
return null;
@@ -783,7 +787,7 @@ export function allForeignKeyColumnsPrefilled(column: any, prefillObj: PrefillOb
));
}
-/* The following 3 functions are for foreignkey fields */
+/* The following 6 functions are for foreignkey and foreignkey-dropdown fields */
export function createForeignKeyReference(
column: any,
parentReference: any,
@@ -891,3 +895,96 @@ export function validateForeignkeyValue(
return foreignKeyCallbacks.onChange(column, data);
}
}
+
+export function disabledRowTooltip(disabledType: DisabledRowType): string {
+ let disabledTooltip = '';
+ if (disabledType === DisabledRowType.ASSOCIATED) {
+ disabledTooltip = MESSAGE_MAP.tooltip.associatedDisabledRow;
+ } else if (disabledType === DisabledRowType.SELECTED) {
+ disabledTooltip = MESSAGE_MAP.tooltip.selectedDisabledRow;
+ }
+
+ return disabledTooltip;
+}
+
+/**
+ * Used to fetch the disabled tuples for a recordset modal picker used to associate rows of data
+ *
+ * @param domainRef the reference used in the modal picker that we want to disable rows for
+ * @param disabledRowsFilters
+ * @param rowsUsedInForm
+ * @returns a function that returns a promise
+ */
+export function disabledTuplesPromise(domainRef: any, disabledRowsFilters: any[], rowsUsedInForm: (SelectedRow | null)[]) {
+ /**
+ * The existing rows in this p&b association must be disabled
+ * so users doesn't resubmit them.
+ */
+ return (
+ page: any,
+ pageLimit: number,
+ logStack: any,
+ logStackPath: string,
+ requestCauses?: any,
+ reloadStartTime?: any
+ ): Promise<{ page: any, disabledRows?: DisabledRow[] }> => {
+ return new Promise((resolve, reject) => {
+ const disabledRows: DisabledRow[] = [];
+
+ let action = LogActions.LOAD,
+ newStack = logStack;
+ if (Array.isArray(requestCauses) && requestCauses.length > 0) {
+ action = LogActions.RELOAD;
+ newStack = LogService.addCausesToStack(logStack, requestCauses, reloadStartTime);
+ }
+
+ // using the service instead of the record one since this is called from the modal
+ const logObj = {
+ action: LogService.getActionString(action, logStackPath),
+ stack: newStack,
+ };
+
+ // fourth input: preserve the paging (read will remove the before if number of results is less than the limit)
+ domainRef
+ .addFacets(disabledRowsFilters)
+ .setSamePaging(page)
+ .read(pageLimit, logObj, false, true)
+ .then((newPage: any) => {
+ newPage.tuples.forEach((newTuple: any) => {
+ const index = page.tuples.findIndex((tuple: any) => {
+ return tuple.uniqueId === newTuple.uniqueId;
+ });
+
+ if (index > -1) {
+ disabledRows.push({
+ disabledType: DisabledRowType.ASSOCIATED,
+ tuple: page.tuples[index]
+ });
+ }
+ });
+
+ // iterate through the current row selections in recordedit forms
+ rowsUsedInForm.forEach((row: SelectedRow | null) => {
+ // if an input is empty, there won't be a row defined
+ if (!row) return;
+
+ const index = page.tuples.findIndex((tuple: any) => {
+ return tuple.uniqueId === row.uniqueId;
+ });
+
+ if (index > -1) {
+ disabledRows.push({
+ disabledType: DisabledRowType.SELECTED,
+ tuple: page.tuples[index]
+ });
+ }
+ });
+
+ resolve({ disabledRows: disabledRows, page: page });
+ })
+ .catch((err: any) => {
+ reject(err);
+ });
+ });
+ };
+}
diff --git a/test/e2e/data_setup/data/product/association_table_w_static_column.json b/test/e2e/data_setup/data/product/association_table_w_static_column.json
new file mode 100644
index 000000000..523ec777a
--- /dev/null
+++ b/test/e2e/data_setup/data/product/association_table_w_static_column.json
@@ -0,0 +1 @@
+[{"main_fk_col": 2004, "leaf_fk_col": 2, "static_col1": 2}]
diff --git a/test/e2e/data_setup/data/product/association_table_w_static_column_dropdown.json b/test/e2e/data_setup/data/product/association_table_w_static_column_dropdown.json
new file mode 100644
index 000000000..523ec777a
--- /dev/null
+++ b/test/e2e/data_setup/data/product/association_table_w_static_column_dropdown.json
@@ -0,0 +1 @@
+[{"main_fk_col": 2004, "leaf_fk_col": 2, "static_col1": 2}]
diff --git a/test/e2e/data_setup/data/product/leaf_table_for_static_columns.json b/test/e2e/data_setup/data/product/leaf_table_for_static_columns.json
new file mode 100644
index 000000000..0531f886f
--- /dev/null
+++ b/test/e2e/data_setup/data/product/leaf_table_for_static_columns.json
@@ -0,0 +1,10 @@
+[{"id": "1", "details": "Leaf 1"},
+{"id": "2", "details": "Leaf 2"},
+{"id": "3", "details": "Leaf 3"},
+{"id": "4", "details": "Leaf 4"},
+{"id": "5", "details": "Leaf 5"},
+{"id": "6", "details": "Leaf 6"},
+{"id": "7", "details": "Leaf 7"},
+{"id": "8", "details": "Leaf 8"},
+{"id": "9", "details": "Leaf 9"},
+{"id": "10", "details": "Leaf 10"}]
diff --git a/test/e2e/data_setup/data/product/leaf_table_for_static_columns_dropdown.json b/test/e2e/data_setup/data/product/leaf_table_for_static_columns_dropdown.json
new file mode 100644
index 000000000..0531f886f
--- /dev/null
+++ b/test/e2e/data_setup/data/product/leaf_table_for_static_columns_dropdown.json
@@ -0,0 +1,10 @@
+[{"id": "1", "details": "Leaf 1"},
+{"id": "2", "details": "Leaf 2"},
+{"id": "3", "details": "Leaf 3"},
+{"id": "4", "details": "Leaf 4"},
+{"id": "5", "details": "Leaf 5"},
+{"id": "6", "details": "Leaf 6"},
+{"id": "7", "details": "Leaf 7"},
+{"id": "8", "details": "Leaf 8"},
+{"id": "9", "details": "Leaf 9"},
+{"id": "10", "details": "Leaf 10"}]
diff --git a/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json b/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json
index 65e214189..9606c5192 100644
--- a/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json
+++ b/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json
@@ -14,7 +14,8 @@
},
{
"unique_columns": [
- "id", "extra_id"
+ "id",
+ "extra_id"
]
},
{
@@ -31,7 +32,12 @@
"foreign_keys": [
{
"comment": null,
- "names": [["product-unordered-related-tables-links", "fk_category"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_category"
+ ]
+ ],
"foreign_key_columns": [
{
"table_name": "accommodation",
@@ -50,7 +56,12 @@
},
{
"comment": null,
- "names": [["product-unordered-related-tables-links", "fk_thumbnail"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_thumbnail"
+ ]
+ ],
"foreign_key_columns": [
{
"table_name": "accommodation",
@@ -59,7 +70,9 @@
}
],
"annotations": {
- "comment": ["thumbnail"]
+ "comment": [
+ "thumbnail"
+ ]
},
"referenced_columns": [
{
@@ -71,7 +84,12 @@
},
{
"comment": null,
- "names": [["product-unordered-related-tables-links", "fk_cover"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_cover"
+ ]
+ ],
"foreign_key_columns": [
{
"table_name": "accommodation",
@@ -80,7 +98,9 @@
}
],
"annotations": {
- "comment": ["thumbnail"]
+ "comment": [
+ "thumbnail"
+ ]
},
"referenced_columns": [
{
@@ -93,21 +113,30 @@
{
"comment": null,
"names": [
- ["product-unordered-related-tables-links", "fk_to_accommodation_outbound1"]
+ [
+ "product-unordered-related-tables-links",
+ "fk_to_accommodation_outbound1"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
+ "table_name": "accommodation",
+ "schema_name": "product-unordered-related-tables-links",
+ "column_name": "fk_col_outbound1"
+ }
],
- "foreign_key_columns": [{
- "table_name": "accommodation",
- "schema_name": "product-unordered-related-tables-links",
- "column_name": "fk_col_outbound1"
- }],
"annotations": {
- "comment": ["thumbnail"]
+ "comment": [
+ "thumbnail"
+ ]
},
- "referenced_columns": [{
- "table_name": "accommodation_outbound1",
- "schema_name": "product-unordered-related-tables-links",
- "column_name": "id"
- }]
+ "referenced_columns": [
+ {
+ "table_name": "accommodation_outbound1",
+ "schema_name": "product-unordered-related-tables-links",
+ "column_name": "id"
+ }
+ ]
}
],
"table_name": "accommodation",
@@ -125,8 +154,8 @@
"comment": [
"hidden"
],
- "tag:misd.isi.edu,2015:display" : {
- "name" : "Id"
+ "tag:misd.isi.edu,2015:display": {
+ "name": "Id"
}
}
},
@@ -154,8 +183,8 @@
"description": {
"display": "Name of Accommodation"
},
- "tag:misd.isi.edu,2015:display" : {
- "name" : "Name of Accommodation"
+ "tag:misd.isi.edu,2015:display": {
+ "name": "Name of Accommodation"
},
"facetOrder": [
"1"
@@ -177,12 +206,12 @@
"description": {
"display": "Website"
},
- "tag:misd.isi.edu,2015:url" : {
- "url" : "{cname}"
+ "tag:misd.isi.edu,2015:url": {
+ "url": "{cname}"
},
- "tag:isrd.isi.edu,2016:column-display" : {
+ "tag:isrd.isi.edu,2016:column-display": {
"*": {
- "markdown_pattern" : "[Link to Website]({{website}})"
+ "markdown_pattern": "[Link to Website]({{website}})"
}
},
"tag:misd.isi.edu,2015:display": {
@@ -199,7 +228,9 @@
"typename": "text"
},
"annotations": {
- "comment": ["top"],
+ "comment": [
+ "top"
+ ],
"description": {
"display": "Category"
},
@@ -220,7 +251,9 @@
"typename": "float4"
},
"annotations": {
- "comment": ["top"],
+ "comment": [
+ "top"
+ ],
"description": {
"display": "User Rating"
},
@@ -281,7 +314,9 @@
"typename": "int4"
},
"annotations": {
- "comment" : ["top"],
+ "comment": [
+ "top"
+ ],
"tag:misd.isi.edu,2015:display": {
"name": "Number of Rooms"
}
@@ -353,14 +388,18 @@
"typename": "boolean"
},
"annotations": {
- "comment": ["top"],
+ "comment": [
+ "top"
+ ],
"description": {
"display": "Luxurious"
},
"tag:misd.isi.edu,2015:display": {
"name": "Is Luxurious"
},
- "tag:isrd.isi.edu,2016:ignore" : ["record"]
+ "tag:isrd.isi.edu,2016:ignore": [
+ "record"
+ ]
}
},
{
@@ -398,38 +437,127 @@
},
"description": {
"display": "Accommodations",
- "top_columns": ["title", "rating", "category", "opened_on"]
+ "top_columns": [
+ "title",
+ "rating",
+ "category",
+ "opened_on"
+ ]
},
- "tag:isrd.isi.edu,2016:visible-columns" : {
- "detailed" : ["id", "title", "website", ["product-unordered-related-tables-links", "fk_category"], ["product-unordered-related-tables-links", "fk_assoc_accommodation_null_key"], ["product-unordered-related-tables-links", "accommodation_inbound_null_fk"], ["product-unordered-related-tables-links", "fk_assoc_accommodation_null_key2"], "rating", "summary", "description", "no_of_rooms", ["product-unordered-related-tables-links", "fk_cover"], ["product-unordered-related-tables-links", "fk_thumbnail"], "opened_on", "luxurious"]
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "detailed": [
+ "id",
+ "title",
+ "website",
+ [
+ "product-unordered-related-tables-links",
+ "fk_category"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation_null_key"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound_null_fk"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation_null_key2"
+ ],
+ "rating",
+ "summary",
+ "description",
+ "no_of_rooms",
+ [
+ "product-unordered-related-tables-links",
+ "fk_cover"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_thumbnail"
+ ],
+ "opened_on",
+ "luxurious"
+ ]
},
"tag:isrd.isi.edu,2016:visible-foreign-keys": {
"detailed": [
- ["product-unordered-related-tables-links", "fk_booking_accommodation"],
- ["product-unordered-related-tables-links", "fk_schedule_accommodation"],
- ["product-unordered-related-tables-links", "fk_media_accommodation"],
- ["product-unordered-related-tables-links", "fk_assoc_accommodation"],
- ["product-unordered-related-tables-links", "fk_accommodation_image"],
- ["product-unordered-related-tables-links", "fk_assoc_mdn_accommodation"],
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_schedule_accommodation"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_media_accommodation"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_accommodation_image"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_mdn_accommodation"
+ ],
{
"source": [
- {"inbound": ["product-unordered-related-tables-links", "fk_assoc_accommodation"]},
- {"outbound": ["product-unordered-related-tables-links", "fk_assoc_related_table"]},
- {"inbound": ["product-unordered-related-tables-links", "fk_related_table_2_to_related"]},
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ },
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_related_table_2_to_related"
+ ]
+ },
"id"
],
"display": {
- "wait_for": ["entity_set_accommodation_inbound2"]
+ "wait_for": [
+ "entity_set_accommodation_inbound2"
+ ]
}
},
- ["product-unordered-related-tables-links", "fk_table_w_agg_accommodation"],
- {"source": [{"inbound": ["product-unordered-related-tables-links", "fk_table_invalid_markdown"]}, "id"]},
+ [
+ "product-unordered-related-tables-links",
+ "fk_table_w_agg_accommodation"
+ ],
+ {
+ "source": [
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_table_invalid_markdown"
+ ]
+ },
+ "id"
+ ]
+ },
{
"sourcekey": "entity_set_accommodation_inbound1",
"markdown_name": "inbound related with display.wait_for entityset",
"display": {
"template_engine": "handlebars",
- "wait_for": ["entity_set_accommodation_inbound2"],
+ "wait_for": [
+ "entity_set_accommodation_inbound2"
+ ],
"markdown_pattern": "{{#each $self}}{{{this.rowName}}}{{#unless @last}}, {{/unless}}{{/each}} ({{#each entity_set_accommodation_inbound2}}{{{this.rowName}}}{{#unless @last}}, {{/unless}}{{/each}})"
},
"comment": "related table, has waitfor entityset and markdown_pattern (has markdown comment)",
@@ -440,22 +568,40 @@
"markdown_name": "inbound related with display.wait_for agg",
"display": {
"template_engine": "handlebars",
- "wait_for": ["cnt_d_accommodation_inbound2"],
+ "wait_for": [
+ "cnt_d_accommodation_inbound2"
+ ],
"markdown_pattern": "{{#each $self}}{{{this.rowName}}}{{#unless @last}}, {{/unless}}{{/each}} ({{{cnt_d_accommodation_inbound2}}})"
}
},
{
"source": [
- {"filter": "id", "operand_pattern": "1111"},
- {"inbound": ["product-unordered-related-tables-links", "fk_booking_accommodation"]},
+ {
+ "filter": "id",
+ "operand_pattern": "1111"
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ]
+ },
"RID"
],
"markdown_name": "inbound related with filter on main table (hidden)"
},
{
"source": [
- {"filter": "id", "operand_pattern": "2004"},
- {"inbound": ["product-unordered-related-tables-links", "fk_booking_accommodation"]},
+ {
+ "filter": "id",
+ "operand_pattern": "2004"
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ]
+ },
"RID"
],
"markdown_name": "inbound related with filter on main table",
@@ -464,12 +610,24 @@
},
{
"source": [
- {"inbound": ["product-unordered-related-tables-links", "fk_booking_accommodation"]},
{
- "or": [
- {"filter": "price", "operand_pattern": "90", "operator": "::lt::"},
- {"filter": "booking_date", "operator": "::null::"}
- ]
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ]
+ },
+ {
+ "or": [
+ {
+ "filter": "price",
+ "operand_pattern": "90",
+ "operator": "::lt::"
+ },
+ {
+ "filter": "booking_date",
+ "operator": "::null::"
+ }
+ ]
},
"RID"
],
@@ -478,18 +636,44 @@
},
{
"source": [
- {"filter": "id", "operand_pattern": "1111"},
- {"inbound": ["product-unordered-related-tables-links", "fk_assoc_accommodation"]},
- {"outbound": ["product-unordered-related-tables-links", "fk_assoc_related_table"]},
+ {
+ "filter": "id",
+ "operand_pattern": "1111"
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ },
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ },
"RID"
],
"markdown_name": "association with filter on main table (hidden)"
},
{
"source": [
- {"filter": "id", "operand_pattern": "2004"},
- {"inbound": ["product-unordered-related-tables-links", "fk_assoc_accommodation"]},
- {"outbound": ["product-unordered-related-tables-links", "fk_assoc_related_table"]},
+ {
+ "filter": "id",
+ "operand_pattern": "2004"
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ },
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ },
"RID"
],
"markdown_name": "association with filter on main table",
@@ -497,9 +681,22 @@
},
{
"source": [
- {"inbound": ["product-unordered-related-tables-links", "fk_assoc_accommodation"]},
- {"filter": "id_related", "operand_pattern": "3"},
- {"outbound": ["product-unordered-related-tables-links", "fk_assoc_related_table"]},
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ },
+ {
+ "filter": "id_related",
+ "operand_pattern": "3"
+ },
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ },
"RID"
],
"markdown_name": "association with filter on assoc table",
@@ -507,13 +704,29 @@
},
{
"source": [
- {"inbound": ["product-unordered-related-tables-links", "fk_assoc_accommodation"]},
- {"outbound": ["product-unordered-related-tables-links", "fk_assoc_related_table"]},
{
- "or": [
- {"filter": "facility", "operand_pattern": "Television"},
- {"filter": "facility", "operand_pattern": "Air Conditioning"}
- ]
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ },
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ },
+ {
+ "or": [
+ {
+ "filter": "facility",
+ "operand_pattern": "Television"
+ },
+ {
+ "filter": "facility",
+ "operand_pattern": "Air Conditioning"
+ }
+ ]
},
"RID"
],
@@ -522,21 +735,53 @@
},
{
"source": [
- {"filter": "id", "operand_pattern": "2004"},
- {"inbound": ["product-unordered-related-tables-links", "fk_assoc_accommodation"]},
+ {
+ "filter": "id",
+ "operand_pattern": "2004"
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ },
{
"and": [
- {"filter": "RID", "operand_pattern": "::null::"},
- {"filter": "RCB", "operand_pattern": "::null::"}
+ {
+ "filter": "RID",
+ "operand_pattern": "::null::"
+ },
+ {
+ "filter": "RCB",
+ "operand_pattern": "::null::"
+ }
],
"negate": true
},
- {"outbound": ["product-unordered-related-tables-links", "fk_assoc_related_table"]},
- {"inbound": ["product-unordered-related-tables-links", "fk_related_table_2_to_related"]},
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ },
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "fk_related_table_2_to_related"
+ ]
+ },
"id"
],
"markdown_name": "path of length 3 with filters"
- }
+ },
+ [
+ "product-unordered-related-tables-links",
+ "static_to_accommodation_fkey"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "static_to_accommodation_w_dropdown_fkey"
+ ]
]
},
"tag:isrd.isi.edu,2018:citation": {
@@ -545,7 +790,9 @@
"year_pattern": "{{formatDate RCT 'YYYY'}}",
"url_pattern": "{{website}}",
"id_pattern": "{{id}}",
- "wait_for": ["entity_all_outbound_outbound1_outbound1"]
+ "wait_for": [
+ "entity_all_outbound_outbound1_outbound1"
+ ]
},
"tag:isrd.isi.edu,2019:source-definitions": {
"fkeys": true,
@@ -553,32 +800,62 @@
"sources": {
"entity_all_outbound_outbound1_outbound1": {
"source": [
- {"outbound": ["product-unordered-related-tables-links", "fk_to_accommodation_outbound1"]},
- {"outbound": ["product-unordered-related-tables-links", "accommodation_outbound1_fk1"]},
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_to_accommodation_outbound1"
+ ]
+ },
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "accommodation_outbound1_fk1"
+ ]
+ },
"RID"
]
},
"entity_set_accommodation_inbound1": {
"source": [
- {"inbound": ["product-unordered-related-tables-links", "accommodation_inbound1_fk1"]},
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound1_fk1"
+ ]
+ },
"RID"
]
},
"entity_set_accommodation_inbound2": {
"source": [
- {"inbound": ["product-unordered-related-tables-links", "accommodation_inbound2_fk1"]},
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound2_fk1"
+ ]
+ },
"RID"
]
},
"entity_set_accommodation_inbound3": {
"source": [
- {"inbound": ["product-unordered-related-tables-links", "accommodation_inbound3_fk1"]},
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound3_fk1"
+ ]
+ },
"RID"
]
},
"cnt_d_accommodation_inbound2": {
"source": [
- {"inbound": ["product-unordered-related-tables-links", "accommodation_inbound2_fk1"]},
+ {
+ "inbound": [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound2_fk1"
+ ]
+ },
"RID"
],
"aggregate": "cnt_d"
@@ -603,7 +880,12 @@
],
"foreign_keys": [
{
- "names" : [["product-unordered-related-tables-links", "fk_booking_accommodation"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ]
+ ],
"comment": null,
"foreign_key_columns": [
{
@@ -627,7 +909,12 @@
]
},
{
- "names" : [["product-unordered-related-tables-links", "fk_booking_accommodation2"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation2"
+ ]
+ ],
"comment": "composite fk that should be prefilled",
"foreign_key_columns": [
{
@@ -655,7 +942,12 @@
]
},
{
- "names" : [["product-unordered-related-tables-links", "fk_booking_accommodation3"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation3"
+ ]
+ ],
"comment": "composite fk that should not be prefilled (nullok)",
"foreign_key_columns": [
{
@@ -683,7 +975,12 @@
]
},
{
- "names" : [["product-unordered-related-tables-links", "fk_booking_accommodation4"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation4"
+ ]
+ ],
"comment": "composite fk that should be prefilled",
"foreign_key_columns": [
{
@@ -711,7 +1008,12 @@
]
},
{
- "names" : [["product-unordered-related-tables-links", "fk_booking_accommodation5"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation5"
+ ]
+ ],
"foreign_key_columns": [
{
"table_name": "booking",
@@ -740,7 +1042,9 @@
"typename": "serial4"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
},
{
@@ -752,7 +1056,9 @@
"typename": "int4"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
},
{
@@ -767,7 +1073,9 @@
"description": {
"display": "Price"
},
- "facetOrder": ["4"]
+ "facetOrder": [
+ "4"
+ ]
}
},
{
@@ -782,7 +1090,9 @@
"description": {
"display": "Date of Booking"
},
- "facetOrder": ["5"]
+ "facetOrder": [
+ "5"
+ ]
}
},
{
@@ -821,22 +1131,101 @@
},
"tag:isrd.isi.edu,2016:visible-columns": {
"entry": [
- "id", "price", "booking_date", "fk2_col", "accommodation_id", "fk3_col1",
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_booking_accommodation"]}, "RID"], "markdown_name": "fk_1"},
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_booking_accommodation2"]}, "RID"], "markdown_name": "fk_2"},
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_booking_accommodation3"]}, "RID"], "markdown_name": "fk_3"},
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_booking_accommodation4"]}, "RID"], "markdown_name": "fk_4"},
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_booking_accommodation5"]}, "RID"], "markdown_name": "fk_5"}
- ],
- "compact/brief": ["price", "booking_date"],
+ "id",
+ "price",
+ "booking_date",
+ "fk2_col",
+ "accommodation_id",
+ "fk3_col1",
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ]
+ },
+ "RID"
+ ],
+ "markdown_name": "fk_1"
+ },
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation2"
+ ]
+ },
+ "RID"
+ ],
+ "markdown_name": "fk_2"
+ },
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation3"
+ ]
+ },
+ "RID"
+ ],
+ "markdown_name": "fk_3"
+ },
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation4"
+ ]
+ },
+ "RID"
+ ],
+ "markdown_name": "fk_4"
+ },
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation5"
+ ]
+ },
+ "RID"
+ ],
+ "markdown_name": "fk_5"
+ }
+ ],
+ "compact/brief": [
+ "price",
+ "booking_date"
+ ],
"filter": {
"and": [
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_booking_accommodation"]}], "markdown_name": "Accommodations"}
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ]
+ }
+ ],
+ "markdown_name": "Accommodations"
+ }
]
}
},
"tag:isrd.isi.edu,2016:table-display": {
- "*": {"row_order": [{"column": "id"}]}
+ "*": {
+ "row_order": [
+ {
+ "column": "id"
+ }
+ ]
+ }
}
}
},
@@ -898,9 +1287,9 @@
"thumbnail",
"download"
],
- "tag:isrd.isi.edu,2016:column-display" : {
+ "tag:isrd.isi.edu,2016:column-display": {
"*": {
- "markdown_pattern" : "[{{uri}}]({{uri}})"
+ "markdown_pattern": "[{{uri}}]({{uri}})"
}
}
}
@@ -1014,7 +1403,12 @@
"entityCount": 0,
"foreign_keys": [
{
- "names" : [["product-unordered-related-tables-links", "fk_accommodation_image"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_accommodation_image"
+ ]
+ ],
"comment": null,
"foreign_key_columns": [
{
@@ -1094,7 +1488,13 @@
"display": "Accommodation Images"
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "compact/brief": ["filename", "uri", "content_type", "bytes", "timestamp"]
+ "compact/brief": [
+ "filename",
+ "uri",
+ "content_type",
+ "bytes",
+ "timestamp"
+ ]
}
}
},
@@ -1145,7 +1545,9 @@
"typename": "text"
},
"annotations": {
- "comment": ["top"]
+ "comment": [
+ "top"
+ ]
}
}
],
@@ -1174,7 +1576,12 @@
],
"foreign_keys": [
{
- "names" : [["product-unordered-related-tables-links", "fk_media_accommodation"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_media_accommodation"
+ ]
+ ],
"comment": null,
"foreign_key_columns": [
{
@@ -1205,7 +1612,9 @@
"typename": "serial4"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
},
{
@@ -1217,7 +1626,9 @@
"typename": "int4"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
}
],
@@ -1233,13 +1644,21 @@
"row_markdown_pattern": "{{{accommodation_id}}}"
}
},
- "tag:isrd.isi.edu,2016:visible-columns" : {
- "*": ["id", ["product-unordered-related-tables-links", "fk_booking_accommodation"], "price", "bookin_date"]
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "*": [
+ "id",
+ [
+ "product-unordered-related-tables-links",
+ "fk_booking_accommodation"
+ ],
+ "price",
+ "bookin_date"
+ ]
}
}
},
"related_table": {
- "comment":"This is a related table and will have entries of facility in an accommodation",
+ "comment": "This is a related table and will have entries of facility in an accommodation",
"kind": "table",
"keys": [
{
@@ -1271,11 +1690,17 @@
"annotations": {
"tag:isrd.isi.edu,2016:table-display": {
"compact": {
- "row_order": [{"column": "id"}]
+ "row_order": [
+ {
+ "column": "id"
+ }
+ ]
}
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "compact/brief": ["facility"]
+ "compact/brief": [
+ "facility"
+ ]
}
}
},
@@ -1311,16 +1736,22 @@
"annotations": {
"tag:isrd.isi.edu,2016:table-display": {
"compact": {
- "row_order": [{"column": "id"}]
+ "row_order": [
+ {
+ "column": "id"
+ }
+ ]
}
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "compact/brief": ["term"]
+ "compact/brief": [
+ "term"
+ ]
}
}
},
"related_table_markdown": {
- "comment":"This is a related table with markdown annotations",
+ "comment": "This is a related table with markdown annotations",
"kind": "table",
"keys": [
{
@@ -1357,51 +1788,72 @@
}
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "compact/brief": ["facility"]
+ "compact/brief": [
+ "facility"
+ ]
}
}
},
"association_table_markdown": {
- "comment":"This is an association table for markdown display",
+ "comment": "This is an association table for markdown display",
"kind": "table",
"keys": [
{
"comment": null,
"annotations": {},
"unique_columns": [
- "id_related", "id_base"
+ "id_related",
+ "id_base"
]
}
],
"foreign_keys": [
{
"comment": "",
- "names": [["product-unordered-related-tables-links", "fk_assoc_mdn_related_table"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_mdn_related_table"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table_markdown",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_related"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "related_table_markdown",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id"
- }],
+ }
+ ],
"annotations": {}
},
{
"comment": "",
- "names": [["product-unordered-related-tables-links", "fk_assoc_mdn_accommodation"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_mdn_accommodation"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table_markdown",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_base"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "accommodation",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id"
- }],
+ }
+ ],
"annotations": {
"tag:isrd.isi.edu,2016:foreign-key": {
"to_name": "base table association related"
@@ -1430,46 +1882,65 @@
"annotations": {}
},
"association_table": {
- "comment":"This is an association table to hold the association of accomodation and facility",
+ "comment": "This is an association table to hold the association of accomodation and facility",
"kind": "table",
"keys": [
{
"comment": null,
"annotations": {},
"unique_columns": [
- "id_related", "id_base"
+ "id_related",
+ "id_base"
]
}
],
"foreign_keys": [
{
"comment": "",
- "names": [["product-unordered-related-tables-links", "fk_assoc_related_table"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_related"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "related_table",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id"
- }],
+ }
+ ],
"annotations": {}
},
{
"comment": "",
- "names": [["product-unordered-related-tables-links", "fk_assoc_accommodation"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_base"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "accommodation",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id"
- }],
+ }
+ ],
"annotations": {
"tag:isrd.isi.edu,2016:foreign-key": {
"to_name": "base table association related"
@@ -1498,7 +1969,11 @@
"annotations": {
"tag:isrd.isi.edu,2016:table-display": {
"compact": {
- "row_order": [{"column": "id_base"}]
+ "row_order": [
+ {
+ "column": "id_base"
+ }
+ ]
}
}
}
@@ -1511,37 +1986,56 @@
"comment": null,
"annotations": {},
"unique_columns": [
- "id_related", "id_base"
+ "id_related",
+ "id_base"
]
}
],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "fk_assoc_related_table_null_key"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table_null_key"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table_null_keys",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_related"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "related_table_null_key",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id"
- }],
+ }
+ ],
"annotations": {}
},
{
- "names": [["product-unordered-related-tables-links", "fk_assoc_accommodation_null_key"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation_null_key"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table_null_keys",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_base"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "accommodation",
"schema_name": "product-unordered-related-tables-links",
"column_name": "nullable_assoc_key"
- }],
+ }
+ ],
"annotations": {
"tag:isrd.isi.edu,2016:foreign-key": {
"to_name": "base table association related"
@@ -1570,7 +2064,11 @@
"annotations": {
"tag:isrd.isi.edu,2016:table-display": {
"compact": {
- "row_order": [{"column": "id_base"}]
+ "row_order": [
+ {
+ "column": "id_base"
+ }
+ ]
}
}
}
@@ -1583,37 +2081,56 @@
"comment": null,
"annotations": {},
"unique_columns": [
- "id_related", "id_base"
+ "id_related",
+ "id_base"
]
}
],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "fk_assoc_related_table_null_key2"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_related_table_null_key2"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table_null_keys2",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_related"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "related_table_null_key",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id"
- }],
+ }
+ ],
"annotations": {}
},
{
- "names": [["product-unordered-related-tables-links", "fk_assoc_accommodation_null_key2"]],
- "foreign_key_columns": [{
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_assoc_accommodation_null_key2"
+ ]
+ ],
+ "foreign_key_columns": [
+ {
"table_name": "association_table_null_keys2",
"schema_name": "product-unordered-related-tables-links",
"column_name": "id_base"
- }],
- "referenced_columns": [{
+ }
+ ],
+ "referenced_columns": [
+ {
"table_name": "accommodation",
"schema_name": "product-unordered-related-tables-links",
"column_name": "nullable_assoc_key2"
- }],
+ }
+ ],
"annotations": {
"tag:isrd.isi.edu,2016:foreign-key": {
"to_name": "base table association related"
@@ -1642,7 +2159,11 @@
"annotations": {
"tag:isrd.isi.edu,2016:table-display": {
"compact": {
- "row_order": [{"column": "id_base"}]
+ "row_order": [
+ {
+ "column": "id_base"
+ }
+ ]
}
}
}
@@ -1651,10 +2172,21 @@
"kind": "table",
"schema_name": "product-unordered-related-tables-links",
"table_name": "inbound_null_key",
- "keys": [{"unique_columns": ["id"]}],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "accommodation_inbound_null_fk"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound_null_fk"
+ ]
+ ],
"foreign_key_columns": [
{
"column_name": "fk1_col",
@@ -1675,16 +2207,22 @@
{
"name": "id",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "name",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk1_col",
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
}
]
},
@@ -1705,7 +2243,12 @@
],
"foreign_keys": [
{
- "names" : [["product-unordered-related-tables-links", "fk_schedule_accommodation"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_schedule_accommodation"
+ ]
+ ],
"comment": null,
"foreign_key_columns": [
{
@@ -1736,7 +2279,9 @@
"typename": "serial4"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
},
{
@@ -1748,7 +2293,9 @@
"typename": "int4"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
},
{
@@ -1760,7 +2307,9 @@
"typename": "text"
},
"annotations": {
- "comment": ["hidden"]
+ "comment": [
+ "hidden"
+ ]
}
}
],
@@ -1778,40 +2327,67 @@
"compact": "tag:isrd.isi.edu,2016:chaise:search"
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "compact/brief": ["summary"],
+ "compact/brief": [
+ "summary"
+ ],
"filter": {
"and": [
- {"source": [{"outbound": ["product-unordered-related-tables-links", "fk_schedule_accommodation"]}], "markdown_name": "Accommodations"}
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_schedule_accommodation"
+ ]
+ }
+ ],
+ "markdown_name": "Accommodations"
+ }
]
}
}
}
},
"related_table_2": {
- "comment":"has a foreignkey to related_table",
+ "comment": "has a foreignkey to related_table",
"kind": "table",
"table_name": "related_table_2",
"schema_name": "product-unordered-related-tables-links",
- "keys": [{
- "comment": null,
- "annotations": {},
- "unique_columns": ["id"]
- }],
- "foreign_keys": [{
- "names" : [["product-unordered-related-tables-links", "fk_related_table_2_to_related"]],
- "comment": null,
- "foreign_key_columns": [{
- "table_name": "related_table_2",
- "schema_name": "product-unordered-related-tables-links",
- "column_name": "fk_to_rel"
- }],
- "annotations": {},
- "referenced_columns": [{
- "table_name": "related_table",
- "schema_name": "product-unordered-related-tables-links",
- "column_name": "id"
- }]
- }],
+ "keys": [
+ {
+ "comment": null,
+ "annotations": {},
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
+ "foreign_keys": [
+ {
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_related_table_2_to_related"
+ ]
+ ],
+ "comment": null,
+ "foreign_key_columns": [
+ {
+ "table_name": "related_table_2",
+ "schema_name": "product-unordered-related-tables-links",
+ "column_name": "fk_to_rel"
+ }
+ ],
+ "annotations": {},
+ "referenced_columns": [
+ {
+ "table_name": "related_table",
+ "schema_name": "product-unordered-related-tables-links",
+ "column_name": "id"
+ }
+ ]
+ }
+ ],
"column_definitions": [
{
"name": "id",
@@ -1835,10 +2411,18 @@
],
"annotations": {
"tag:isrd.isi.edu,2016:table-display": {
- "*": {"row_order": [{"column": "id"}]}
+ "*": {
+ "row_order": [
+ {
+ "column": "id"
+ }
+ ]
+ }
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "compact/brief": ["col"]
+ "compact/brief": [
+ "col"
+ ]
}
}
},
@@ -1847,9 +2431,13 @@
"kind": "table",
"table_name": "table_w_aggregates",
"schema_name": "product-unordered-related-tables-links",
- "keys": [{
- "unique_columns": ["id"]
- }],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"column_definitions": [
{
"name": "id",
@@ -1873,7 +2461,12 @@
],
"foreign_keys": [
{
- "names" : [["product-unordered-related-tables-links", "fk_table_w_agg_accommodation"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_table_w_agg_accommodation"
+ ]
+ ],
"comment": null,
"foreign_key_columns": [
{
@@ -1896,33 +2489,65 @@
"tag:isrd.isi.edu,2016:visible-columns": {
"*": [
"id",
- ["product-unordered-related-tables-links", "fk_table_w_agg_accommodation"],
- {"source": "value", "aggregate": "max"},
- {"sourcekey": "value_min"},
- {"source": "value", "aggregate": "cnt"},
- {"source": "value", "aggregate": "cnt_d"},
+ [
+ "product-unordered-related-tables-links",
+ "fk_table_w_agg_accommodation"
+ ],
+ {
+ "source": "value",
+ "aggregate": "max"
+ },
+ {
+ "sourcekey": "value_min"
+ },
+ {
+ "source": "value",
+ "aggregate": "cnt"
+ },
+ {
+ "source": "value",
+ "aggregate": "cnt_d"
+ },
{
"markdown_name": "virtual column",
"display": {
"template_engine": "handlebars",
"markdown_pattern": "virtual {{{value_min}}} with {{{outbound_to_accommodation.rowName}}}",
- "wait_for": ["value_min", "outbound_to_accommodation"]
+ "wait_for": [
+ "value_min",
+ "outbound_to_accommodation"
+ ]
}
}
]
},
"tag:isrd.isi.edu,2016:table-display": {
- "*": {"row_order": [{"column": "id"}]}
+ "*": {
+ "row_order": [
+ {
+ "column": "id"
+ }
+ ]
+ }
},
"tag:isrd.isi.edu,2019:source-definitions": {
"fkeys": true,
"columns": true,
"sources": {
"value_min": {
- "source": "value", "aggregate": "min"
+ "source": "value",
+ "aggregate": "min"
},
"outbound_to_accommodation": {
- "source": [{"outbound": ["product-unordered-related-tables-links", "fk_table_w_agg_accommodation"]}, "RID"]
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "fk_table_w_agg_accommodation"
+ ]
+ },
+ "RID"
+ ]
}
}
}
@@ -1932,27 +2557,41 @@
"table_name": "table_w_invalid_row_markdown_pattern",
"schema_name": "product-unordered-related-tables-links",
"kind": "table",
- "keys": [{
- "unique_columns": ["id"]
- }],
- "foreign_keys": [{
- "names": [
- ["product-unordered-related-tables-links", "fk_table_invalid_markdown"]
- ],
- "comment": null,
- "foreign_key_columns": [{
- "table_name": "table_w_invalid_row_markdown_pattern",
- "schema_name": "product-unordered-related-tables-links",
- "column_name": "id"
- }],
- "annotations": {},
- "referenced_columns": [{
- "table_name": "accommodation",
- "schema_name": "product-unordered-related-tables-links",
- "column_name": "id"
- }]
- }],
- "column_definitions": [{
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
+ "foreign_keys": [
+ {
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "fk_table_invalid_markdown"
+ ]
+ ],
+ "comment": null,
+ "foreign_key_columns": [
+ {
+ "table_name": "table_w_invalid_row_markdown_pattern",
+ "schema_name": "product-unordered-related-tables-links",
+ "column_name": "id"
+ }
+ ],
+ "annotations": {},
+ "referenced_columns": [
+ {
+ "table_name": "accommodation",
+ "schema_name": "product-unordered-related-tables-links",
+ "column_name": "id"
+ }
+ ]
+ }
+ ],
+ "column_definitions": [
+ {
"name": "id",
"nullok": false,
"type": {
@@ -1976,7 +2615,10 @@
}
},
"tag:isrd.isi.edu,2016:visible-columns": {
- "*": ["id", "value"]
+ "*": [
+ "id",
+ "value"
+ ]
}
}
},
@@ -1985,7 +2627,11 @@
"table_name": "table_w_rowname",
"schema_name": "product-unordered-related-tables-links",
"keys": [
- {"unique_columns": ["id"]}
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
],
"column_definitions": [
{
@@ -2015,10 +2661,21 @@
"kind": "table",
"schema_name": "product-unordered-related-tables-links",
"table_name": "accommodation_outbound1",
- "keys": [{"unique_columns": ["id"]}],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "accommodation_outbound1_fk1"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "accommodation_outbound1_fk1"
+ ]
+ ],
"foreign_key_columns": [
{
"column_name": "fk1_col",
@@ -2039,28 +2696,40 @@
{
"name": "id",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "name",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk1_col",
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk2_col",
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk3_col",
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk4_col",
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
}
]
},
@@ -2068,22 +2737,34 @@
"kind": "table",
"schema_name": "product-unordered-related-tables-links",
"table_name": "accommodation_outbound1_outbound1",
- "keys": [{"unique_columns": ["id"]}],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"foreign_keys": [],
"column_definitions": [
{
"name": "id",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk1_col",
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "name",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
}
]
},
@@ -2091,10 +2772,21 @@
"kind": "table",
"schema_name": "product-unordered-related-tables-links",
"table_name": "accommodation_inbound1",
- "keys": [{"unique_columns": ["id"]}],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "accommodation_inbound1_fk1"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound1_fk1"
+ ]
+ ],
"foreign_key_columns": [
{
"column_name": "fk1_col",
@@ -2115,16 +2807,22 @@
{
"name": "id",
"nullok": false,
- "type": {"typename": "text"}
- },
+ "type": {
+ "typename": "text"
+ }
+ },
{
"name": "name",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk1_col",
- "type": {"typename": "int4"}
+ "type": {
+ "typename": "int4"
+ }
}
]
},
@@ -2132,10 +2830,21 @@
"kind": "table",
"schema_name": "product-unordered-related-tables-links",
"table_name": "accommodation_inbound2",
- "keys": [{"unique_columns": ["id"]}],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "accommodation_inbound2_fk1"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound2_fk1"
+ ]
+ ],
"foreign_key_columns": [
{
"column_name": "fk1_col",
@@ -2156,16 +2865,22 @@
{
"name": "id",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "name",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk1_col",
- "type": {"typename": "int4"}
+ "type": {
+ "typename": "int4"
+ }
}
]
},
@@ -2173,10 +2888,21 @@
"kind": "table",
"schema_name": "product-unordered-related-tables-links",
"table_name": "accommodation_inbound3",
- "keys": [{"unique_columns": ["id"]}],
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
"foreign_keys": [
{
- "names": [["product-unordered-related-tables-links", "accommodation_inbound3_fk1"]],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "accommodation_inbound3_fk1"
+ ]
+ ],
"foreign_key_columns": [
{
"column_name": "fk1_col",
@@ -2197,18 +2923,317 @@
{
"name": "id",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "name",
"nullok": false,
- "type": {"typename": "text"}
+ "type": {
+ "typename": "text"
+ }
},
{
"name": "fk1_col",
- "type": {"typename": "int4"}
+ "type": {
+ "typename": "int4"
+ }
}
]
+ },
+ "association_table_w_static_column": {
+ "comment": "For testing 'add records' prefill and using bulk create foreign key features (with modal pickers for fk input)",
+ "kind": "table",
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_static_column",
+ "column_definitions": [
+ {
+ "name": "static_col1",
+ "type": {
+ "typename": "int4"
+ }
+ },
+ {
+ "name": "main_fk_col",
+ "nullok": false,
+ "type": {
+ "typename": "int4"
+ }
+ },
+ {
+ "name": "leaf_fk_col",
+ "nullok": false,
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "keys": [
+ {
+ "unique_columns": [
+ "RID"
+ ]
+ },
+ {
+ "unique_columns": [
+ "main_fk_col",
+ "leaf_fk_col"
+ ]
+ }
+ ],
+ "foreign_keys": [
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_static_column",
+ "column_name": "leaf_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "leaf_table_for_static_columns",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "static_to_leaf_fkey"
+ ]
+ ]
+ },
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_static_column",
+ "column_name": "main_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "accommodation",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "static_to_accommodation_fkey"
+ ]
+ ]
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "entry": [
+ "static_col1",
+ [
+ "product-unordered-related-tables-links",
+ "static_to_accommodation_fkey"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "static_to_leaf_fkey"
+ ]
+ ],
+ "compact": "entry",
+ "detailed": "entry"
+ }
+ }
+ },
+ "leaf_table_for_static_columns": {
+ "comment": "leaf table for testing an association with static columns",
+ "kind": "table",
+ "table_name": "leaf_table_for_static_columns",
+ "schema_name": "product-unordered-related-tables-links",
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
+ "foreign_keys": [],
+ "column_definitions": [
+ {
+ "name": "id",
+ "type": {
+ "typename": "text"
+ }
+ },
+ {
+ "name": "details",
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:table-display": {
+ "row_name": {
+ "row_markdown_pattern": "{{{details}}}"
+ }
+ },
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "compact": [
+ "id",
+ "details"
+ ]
+ }
+ }
+ },
+ "association_table_w_static_column_dropdown": {
+ "comment": "For testing 'add records' prefill and using bulk create foreign key features (with dropdown pickers for fk input)",
+ "kind": "table",
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_static_column_dropdown",
+ "column_definitions": [
+ {
+ "name": "static_col1",
+ "type": {
+ "typename": "int4"
+ }
+ },
+ {
+ "name": "main_fk_col",
+ "nullok": false,
+ "type": {
+ "typename": "int4"
+ }
+ },
+ {
+ "name": "leaf_fk_col",
+ "nullok": false,
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "keys": [
+ {
+ "unique_columns": [
+ "RID"
+ ]
+ },
+ {
+ "unique_columns": [
+ "main_fk_col",
+ "leaf_fk_col"
+ ]
+ }
+ ],
+ "foreign_keys": [
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_static_column_dropdown",
+ "column_name": "leaf_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "leaf_table_for_static_columns_dropdown",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "static_to_leaf_w_dropdown_fkey"
+ ]
+ ]
+ },
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_static_column_dropdown",
+ "column_name": "main_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "accommodation",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "static_to_accommodation_w_dropdown_fkey"
+ ]
+ ]
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "entry": [
+ "static_col1",
+ [
+ "product-unordered-related-tables-links",
+ "static_to_accommodation_w_dropdown_fkey"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "static_to_leaf_w_dropdown_fkey"
+ ]
+ ],
+ "compact": "entry",
+ "detailed": "entry"
+ }
+ }
+ },
+ "leaf_table_for_static_columns_dropdown": {
+ "comment": "leaf table for testing an association with static columns (and dropdown for fk input)",
+ "kind": "table",
+ "table_name": "leaf_table_for_static_columns_dropdown",
+ "schema_name": "product-unordered-related-tables-links",
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
+ "foreign_keys": [],
+ "column_definitions": [
+ {
+ "name": "id",
+ "type": {
+ "typename": "text"
+ }
+ },
+ {
+ "name": "details",
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:table-display": {
+ "entry": {
+ "selector_ux_mode": "simple-search-dropdown"
+ },
+ "row_name": {
+ "row_markdown_pattern": "{{{details}}}"
+ }
+ },
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "compact": [
+ "id",
+ "details"
+ ]
+ }
+ }
}
},
"comment": null,
@@ -2216,7 +3241,6 @@
"tag:misd.isi.edu,2015:display": {
"name": "accommodation"
}
-
},
"schema_name": "product-unordered-related-tables-links"
}
diff --git a/test/e2e/locators/modal.ts b/test/e2e/locators/modal.ts
index 40ee715a2..31342d4f9 100644
--- a/test/e2e/locators/modal.ts
+++ b/test/e2e/locators/modal.ts
@@ -37,6 +37,10 @@ export default class ModalLocators {
return page.locator('.foreignkey-popup');
}
+ static getRecordeditBulkFKPopup(page: Page): Locator {
+ return page.locator('.bulk-foreign-key-popup');
+ }
+
static getErrorModal(page: Page): Locator {
return page.locator('.modal-error');
}
diff --git a/test/e2e/locators/recordedit.ts b/test/e2e/locators/recordedit.ts
index ce9758f75..30475949c 100644
--- a/test/e2e/locators/recordedit.ts
+++ b/test/e2e/locators/recordedit.ts
@@ -87,6 +87,10 @@ export default class RecordeditLocators {
return container.locator('#bulk-delete-button');
}
+ static getAddMoreButton(container: Locator | Page): Locator {
+ return container.locator('#recordedit-add-more');
+ }
+
static getRecordeditForms(container: Locator | Page): Locator {
return container.locator('.recordedit-form .form-header');
}
@@ -123,6 +127,7 @@ export default class RecordeditLocators {
return container.locator('button.remove-form-btn');
}
+ // Forms are indexed starting with 1, delete row button is indexed with 0
static getDeleteRowButton(container: Locator | Page, index: number): Locator {
index = index || 0;
return RecordeditLocators.getAllDeleteRowButtons(container).nth(index);
@@ -334,6 +339,12 @@ export default class RecordeditLocators {
return container.locator(`.input-switch-container-${inputName} .dropdown-toggle`);
}
+ static getDropdownMenuByName = (container: Locator | Page, name: string, formNumber: number) => {
+ formNumber = formNumber || 1;
+ const inputName = `c_${formNumber}-${name}`;
+ return container.locator(`.input-switch-container-${inputName} .dropdown-menu`);
+ }
+
// --------------- foreign key dropdown selectors ------------- //
static getFkeyDropdowns(container: Locator | Page): Locator {
return container.locator('.fk-dropdown');
@@ -343,6 +354,18 @@ export default class RecordeditLocators {
return container.locator('.dropdown-menu.show').locator('.dropdown-select-value');
}
+ static getFKDropdownOptions(container: Locator | Page): Locator {
+ return container.locator('.dropdown-menu.show').locator('.dropdown-list li');
+ }
+
+ static getDropdownDisabledOptions(container: Locator | Page): Locator {
+ return container.locator('.dropdown-menu.show').locator('.dropdown-item.disabled');
+ }
+
+ static getDropdownRow(container: Locator | Page, index: number): Locator {
+ return this.getDropdownSelectableOptions(container).nth(index);
+ }
+
static getDropdownLoadMoreRow(container: Locator | Page): Locator {
return container.locator('.dropdown-menu .load-more-row');
}
diff --git a/test/e2e/specs/all-features/record/related-table.spec.ts b/test/e2e/specs/all-features/record/related-table.spec.ts
index d807771ec..ba19a984e 100644
--- a/test/e2e/specs/all-features/record/related-table.spec.ts
+++ b/test/e2e/specs/all-features/record/related-table.spec.ts
@@ -1,4 +1,4 @@
-import { expect, Page, test } from '@playwright/test';
+import { expect, Locator, Page, test } from '@playwright/test';
import moment from 'moment';
//locators
@@ -10,12 +10,13 @@ import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset'
//utils
import { getCatalogID, getEntityRow, importACLs } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils';
import { APP_NAMES, RESTRICTED_USER_STORAGE_STATE } from '@isrd-isi-edu/chaise/test/e2e/utils/constants';
-import { testTooltip } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils';
+import { clickNewTabLink, testTooltip } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils';
import {
- testAddAssociationTable, testAddRelatedTable, testBatchUnlinkAssociationTable,
- testRelatedTablePresentation, testShareCiteModal
+ testAddAssociationTable, testAddRelatedTable, testAddRelatedWithForeignKeyMultiPicker,
+ testBatchUnlinkAssociationTable, testRelatedTablePresentation, testShareCiteModal
} from '@isrd-isi-edu/chaise/test/e2e/utils/record-utils';
-import { testRecordsetTableRowValues, testTotalCount } from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils';
+import { testInputValue } from '@isrd-isi-edu/chaise/test/e2e/utils/recordedit-utils';
+import { testModalClose, testRecordsetTableRowValues, testTotalCount } from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils';
const testParams = {
@@ -42,7 +43,9 @@ const testParams = {
'inbound related with filter on related table', // related entity with filter on related table
'association with filter on main table',
'association with filter on related table', // association with filter on related table
- 'path of length 3 with filters' // path of length 3 with filters
+ 'path of length 3 with filters', // path of length 3 with filters
+ 'association_table_w_static_column', // "almost" pure and binary multi create foreig key with fk input modals
+ 'association_table_w_static_column_dropdown' // "almost" pure and binary multi create foreig key with fk input dropdowns
],
tocHeaders: [
'Summary', 'booking (6)', 'schedule (2)', 'media (1)', 'association_table (1)',
@@ -54,7 +57,9 @@ const testParams = {
'inbound related with filter on related table (1)',
'association with filter on main table (1)',
'association with filter on related table (1)',
- 'path of length 3 with filters (1)'
+ 'path of length 3 with filters (1)',
+ 'association_table_w_static_column (1)',
+ 'association_table_w_static_column_dropdown (1)'
],
scrollToDisplayname: 'table_w_aggregates'
};
@@ -831,6 +836,131 @@ test.describe('Related tables', () => {
});
});
+ /**
+ * The following 2 tests are for testing the prefill functionality when the inbound foreign key is part of a table that is "almost" pure and binary
+ *
+ * This means there are 2 foreign keys that are part of the same key (making the pair unique) and there are other columns that are not foreign keys
+ */
+ test.describe('for a table that is almost pure and binary and the foreign keys are a unique key', async () => {
+ const params = {
+ table_name: 'association_table_w_static_column',
+ prefill_col: 'main_fk_col',
+ leaf_col: 'leaf_fk_col',
+ leaf_fk_name: 'leaf_fk_col',
+ prefill_value: 'Super 8 North Hollywood Motel',
+ column_names: ['static1', 'main_fk_col', 'leaf_fk_col'],
+ resultset_values: [['', '2004', '10'], ['', '2004', '7']],
+ related_table_values: [['2', 'Leaf 2'], ['', 'Leaf 10'], ['', 'Leaf 7']],
+ bulk_modal_title: 'Select a set of leaf_fk_col for association_table_w_static_column'
+ }
+
+ test('with fk inputs as modals', async ({ page }) => {
+ await testAddRelatedWithForeignKeyMultiPicker(page, params, RecordeditInputType.FK_POPUP);
+ });
+
+ /**
+ * this test verifies the functionality when the first modal is closed and does NOT fill in the first form
+ * subsequent actions in add more should continue to add new forms without filling the first form
+ *
+ * This test ensures the tracking of bulkForeignKeySelectedRows is done right when the first form has no value filled in by the initial modal
+ */
+ test('closing the initial modal and using "Add more" to add more forms with values', async ({ page }) => {
+ let newPage: Page, bulkFKModal: Locator;
+
+ await test.step('should open recordedit with a modal picker showing', async () => {
+ const addBtn = RecordLocators.getRelatedTableAddButton(page, params.table_name);
+
+ newPage = await clickNewTabLink(addBtn);
+ await RecordeditLocators.waitForRecordeditPageReady(newPage);
+
+ bulkFKModal = ModalLocators.getRecordeditBulkFKPopup(newPage);
+ await expect.soft(bulkFKModal).toBeAttached();
+ });
+
+ await test.step('modal should have 3 disabled rows', async () => {
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(3);
+
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(3);
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled-row/); // Leaf 2
+ await expect.soft(rows.nth(6)).toHaveClass(/disabled-row/); // Leaf 7
+ await expect.soft(rows.nth(9)).toHaveClass(/disabled-row/); // Leaf 10
+ });
+
+ await test.step('closing the initial modal should not add any new forms AND not fill the first form', async () => {
+ await testModalClose(bulkFKModal);
+
+ await expect.soft(RecordeditLocators.getRecordeditForms(newPage)).toHaveCount(1);
+ await testInputValue(newPage, 1, params.prefill_col, params.prefill_col, RecordeditInputType.FK_POPUP, false, params.prefill_value);
+ await testInputValue(newPage, 1, params.leaf_col, params.leaf_col, RecordeditInputType.FK_POPUP, false, 'Select a value');
+ });
+
+ await test.step('clicking "add more" should have 3 rows disabled', async () => {
+ await RecordeditLocators.getAddMoreButton(newPage).click();
+ // The same modal when the page loaded should show again
+ await expect.soft(bulkFKModal).toBeAttached();
+
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(3);
+
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(3);
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled-row/); // Leaf 2
+ await expect.soft(rows.nth(6)).toHaveClass(/disabled-row/); // Leaf 7
+ await expect.soft(rows.nth(9)).toHaveClass(/disabled-row/); // Leaf 10
+ });
+
+ await test.step('select 2 more rows and submit the selection', async () => {
+ await RecordsetLocators.getRowCheckboxInput(bulkFKModal, 0).click();
+ await RecordsetLocators.getRowCheckboxInput(bulkFKModal, 4).click();
+
+ await ModalLocators.getSubmitButton(bulkFKModal).click();
+ await expect.soft(bulkFKModal).not.toBeAttached();
+
+ await expect.soft(RecordeditLocators.getRecordeditForms(newPage)).toHaveCount(3);
+ });
+
+ await test.step('first form should still not be filled in', async () => {
+ await testInputValue(newPage, 1, params.leaf_col, params.leaf_col, RecordeditInputType.FK_POPUP, false, 'Select a value');
+ });
+
+ await test.step('2 new forms should have expected values filled in for prefill and new modal selections', async () => {
+ await testInputValue(newPage, 2, params.prefill_col, params.prefill_col, RecordeditInputType.FK_POPUP, false, params.prefill_value);
+ await testInputValue(newPage, 3, params.prefill_col, params.prefill_col, RecordeditInputType.FK_POPUP, false, params.prefill_value);
+
+ await testInputValue(newPage, 2, params.leaf_col, params.leaf_col, RecordeditInputType.FK_POPUP, false, 'Leaf 1');
+ await testInputValue(newPage, 3, params.leaf_col, params.leaf_col, RecordeditInputType.FK_POPUP, false, 'Leaf 5');
+ });
+
+ await test.step('removing the 2nd form should have the correct rows disabled in the "Add more" modal', async () => {
+ await RecordeditLocators.getDeleteRowButton(newPage, 1).click();
+
+ await RecordeditLocators.getAddMoreButton(newPage).click();
+ // The same modal when the page loaded should show again
+ await expect.soft(bulkFKModal).toBeAttached();
+
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(4);
+
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(4);
+ await expect.soft(rows.nth(0)).not.toHaveClass(/disabled-row/); // Leaf 1 - the row that was just removed
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled-row/); // Leaf 2
+ await expect.soft(rows.nth(4)).toHaveClass(/disabled-row/); // Leaf 5
+ await expect.soft(rows.nth(6)).toHaveClass(/disabled-row/); // Leaf 7
+ await expect.soft(rows.nth(9)).toHaveClass(/disabled-row/); // Leaf 10
+ });
+ });
+
+ test('with fk inputs as dropdowns', async ({ page }) => {
+ params.table_name = 'association_table_w_static_column_dropdown';
+ params.leaf_fk_name = 'U0KYeFQJ-lwuLEaGb2RNRg';
+ params.bulk_modal_title = 'Select a set of leaf_fk_col for association_table_w_static_column_dropdown'
+
+ await testAddRelatedWithForeignKeyMultiPicker(page, params, RecordeditInputType.FK_DROPDOWN);
+ });
+ });
});
test.describe('Scroll to query parameter', () => {
diff --git a/test/e2e/utils/page-utils.ts b/test/e2e/utils/page-utils.ts
index cbdf5377b..1cfcc15d7 100644
--- a/test/e2e/utils/page-utils.ts
+++ b/test/e2e/utils/page-utils.ts
@@ -102,7 +102,6 @@ export async function clickAndVerifyDownload(locator: Locator, expectedFileName:
*/
export async function testTooltip(locator: Locator, expectedTooltip: string | RegExp, appName: APP_NAMES, isSoft?: boolean, hoverEl?: Locator) {
await locator.hover();
- await locator.page().pause();
const el = PageLocators.getTooltipContainer(locator.page());
diff --git a/test/e2e/utils/record-utils.ts b/test/e2e/utils/record-utils.ts
index 92a58b362..a9fa0766a 100644
--- a/test/e2e/utils/record-utils.ts
+++ b/test/e2e/utils/record-utils.ts
@@ -6,14 +6,16 @@ import RecordLocators from '@isrd-isi-edu/chaise/test/e2e/locators/record';
import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit';
import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset';
+// utils
import { EntityRowColumnValues, getCatalogID, getEntityRow } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils';
import { APP_NAMES, PW_PROJECT_NAMES } from '@isrd-isi-edu/chaise/test/e2e/utils/constants';
import {
clickAndVerifyDownload, clickNewTabLink, getClipboardContent,
manuallyTriggerFocus, testTooltip
} from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils';
+import { clearInputValue, testInputValue, testSubmission } from '@isrd-isi-edu/chaise/test/e2e/utils/recordedit-utils';
import {
- RecordsetColValue, RecordsetRowValue,
+ RecordsetColValue, RecordsetRowValue, testModalClose,
testRecordsetTableRowValues, testTotalCount
} from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils';
@@ -760,3 +762,251 @@ export const testDeleteConfirm = async (page: Page, btn: Locator, confirmText: s
await ModalLocators.getOkButton(modal).click();
await expect.soft(modal).not.toBeAttached();
}
+
+type AddRecordsForeignKeyMultiParams = {
+ table_name: string,
+ prefill_col: string,
+ leaf_col: string,
+ leaf_fk_name: string,
+ prefill_value: string,
+ column_names: string[],
+ resultset_values: RecordsetRowValue[],
+ related_table_values: RecordsetRowValue[],
+ bulk_modal_title: string
+}
+
+/**
+ * Function to test foreign key multi picker when there is a prefill query param from record app in recordedit
+ * can test both modal and dropdown foreign key input types
+ *
+ * @param params params for the test
+ * @param inputType RecordeditInputType for popup or dropdown
+ */
+export const testAddRelatedWithForeignKeyMultiPicker = async (
+ page: Page,
+ params: AddRecordsForeignKeyMultiParams,
+ inputType: RecordeditInputType.FK_DROPDOWN | RecordeditInputType.FK_POPUP
+) => {
+ let newPage: Page, bulkFKModal: Locator, fkInputModal: Locator, fkInputDropdown: Locator, dropdownMenu: Locator;
+
+ const isModal = inputType === RecordeditInputType.FK_POPUP;
+
+ await test.step('should open recordedit with a modal picker showing', async () => {
+ const addBtn = RecordLocators.getRelatedTableAddButton(page, params.table_name);
+
+ newPage = await clickNewTabLink(addBtn);
+ await RecordeditLocators.waitForRecordeditPageReady(newPage);
+
+ bulkFKModal = ModalLocators.getRecordeditBulkFKPopup(newPage);
+ await expect.soft(bulkFKModal).toBeAttached();
+ await expect.soft(ModalLocators.getModalTitle(bulkFKModal)).toHaveText(params.bulk_modal_title);
+ });
+
+ await test.step('modal should have 1 row selected and disabled', async () => {
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(1);
+
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(1);
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled-row/);
+ });
+
+ await test.step('select 2 rows and submit the selection', async () => {
+ await RecordsetLocators.getRowCheckboxInput(bulkFKModal, 3).click();
+ await RecordsetLocators.getRowCheckboxInput(bulkFKModal, 4).click();
+
+ await ModalLocators.getSubmitButton(bulkFKModal).click();
+ await expect.soft(bulkFKModal).not.toBeAttached();
+
+ await expect.soft(RecordeditLocators.getRecordeditForms(newPage)).toHaveCount(2);
+ });
+
+ await test.step('2 forms should have expected values filled in for prefill and modal selections', async () => {
+ await testInputValue(newPage, 1, params.prefill_col, params.prefill_col, inputType, false, params.prefill_value);
+ await testInputValue(newPage, 2, params.prefill_col, params.prefill_col, inputType, false, params.prefill_value);
+
+ await testInputValue(newPage, 1, params.leaf_col, params.leaf_col, inputType, false, 'Leaf 4');
+ await testInputValue(newPage, 2, params.leaf_col, params.leaf_col, inputType, false, 'Leaf 5');
+ });
+
+ await test.step('clicking a foreign key input should show a modal/dropdown with 2 rows disabled', async () => {
+ let rows;
+ if (isModal) {
+ fkInputModal = ModalLocators.getForeignKeyPopup(newPage);
+ await RecordeditLocators.getForeignKeyInputButton(newPage, params.leaf_fk_name, 1).click();
+
+ await expect.soft(fkInputModal).toBeAttached();
+ await expect.soft(RecordsetLocators.getDisabledRows(fkInputModal)).toHaveCount(2);
+
+ rows = RecordsetLocators.getRows(fkInputModal);
+ } else {
+ fkInputDropdown = RecordeditLocators.getDropdownElementByName(newPage, params.leaf_fk_name, 1);
+ dropdownMenu = RecordeditLocators.getDropdownMenuByName(newPage, params.leaf_fk_name, 1);
+ await fkInputDropdown.click();
+
+ await expect.soft(dropdownMenu).toBeVisible();
+ await expect.soft(RecordeditLocators.getDropdownDisabledOptions(newPage)).toHaveCount(2);
+
+ rows = RecordeditLocators.getFKDropdownOptions(newPage);
+ }
+
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled/);
+ await expect.soft(rows.nth(4)).toHaveClass(/disabled/);
+ });
+
+ await test.step('test tooltips for disabled rows and already selected row', async () => {
+ let associatedSelector, otherInputSelector, app;
+ if (isModal) {
+ associatedSelector = RecordsetLocators.getRowSelectButton(fkInputModal, 1);
+ otherInputSelector = RecordsetLocators.getRowSelectButton(fkInputModal, 4)
+ app = APP_NAMES.RECORDSET;
+
+ // test row tooltip for row that is already selected for this input
+ await testTooltip(RecordsetLocators.getRowSelectButton(fkInputModal, 3), 'Selected', app, true);
+ } else {
+ associatedSelector = RecordeditLocators.getDropdownRow(newPage, 1);
+ otherInputSelector = RecordeditLocators.getDropdownRow(newPage, 4);
+ app = APP_NAMES.RECORDEDIT;
+
+ // no tooltip on selected row in fk dropdown
+ }
+
+ // test row tooltip that is associated when catalog created
+ await testTooltip(associatedSelector, 'This row is already associated', app, true);
+
+ // test row tooltip for row that is selected for another input
+ await testTooltip(otherInputSelector, 'This row is selected in another input in the form', app, true);
+ });
+
+ await test.step('selecting a row should update the input we selected a value for', async () => {
+ if (isModal) {
+ await RecordsetLocators.getRowSelectButton(fkInputModal, 9).click();
+ await expect.soft(fkInputModal).not.toBeAttached();
+ } else {
+ await RecordeditLocators.getDropdownRow(newPage, 9).click();
+ await expect.soft(dropdownMenu).not.toBeVisible();
+ }
+
+ await expect.soft(RecordeditLocators.getForeignKeyInputDisplay(newPage, params.leaf_col, 1)).not.toHaveText('Leaf 4');
+ await testInputValue(newPage, 1, params.leaf_col, params.leaf_col, inputType, false, 'Leaf 10');
+
+ // make sure other input didn't change
+ await testInputValue(newPage, 2, params.leaf_col, params.leaf_col, inputType, false, 'Leaf 5');
+ });
+
+ await test.step('clicking "add more" should have 3 rows disabled', async () => {
+ await RecordeditLocators.getAddMoreButton(newPage).click();
+ // The same modal when the page loaded should show again
+ await expect.soft(bulkFKModal).toBeAttached();
+
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(3);
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(3);
+
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled-row/);
+ await expect.soft(rows.nth(4)).toHaveClass(/disabled-row/);
+ await expect.soft(rows.nth(9)).toHaveClass(/disabled-row/);
+ });
+
+ await test.step('select 2 more rows and submit the selection', async () => {
+ await RecordsetLocators.getRowCheckboxInput(bulkFKModal, 0).click();
+ await RecordsetLocators.getRowCheckboxInput(bulkFKModal, 6).click();
+
+ await ModalLocators.getSubmitButton(bulkFKModal).click();
+ await expect.soft(bulkFKModal).not.toBeAttached();
+
+ await expect.soft(RecordeditLocators.getRecordeditForms(newPage)).toHaveCount(4);
+ });
+
+ await test.step('2 new forms should have expected values filled in for prefill and new modal selections', async () => {
+ await testInputValue(newPage, 3, params.prefill_col, params.prefill_col, inputType, false, params.prefill_value);
+ await testInputValue(newPage, 4, params.prefill_col, params.prefill_col, inputType, false, params.prefill_value);
+
+ await testInputValue(newPage, 3, params.leaf_col, params.leaf_col, inputType, false, 'Leaf 1');
+ await testInputValue(newPage, 4, params.leaf_col, params.leaf_col, inputType, false, 'Leaf 7');
+ });
+
+ await test.step('clicking a different foreign key input should show a modal with 4 rows disabled', async () => {
+ let rows;
+ if (isModal) {
+ await RecordeditLocators.getForeignKeyInputButton(newPage, params.leaf_fk_name, 3).click();
+
+ await expect.soft(fkInputModal).toBeAttached();
+ await expect.soft(RecordsetLocators.getDisabledRows(fkInputModal)).toHaveCount(4);
+
+ rows = RecordsetLocators.getRows(fkInputModal);
+ } else {
+ fkInputDropdown = RecordeditLocators.getDropdownElementByName(newPage, params.leaf_fk_name, 3);
+ dropdownMenu = RecordeditLocators.getDropdownMenuByName(newPage, params.leaf_fk_name, 3);
+ await fkInputDropdown.click();
+
+ await expect.soft(dropdownMenu).toBeVisible();
+ await expect.soft(RecordeditLocators.getDropdownDisabledOptions(newPage)).toHaveCount(4);
+
+ rows = RecordeditLocators.getFKDropdownOptions(newPage);
+ }
+
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(rows.nth(1)).toHaveClass(/disabled/);
+ await expect.soft(rows.nth(4)).toHaveClass(/disabled/);
+ await expect.soft(rows.nth(6)).toHaveClass(/disabled/);
+ await expect.soft(rows.nth(9)).toHaveClass(/disabled/);
+
+ if (isModal) {
+ await testModalClose(fkInputModal);
+ } else {
+ await fkInputDropdown.click();
+ await expect.soft(dropdownMenu).not.toBeVisible();
+ }
+ });
+
+ await test.step('clicking x for an input should clear the value and update disabled rows in "add more"', async () => {
+ // clear the value in the 2nd form
+ await clearInputValue(newPage, 2, params.leaf_col, params.leaf_col, inputType);
+ await testInputValue(newPage, 2, params.leaf_col, params.leaf_col, inputType, false, 'Select a value');
+
+ await RecordeditLocators.getAddMoreButton(newPage).click();
+ await expect.soft(bulkFKModal).toBeAttached();
+
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(4);
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(4);
+
+ await testModalClose(bulkFKModal);
+ });
+
+ await test.step('remove the cleared input form and another form, then verify rows disabled in "add more" once more', async () => {
+ // remove in reverse order
+ await RecordeditLocators.getDeleteRowButton(newPage, 2).click();
+ await RecordeditLocators.getDeleteRowButton(newPage, 1).click();
+
+ await RecordeditLocators.getAddMoreButton(newPage).click();
+ await expect.soft(bulkFKModal).toBeAttached();
+
+ const rows = RecordsetLocators.getRows(bulkFKModal);
+ await expect.soft(rows).toHaveCount(10);
+ await expect.soft(RecordsetLocators.getCheckedCheckboxInputs(bulkFKModal)).toHaveCount(3);
+ await expect.soft(RecordsetLocators.getDisabledRows(bulkFKModal)).toHaveCount(3);
+
+ await testModalClose(bulkFKModal);
+ });
+
+ await test.step('submit the data and test submission table', async () => {
+ await testSubmission(newPage, {
+ tableDisplayname: params.table_name,
+ resultColumnNames: params.column_names,
+ resultRowValues: params.resultset_values
+ });
+ });
+
+ await test.step('close the tab and record app should update the related table with the new rows', async () => {
+ await newPage.close();
+ await manuallyTriggerFocus(page);
+
+ const prefillTable = RecordLocators.getRelatedTableContainer(page, params.table_name);
+ await testRecordsetTableRowValues(prefillTable, params.related_table_values, true);
+ });
+}
diff --git a/test/e2e/utils/recordedit-utils.ts b/test/e2e/utils/recordedit-utils.ts
index fd07ac9ac..66757011c 100644
--- a/test/e2e/utils/recordedit-utils.ts
+++ b/test/e2e/utils/recordedit-utils.ts
@@ -146,6 +146,7 @@ export const clearInputValue = async (
page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType,
) => {
switch (inputType) {
+ case RecordeditInputType.FK_DROPDOWN:
case RecordeditInputType.FK_POPUP:
const fkBtn = RecordeditLocators.getForeignKeyInputClear(page, displayname, formNumber);
await fkBtn.click();
@@ -606,8 +607,25 @@ const _testInputValidationAndExtraFeatures = async (
case RecordeditInputType.FK_POPUP:
const displayedValue = RecordeditLocators.getForeignKeyInputDisplay(page, displayname, formNumber);
+ const rsModal = ModalLocators.getForeignKeyPopup(page);
if (typeof existingValue === 'string') {
+ // before clearing the value, ensure the selected row has the right tooltip in the fk input modal first
+ await test.step('check the tooltip of the selected row in the modal before clearing the value', async () => {
+ await RecordeditLocators.getForeignKeyInputButton(page, displayname, formNumber).click();
+ await expect.soft(rsModal).toBeVisible();
+
+ // In the multi edit spec, we have 2 forms
+ // in the 1st form, the 1st row is selected
+ // in the 2nd form, the 3rd row is selected
+ const selectedRowIndex = formNumber === 1 ? 0 : 2;
+ await testTooltip(RecordsetLocators.getRowSelectButton(rsModal, selectedRowIndex), 'Selected', APP_NAMES.RECORDSET, true);
+
+ await ModalLocators.getCloseBtn(rsModal).click();
+ await expect.soft(rsModal).not.toBeAttached();
+ });
+
+ // value should be unchanged from previous test since the modal was closed with no selection made
await test.step('clicking the "x" should remove the value in the foreign key field.', async () => {
await expect.soft(displayedValue).toHaveText(existingValue);
await RecordeditLocators.getForeignKeyInputClear(page, displayname, formNumber).click();
@@ -616,7 +634,6 @@ const _testInputValidationAndExtraFeatures = async (
}
await test.step('popup selector', async () => {
- const rsModal = ModalLocators.getForeignKeyPopup(page);
await test.step('should have the proper title.', async () => {
await RecordeditLocators.getForeignKeyInputButton(page, displayname, formNumber).click();
await expect.soft(rsModal).toBeVisible();
From af9cbbc0ec3f7dcf785f82d17785210addf2e5d0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Oct 2024 15:54:57 -0700
Subject: [PATCH 03/30] Bump webpack from 5.94.0 to 5.95.0 (#2553)
* Bump webpack from 5.94.0 to 5.95.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.94.0 to 5.95.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.94.0...v5.95.0)
---
updated-dependencies:
- dependency-name: webpack
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
* wait for list before checking and add retries to misc facet
---------
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aref Shafaei
---
package-lock.json | 6 +++---
.../specs/delete-prohibited/recordset/misc-facet.spec.ts | 3 ++-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a4ac46939..e88b5235d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10218,9 +10218,9 @@
}
},
"node_modules/webpack": {
- "version": "5.94.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
- "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
+ "version": "5.95.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
+ "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
"dependencies": {
"@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1",
diff --git a/test/e2e/specs/delete-prohibited/recordset/misc-facet.spec.ts b/test/e2e/specs/delete-prohibited/recordset/misc-facet.spec.ts
index 7cf8e5863..a76c66841 100644
--- a/test/e2e/specs/delete-prohibited/recordset/misc-facet.spec.ts
+++ b/test/e2e/specs/delete-prohibited/recordset/misc-facet.spec.ts
@@ -257,7 +257,7 @@ const testParams = {
};
test.describe('Other facet features', () => {
- test.describe.configure({ mode: 'parallel' });
+ test.describe.configure({ mode: 'parallel', retries: 3 });
test('selecting entity facet that is not on the shortest key.', async ({ page, baseURL }, testInfo) => {
const facet = RecordsetLocators.getFacetById(page, testParams.filter_secondary_key.facetIdx);
@@ -526,6 +526,7 @@ test.describe('Other facet features', () => {
await expect.soft(RecordsetLocators.getFacetSpinner(facet2)).not.toBeVisible();
await RecordsetLocators.getFacetOption(facet2, params.secondFacet.selectedOption).uncheck();
// wait for facet checkboxes to load for the first facet
+ await expect.soft(RecordsetLocators.getFacetSpinner(facet1)).not.toBeVisible();
await expect.soft(RecordsetLocators.getFacetOptions(facet1)).toHaveText(params.firstFacet.optionsAfterFirstChange);
// make sure all are selected
From 3a20376a74757cea9e745a8979d0d87785c8e0fd Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Tue, 8 Oct 2024 09:32:48 -0700
Subject: [PATCH 04/30] remove deprecated props and unnecessary code from
chaise-config-sample.js
---
config/chaise-config-sample.js | 7 -------
1 file changed, 7 deletions(-)
diff --git a/config/chaise-config-sample.js b/config/chaise-config-sample.js
index b271a800c..9ce7dc089 100644
--- a/config/chaise-config-sample.js
+++ b/config/chaise-config-sample.js
@@ -19,9 +19,6 @@ var chaiseConfig = {
enable: ["*"] // [] <- disable
}
},
- maxColumns: 6,
- feedbackURL: 'http://goo.gl/forms/f30sfheh4H',
- helpURL: '/help/using-the-data-browser/',
editRecord: true,
deleteRecord: true,
maxRecordsetRowHeight: 160,
@@ -109,7 +106,3 @@ var chaiseConfig = {
engine: 'handlebars'
}
};
-
-if (typeof module === 'object' && module.exports && typeof require === 'function') {
- exports.config = chaiseConfig;
-}
From 8694f4567020c347e1249b314b084e662e5a9805 Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Wed, 9 Oct 2024 17:57:27 -0700
Subject: [PATCH 05/30] Facet reoder: Add explicit save and allow reseting to
default (#2558)
* change how facet reorder works by adding more controls
---
help-docs/chaise/facet-panel.md | 56 ++++++
src/assets/scss/_buttons.scss | 17 +-
src/assets/scss/_dropdown.scss | 30 +++-
src/assets/scss/_faceting.scss | 8 +
src/assets/scss/app.scss | 8 +
src/components/faceting/faceting.tsx | 163 +++++++++++++++---
src/models/recordset.ts | 10 ++
src/utils/constants.ts | 22 ++-
src/utils/faceting-utils.ts | 23 ++-
test/e2e/locators/recordset.ts | 22 ++-
.../recordset/reorder-facet.spec.ts | 95 +++++++++-
11 files changed, 405 insertions(+), 49 deletions(-)
create mode 100644 help-docs/chaise/facet-panel.md
diff --git a/help-docs/chaise/facet-panel.md b/help-docs/chaise/facet-panel.md
new file mode 100644
index 000000000..0fdcf9b2d
--- /dev/null
+++ b/help-docs/chaise/facet-panel.md
@@ -0,0 +1,56 @@
+# Filter panel
+
+Filters are the data constraints or restrictions that are applied during search operations. For example, search for "mouse" data, or data submitted by certain "principal investigators".
+
+The filter panel (displayed on the left under "Refine search") contains a list of filter controls used to set these constraints. This page provides details on how the filter panel works. Please find the topics discussed in the table of contents below:
+
+- [Customizing the order of filters](#customizing-the-order-of-filters)
+ - [How to move filters](#how-to-move-filters)
+ - [How to save the order](#how-to-save-the-order)
+ - [How to apply the default order](#how-to-apply-the-default-order)
+ - [How to apply the saved order](#how-to-apply-the-saved-order)
+
+
+## Customizing the order of filters {id=customizing-the-order-of-filters}
+
+You can change the order of filters in the filter panel. This section goes over how you could move filters, save the customized order, and, if needed, discard the customized order.
+
+### How to move filters {id=how-to-move-filters}
+
+To move the filters,
+
+ 1. Left click on the grab ( :span::/span:{.fa-solid .fa-grip-vertical .help-page-icon} ) icon.
+ 2. While holding the left click, move your mouse to the desired location.
+ 3. Release the left click to finish the drag movement.
+
+Keep in mind that this order is not going to be persistent and will change back after refreshing the page. To save the order, please refer to the [How to save the order](#how-to-save-the-order) section.
+
+### How to save the order {id=how-to-save-the-order}
+
+If you would like to save the order in your browser,
+
+ 1. Click on the menu icon (:span::/span:{.fa-solid .fa-bars .help-page-icon}) besides the "Refine search".
+
+ 2. In the opened menu, select the ":span::/span:{.fa-solid .fa-check-to-slot .help-page-icon} Save filter order" option.
+
+This will ensure your customized order is saved in your browser. If you would like to apply the default order, please refer to the [How to discard the saved order](#how-to-apply-the-default-order) section.
+
+### How to apply the default order {id=how-to-apply-the-default-order}
+
+If you've customized the order, or applied the saved state, you may go back to the default order by following these steps:
+
+ 1. Click on the menu icon ( :span::/span:{.fa-solid .fa-bars .help-page-icon} ) besides the "Refine search".
+
+ 2. In the opened menu, select the ":span::/span:{.fa-solid .fa-undo .help-page-icon} Reset to default" option.
+
+With this, the filter panel will reset to the default order. Some filters move, and some might get opened/closed depending on the current state of the page. If you want to return to your saved order, please follow the steps described [here](#how-to-apply-the-saved-order).
+
+### How to apply the saved order {id=how-to-apply-the-saved-order}
+
+If you have saved the filter order before, we will apply this order when you load the page. If you made some modifications to this order, follow these steps to go back to the saved order:
+
+ 1. Click on the menu icon ( :span::/span:{.fa-solid .fa-bars .help-page-icon} ) besides the "Refine search".
+
+ 2. In the opened menu, select the ":span::/span:{.fa-solid .fa-check .help-page-icon} Apply saved state" option.
+
+With this, the filter panel will reset to the saved order. Some filters move, and some might get opened/closed depending on the current state of the page.
diff --git a/src/assets/scss/_buttons.scss b/src/assets/scss/_buttons.scss
index 6698b4961..7434d5463 100644
--- a/src/assets/scss/_buttons.scss
+++ b/src/assets/scss/_buttons.scss
@@ -95,6 +95,18 @@
&.chaise-btn-no-padding {
padding: 0;
}
+
+ // this is currently designed mostly for
+ &.chaise-btn-with-indicator:before {
+ content: '';
+ background-color: map-get(variables.$color-map, 'primary');
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ }
}
//TODO
@@ -113,8 +125,3 @@
.dropdown-item:active {
background-color: unset;
}
-
-// override the default behavior of dropdown toggle btn in bootstrap
-.show > button.chaise-btn.btn.dropdown-toggle {
- @include helpers.chaise-btn-primary();
-}
diff --git a/src/assets/scss/_dropdown.scss b/src/assets/scss/_dropdown.scss
index f7843ecfd..a34814c6d 100644
--- a/src/assets/scss/_dropdown.scss
+++ b/src/assets/scss/_dropdown.scss
@@ -1,5 +1,10 @@
@use 'sass:map';
@use 'variables';
+@use 'helpers';
+
+.dropdown.chaise-dropdown-no-icon .dropdown-toggle::after{
+ display: none;
+}
// the .dropdown selector is neede to ensure overriding default bootstrap and chaise styles
.chaise-dropdown.dropdown {
@@ -111,6 +116,7 @@
// style to override react-bootstrap
.dropdown-item {
padding: 0;
+
}
// change the default link behaviors to not show the underline
@@ -132,6 +138,13 @@
padding: 3px 15px 3px 15px;
line-height: 1.4;
+ &.dropdown-item-w-icon {
+ padding: 3px 15px 3px 8px;
+ .dropdown-item-icon {
+ margin-right: 7px;
+ }
+ }
+
&:focus,
&:hover {
color: lighten(map-get(variables.$color-map, 'black'), 10%);
@@ -166,7 +179,7 @@
right: unset;
}
- .disable-link {
+ .disable-link, .disabled {
pointer-events: none !important;
cursor: default !important;
color: map-get(variables.$color-map, 'disabled') !important;
@@ -177,3 +190,18 @@
}
}
}
+
+// override the default behavior of dropdown toggle btn in bootstrap
+.dropdown.show > button.chaise-btn.dropdown-toggle {
+ &.chaise-btn-primary {
+ @include helpers.chaise-btn-primary();
+ }
+
+ &.chaise-btn-secondary, &.chaise-btn-tertiary {
+ @include helpers.chaise-btn-secondary();
+
+ &:focus {
+ background-color: none;
+ }
+ }
+}
diff --git a/src/assets/scss/_faceting.scss b/src/assets/scss/_faceting.scss
index b48cae95d..3b37793cf 100644
--- a/src/assets/scss/_faceting.scss
+++ b/src/assets/scss/_faceting.scss
@@ -1,6 +1,14 @@
@use 'sass:map';
@use 'variables';
+.side-panel-container {
+ .side-panel-heading-menu {
+ position: absolute;
+ top: -37px;
+ left: 125px;
+ }
+}
+
.faceting-columns-container {
// disable the animation from the accordion
.accordion-collapse.collapsing {
diff --git a/src/assets/scss/app.scss b/src/assets/scss/app.scss
index 33030119e..30c3525a7 100644
--- a/src/assets/scss/app.scss
+++ b/src/assets/scss/app.scss
@@ -596,6 +596,10 @@ html {
padding: 0;
padding-bottom: 3px;
}
+
+ .pull-left {
+ display: flex;
+ }
}
}
@@ -1076,6 +1080,10 @@ html {
padding-top: 20px;
padding-bottom: 20px;
}
+
+ .help-page-icon {
+ margin: 0 5px;
+ }
}
/****** switch user accounts help page *******/
diff --git a/src/components/faceting/faceting.tsx b/src/components/faceting/faceting.tsx
index d8ba4241e..54edbac83 100644
--- a/src/components/faceting/faceting.tsx
+++ b/src/components/faceting/faceting.tsx
@@ -1,8 +1,15 @@
import '@isrd-isi-edu/chaise/src/assets/scss/_faceting.scss';
+//react-beatiful-dnd
+import {
+ DragDropContext, Draggable, DraggableProvided, DroppableProvided, DropResult
+} from 'react-beautiful-dnd';
+
// Components
import Accordion from 'react-bootstrap/Accordion';
+import ChaiseDroppable from '@isrd-isi-edu/chaise/src/components/chaise-droppable';
import ChaiseTooltip from '@isrd-isi-edu/chaise/src/components/tooltip';
+import Dropdown from 'react-bootstrap/Dropdown';
import FacetChoicePicker from '@isrd-isi-edu/chaise/src/components/faceting/facet-choice-picker';
import FacetCheckPresence from '@isrd-isi-edu/chaise/src/components/faceting/facet-check-presence';
import FacetHeader from '@isrd-isi-edu/chaise/src/components/faceting/facet-header';
@@ -24,13 +31,14 @@ import { ConfigService } from '@isrd-isi-edu/chaise/src/services/config';
import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
import $log from '@isrd-isi-edu/chaise/src/services/logger';
-//react-beatiful-dnd
+// utils
+import { HELP_PAGES } from '@isrd-isi-edu/chaise/src/utils/constants';
import {
- DragDropContext, Draggable, DraggableProvided, DroppableProvided, DropResult
-} from 'react-beautiful-dnd';
-import ChaiseDroppable from '@isrd-isi-edu/chaise/src/components/chaise-droppable';
-import { getFacetOrderStorageKey, getInitialFacetOpenStatus, getInitialFacetOrder } from '@isrd-isi-edu/chaise/src/utils/faceting-utils';
+ getFacetOrderStorageKey, getInitialFacetOpenStatus, getInitialFacetOrder,
+ hasStoredFacetOrder
+} from '@isrd-isi-edu/chaise/src/utils/faceting-utils';
import LocalStorage from '@isrd-isi-edu/chaise/src/utils/storage';
+import { getHelpPageURL } from '@isrd-isi-edu/chaise/src/utils/uri-utils';
type FacetingProps = {
@@ -46,7 +54,7 @@ type FacetingProps = {
registerRecordsetCallbacks: (
getAppliedFilters: () => FacetCheckBoxRow[][],
removeAppliedFilters: (index?: number | 'filters' | 'cfacets') => void,
- focusOnFacet: (index: number, dontUpdate?: boolean) => void
+ focusOnFacet: (index: number, dontUpdate?: boolean) => void,
) => void,
/**
* the recordset's log stack path
@@ -126,6 +134,12 @@ const Faceting = ({
* when this is set to true, we should save the changes in the local storage and then change it back to false.
*/
const [facetListModified, setFacetListModified] = useState(false);
+ /**
+ * whether the current order is based on teh stored facet order or not.
+ */
+ const [isStoredFacetOrderApplied, setIsStoredFacetOrderApplied] = useState(() => {
+ return hasStoredFacetOrder(reference);
+ });
const setFacetModelByIndex = (index: number, updatedVals: { [key: string]: boolean }) => {
setFacetModels((prevFacetModels: FacetModel[]) => {
@@ -260,29 +274,12 @@ const Faceting = ({
*/
useEffect(() => {
registerFacetCallbacks(updateFacetStates, updateFacets);
- registerRecordsetCallbacks(getAppliedFiltersFromRS, removeAppliedFiltersFromRS, focusOnFacet);
}, [facetModels]);
- /**
- * store the facet order in the local stroage if any changes happened to the facets
- */
useEffect(() => {
- if (!facetOrders || !facetOrders.length) return;
- if (!facetListModified) return;
- /**
- * store isOpen state for facets to localStorage
- */
- LocalStorage.setStorage(getFacetOrderStorageKey(reference), facetOrders.map((i) => {
- return {
- name: reference.facetColumns[i].sourceObjectWrapper.name,
- open: facetModelsRef.current[i].isOpen
- };
- }));
-
- // now that the state is saved, just set it to false so we don't update this until the next user action
- setFacetListModified(false);
-
- }, [facetListModified, facetModels, facetOrders])
+ console.log('calling registered in faceting');
+ registerRecordsetCallbacks(getAppliedFiltersFromRS, removeAppliedFiltersFromRS, focusOnFacet);
+ }, [facetModels, facetOrders, facetListModified, isStoredFacetOrderApplied]);
//------------------- flow-control related functions: --------------------//
@@ -629,6 +626,7 @@ const Faceting = ({
// make sure we're saving the new state
setFacetListModified(true);
+ setIsStoredFacetOrderApplied(false);
};
/**
@@ -684,8 +682,63 @@ const Faceting = ({
}
setFacetOrders(items);
- setFacetListModified(true)
+ setFacetListModified(true);
+ setIsStoredFacetOrderApplied(false);
+ };
+
+ const storeFacetOrder = () => {
+ LocalStorage.setStorage(getFacetOrderStorageKey(reference), facetOrders.map((i) => {
+ return {
+ name: reference.facetColumns[i].sourceObjectWrapper.name,
+ open: facetModelsRef.current[i].isOpen
+ };
+ }));
+ setFacetListModified(false);
+ setIsStoredFacetOrderApplied(true);
+ };
+
+ const applyDefaultOrStoredFacetOrder = (useDefault: boolean) => {
+ // change their order
+ setFacetOrders(() => {
+ return getInitialFacetOrder(reference, useDefault).map((o) => o.facetIndex);
+ });
+
+ // open or close facets
+ setFacetModels((prevFacetModels) => {
+ const { openStatus: newOpenStatus } = getInitialFacetOpenStatus(reference, useDefault);
+ return prevFacetModels.map((fm: FacetModel, fmIndex: number) => {
+ const isOpen = newOpenStatus[`${fmIndex}`];
+
+ // if open status has not changed, just return it
+ if (fm.isOpen === isOpen) return fm;
+
+ // if we are closing
+ if (!isOpen) {
+ return { ...fm, isOpen,
+ // hide the spinner:
+ isLoading: false,
+ // if we were waiting for data, make sure to fetch it later
+ initialized: !fm.isLoading
+ }
+ }
+
+ // if we're opening and it's not initialized, initiate the request
+ if (!fm.initialized) {
+ // send a request
+ dispatchFacetUpdate(fmIndex, false);
+ return { ...fm, isOpen, isLoading: true };
+ }
+
+ // otherwise just open it
+ return { ...fm, isOpen };
+ });
+ });
+
+ // set the boolean states
+ setIsStoredFacetOrderApplied(!useDefault);
+ setFacetListModified(false);
}
+
//------------------- render logic: --------------------//
const renderFacetList = () => {
@@ -755,6 +808,61 @@ const Faceting = ({
}
};
+ const renderFacetDropdownMenu = () => {
+ const storedIsAvailable = hasStoredFacetOrder(reference);
+ const showChangeIndicator = facetListModified || (storedIsAvailable && !isStoredFacetOrderApplied);
+ const allowSave = showChangeIndicator;
+ const allowApplyDefault = facetListModified || isStoredFacetOrderApplied;
+ const allowApplySaved = storedIsAvailable && showChangeIndicator;
+
+ return (
+
+
+
+
+
+
+
+ storeFacetOrder()}>
+
+
+ Save filter order
+
+
+ applyDefaultOrStoredFacetOrder(true)}
+ >
+
+
+ Reset to default
+
+
+ applyDefaultOrStoredFacetOrder(false)}
+ >
+
+
+ Apply saved state
+
+
+
+
+
+ Help
+
+
+
+
+ )
+ }
+
// bootstrap expects an array of strings
const activeKeys: string[] = [];
facetModels.forEach((fm, index) => { if (fm.isOpen) activeKeys.push(`${index}`) });
@@ -768,6 +876,7 @@ const Faceting = ({
return (
+ {renderFacetDropdownMenu()}
diff --git a/src/models/recordset.ts b/src/models/recordset.ts
index 963a14757..7d7c4e4d3 100644
--- a/src/models/recordset.ts
+++ b/src/models/recordset.ts
@@ -220,3 +220,13 @@ export type RecordsetProviderUpdateMainEntity = (
export type RecordsetProviderFetchSecondaryRequests = (
updatePageCB: Function, hideSpinner?: boolean
) => void;
+
+
+export type FacetOrderProps = {
+ isFacetOrderModified: boolean,
+ hasStoredFacetOrder: boolean,
+ isStoredFacetOrderApplied: boolean,
+ storeFacetOrder: () => void,
+ applyDefaultOrder: () => void,
+ applyStoredOrder: () => void
+}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 6b605b851..9d2cb4539 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -198,7 +198,22 @@ export const CUSTOM_EVENTS = {
export const HELP_PAGES_FOLDER_LOCATION = 'help-docs';
-export const HELP_PAGES = {
+export const HELP_PAGES : {
+ [name: string]: {
+ /**
+ * the title of page (what users see)
+ */
+ title: string,
+ /**
+ * what should be used as the query parameter to find the page.
+ */
+ location: string,
+ /**
+ * whether this is a built-in component or a markdown help page.
+ */
+ isComponent: boolean
+ }
+} = {
MARKDOWN_HELP: {
title: 'Markdown Help',
location: 'chaise/markdown-help',
@@ -213,6 +228,11 @@ export const HELP_PAGES = {
title: 'Viewer Annotation',
location: 'chaise/viewer-annotation',
isComponent: false
+ },
+ FACET_PANEL: {
+ title: 'Filter panel',
+ location: 'chaise/facet-panel',
+ isComponent: false
}
}
diff --git a/src/utils/faceting-utils.ts b/src/utils/faceting-utils.ts
index 168605ee0..bba190e17 100644
--- a/src/utils/faceting-utils.ts
+++ b/src/utils/faceting-utils.ts
@@ -60,6 +60,12 @@ export const getFacetOrderStorageKey = (reference: any): string => {
return `facet-order-${reference.table.schema.catalog.id}_${reference.table.schema.name}_${reference.table.name}`;
}
+export const hasStoredFacetOrder = (reference: any): boolean => {
+ const facetListKey = getFacetOrderStorageKey(reference);
+ const facetOrder = LocalStorage.getStorage(facetListKey);
+ return facetOrder && Array.isArray(facetOrder) && facetOrder.length > 0;
+}
+
/**
* Return the order of facets that should be used initially.
*
@@ -67,16 +73,19 @@ export const getFacetOrderStorageKey = (reference: any): string => {
*
* @param reference the reference that represents the main recordset page
*/
-export const getInitialFacetOrder = (reference: any): { facetIndex: number, isOpen: boolean }[] => {
+export const getInitialFacetOrder = (reference: any, ignoreStorage?: boolean): { facetIndex: number, isOpen: boolean }[] => {
const res: { facetIndex: number, isOpen: boolean }[] = [];
const facetColumns = reference.facetColumns;
const facetListKey = getFacetOrderStorageKey(reference);
- const facetOrder = LocalStorage.getStorage(facetListKey) as {
- name: string,
- open: boolean
- }[] || undefined;
+ let facetOrder: { name: string, open: boolean }[] | undefined;
let atLeastOneIsOpen = false;
+ if (ignoreStorage) {
+ facetOrder = [];
+ } else {
+ facetOrder = LocalStorage.getStorage(facetListKey);
+ }
+
// no valid stored value was found in storage, so return the annotaion value.
if (!facetOrder || !Array.isArray(facetOrder) || facetOrder.length === 0) {
facetColumns.forEach((fc: any, index: number) => {
@@ -157,11 +166,11 @@ export const getInitialFacetOrder = (reference: any): { facetIndex: number, isOp
*
* @param reference the reference that represents the main recordset page
*/
-export const getInitialFacetOpenStatus = (reference: any): {
+export const getInitialFacetOpenStatus = (reference: any, ignoreStorage?: boolean): {
order: { facetIndex: number, isOpen: boolean }[],
openStatus: { [facetIndex: string]: boolean }
} => {
- const storedOrder = getInitialFacetOrder(reference);
+ const storedOrder = getInitialFacetOrder(reference, ignoreStorage);
const booleanRes: { [facetIndex: string]: boolean } = {};
storedOrder.forEach((r) => { booleanRes[r.facetIndex] = r.isOpen; });
diff --git a/test/e2e/locators/recordset.ts b/test/e2e/locators/recordset.ts
index abdd0c5c1..4a5bb401e 100644
--- a/test/e2e/locators/recordset.ts
+++ b/test/e2e/locators/recordset.ts
@@ -290,7 +290,27 @@ export default class RecordsetLocators {
static getShowFilterPanelBtn(container: Page | Locator): Locator {
return container.locator('.show-filter-panel-btn');
-}
+ }
+
+ static getSidePanelHeadingMenu(container: Page | Locator): Locator {
+ return container.locator('.side-panel-heading-menu .dropdown-toggle');
+ }
+
+ static getSaveFacetOrderBtn(container: Page | Locator): Locator {
+ return container.locator('.save-facet-order-btn');
+ }
+
+ static getShowDefaultFacetOrderBtn(container: Page | Locator): Locator {
+ return container.locator('.show-default-facet-order-btn');
+ }
+
+ static getApplySavedFacetOrderBtn(container: Page | Locator): Locator {
+ return container.locator('.apply-saved-facet-order-btn');
+ }
+
+ static getSidePanelHeadingMenuHelpBtn(container: Page | Locator): Locator {
+ return container.locator('.side-panel-heading-menu-help-btn');
+ }
static getAllFacets(container: Page | Locator): Locator {
return container.locator('.panel-group .facet-panel');
diff --git a/test/e2e/specs/delete-prohibited/recordset/reorder-facet.spec.ts b/test/e2e/specs/delete-prohibited/recordset/reorder-facet.spec.ts
index dfe5e528d..ba9bb3655 100644
--- a/test/e2e/specs/delete-prohibited/recordset/reorder-facet.spec.ts
+++ b/test/e2e/specs/delete-prohibited/recordset/reorder-facet.spec.ts
@@ -1,4 +1,4 @@
-import { test, expect, TestInfo, Page } from '@playwright/test';
+import { test, expect, TestInfo, Page, Locator } from '@playwright/test';
// locators
import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset';
@@ -9,7 +9,7 @@ import {
openRecordsetAndResetFacetState, TestIndividualFacetParams, testIndividualFacet, resetFacetState,
testDisplayedFacets
} from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils';
-import { dragAndDropWithScroll } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils';
+import { clickNewTabLink, dragAndDropWithScroll } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils';
const testParams = {
@@ -36,7 +36,8 @@ const testParams = {
'f3 (term)', 'from_name', 'to_name', 'F1 with Term', 'Check Presence Text',
'F3 Entity', 'F5', 'F5 with filter', 'Outbound1 (using F1)',
'col_w_column_order_false', 'col_w_column_order', 'col_w_long_values',
- ]
+ ],
+ facetsToOpenAfterReorder: [4, 10]
},
savedStateWInvalids: {
storage: [
@@ -153,6 +154,10 @@ test.describe('Facet reorder feature', () => {
test('changing the order of facets', async ({ page, baseURL }, testInfo) => {
const currParams = testParams.initialState;
+ const menuBtn = RecordsetLocators.getSidePanelHeadingMenu(page);
+ const saveBtn = RecordsetLocators.getSaveFacetOrderBtn(page);
+ const applyDefaultBtn = RecordsetLocators.getShowDefaultFacetOrderBtn(page);
+ const applySavedBtn = RecordsetLocators.getApplySavedFacetOrderBtn(page);
// this will close all the facets
await openRecordsetAndResetFacetState(
@@ -172,14 +177,72 @@ test.describe('Facet reorder feature', () => {
});
await test.step('interacting with the reordered facets', async () => {
+ // test facet selection
await testFacetSelection(page, []);
+
+ // open some of the facets
+ for await (const facetId of testParams.initialState.facetsToOpenAfterReorder) {
+ const facet = RecordsetLocators.getFacetById(page, facetId);
+ await RecordsetLocators.getFacetHeaderButtonById(facet, facetId).click();
+ }
+ });
+
+ await test.step('the Save button should be available and clicking on it should save the order.', async () => {
+ await testMenuBtnIndicator(menuBtn, true);
+ await menuBtn.click();
+
+ await testMenuBtnDisabled(saveBtn, false);
+ await testMenuBtnDisabled(applyDefaultBtn, false);
+ await testMenuBtnDisabled(applySavedBtn, true);
+ await saveBtn.click();
+
+ await testMenuBtnIndicator(menuBtn, false);
+ await testDisplayedFacets(page, testParams.initialState.facetNamesAfterReorder, ['timestamp_col', 'F1']);
+ });
+
+ await test.step('clicking on "Reset to default" should display the default order.', async () => {
+ await menuBtn.click();
+ await testMenuBtnDisabled(saveBtn, true);
+ await testMenuBtnDisabled(applyDefaultBtn, false);
+ await testMenuBtnDisabled(applySavedBtn, true);
+ await applyDefaultBtn.click();
+
+ await testMenuBtnIndicator(menuBtn, true);
+ await testDisplayedFacets(page, testParams.initialState.facetNames, ['to_name']);
+ });
+
+ await test.step('clicking on "Apply saved state" should display the saved state.', async () => {
+ await menuBtn.click();
+ await testMenuBtnDisabled(saveBtn, false);
+ await testMenuBtnDisabled(applyDefaultBtn, true);
+ await testMenuBtnDisabled(applySavedBtn, false)
+ await applySavedBtn.click();
+
+ await testMenuBtnIndicator(menuBtn, false);
+ await testDisplayedFacets(page, testParams.initialState.facetNamesAfterReorder, ['timestamp_col', 'F1']);
});
await test.step('refreshing the page should display the saved order and open state.', async () => {
await page.reload();
- // int_col and id will always be open
- await testDisplayedFacets(page, testParams.initialState.facetNamesAfterReorder, ['int_col', 'id']);
+ // id and int have filters so they will be opened anyways
+ await RecordsetLocators.waitForRecordsetPageReady(page);
+ await testDisplayedFacets(page, testParams.initialState.facetNamesAfterReorder, ['int_col', 'id', 'timestamp_col', 'F1']);
});
+
+ await test.step('changing the order of facets without clicking on save should not save the order.', async () => {
+ await moveFacet(page, 0, 2);
+ await page.reload();
+ await RecordsetLocators.waitForRecordsetPageReady(page);
+ await testDisplayedFacets(page, testParams.initialState.facetNamesAfterReorder, ['int_col', 'id', 'timestamp_col', 'F1']);
+ });
+
+ await test.step('"Help" option should navigate to the help page.', async () => {
+ await menuBtn.click();
+ const newPage = await clickNewTabLink(RecordsetLocators.getSidePanelHeadingMenuHelpBtn(page));
+ await newPage.waitForURL('**/help/?page=chaise%2Ffacet-panel');
+ await newPage.close();
+ });
+
});
test('opening a page where the saved state has extra or invalid facets', async ({ page, baseURL }, testInfo) => {
@@ -200,7 +263,7 @@ test.describe('Facet reorder feature', () => {
// open a facet since all of them are closed due to the previous test
await RecordsetLocators.getFacetHeaderButtonById(RecordsetLocators.getFacetById(page, 12), 12).click();
await page.reload();
- await testDisplayedFacets(page, currParams.facetNames, ['id', 'int_col', 'f3 (term)']);
+ await testDisplayedFacets(page, currParams.facetNames, currParams.openFacets.names);
});
});
@@ -220,7 +283,7 @@ test.describe('Facet reorder feature', () => {
await test.step('refreshing the page should display the saved order and open state.', async () => {
await page.reload();
- await testDisplayedFacets(page, currParams.facetNames);
+ await testDisplayedFacets(page, currParams.facetNames, currParams.openFacets.names);
});
});
@@ -276,3 +339,21 @@ const changeStoredOrder = async (page: Page, testInfo: TestInfo, order: any) =>
await page.reload();
}
+
+const testMenuBtnDisabled = async (locator: Locator, disabled: boolean) => {
+ await expect.soft(locator).toBeVisible();
+ if (disabled) {
+ await expect.soft(locator).toHaveClass(/disabled/);
+ } else {
+ await expect.soft(locator).not.toHaveClass(/disabled/);
+ }
+}
+
+const testMenuBtnIndicator = async (locator: Locator, hasIndicator: boolean) => {
+ await expect.soft(locator).toBeVisible();
+ if (hasIndicator) {
+ await expect.soft(locator).toHaveClass(/chaise-btn-with-indicator/);
+ } else {
+ await expect.soft(locator).not.toHaveClass(/chaise-btn-with-indicator/);
+ }
+}
From c10cde7534863c7ba3f393bd561aa287c96dce8f Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Thu, 10 Oct 2024 13:51:44 -0700
Subject: [PATCH 06/30] facet-within-facet: add a test case for scalar choice
picker
---
.../schema/recordset/facet-within-facet.json | 25 ++++++++++
.../recordset/facet-within-facet.spec.ts | 48 ++++++++++++++++++-
2 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/test/e2e/data_setup/schema/recordset/facet-within-facet.json b/test/e2e/data_setup/schema/recordset/facet-within-facet.json
index f2885b2a2..37d9e2e89 100644
--- a/test/e2e/data_setup/schema/recordset/facet-within-facet.json
+++ b/test/e2e/data_setup/schema/recordset/facet-within-facet.json
@@ -427,6 +427,11 @@
"markdown_name": "2nd layer timestamptz",
"source": "timestamptz_col",
"open": true
+ },
+ {
+ "markdown_name": "2nd layer choice scalar",
+ "source": "facet_col",
+ "open": true
}
]
}
@@ -573,6 +578,11 @@
"markdown_name": "2nd layer timestamptz",
"source": "timestamptz_col",
"open": true
+ },
+ {
+ "markdown_name": "2nd layer choice scalar",
+ "source": "facet_col",
+ "open": true
}
]
}
@@ -775,6 +785,11 @@
"markdown_name": "2nd layer timestamptz",
"source": "timestamptz_col",
"open": true
+ },
+ {
+ "markdown_name": "2nd layer choice scalar",
+ "source": "facet_col",
+ "open": true
}
]
}
@@ -900,6 +915,11 @@
"markdown_name": "2nd layer timestamptz",
"source": "timestamptz_col",
"open": true
+ },
+ {
+ "markdown_name": "2nd layer choice scalar",
+ "source": "facet_col",
+ "open": true
}
]
}
@@ -1083,6 +1103,11 @@
"markdown_name": "2nd layer timestamptz",
"source": "timestamptz_col",
"open": true
+ },
+ {
+ "markdown_name": "2nd layer choice scalar",
+ "source": "facet_col",
+ "open": true
}
]
}
diff --git a/test/e2e/specs/default-config/recordset/facet-within-facet.spec.ts b/test/e2e/specs/default-config/recordset/facet-within-facet.spec.ts
index 500512bbb..c431e5b77 100644
--- a/test/e2e/specs/default-config/recordset/facet-within-facet.spec.ts
+++ b/test/e2e/specs/default-config/recordset/facet-within-facet.spec.ts
@@ -118,7 +118,8 @@ type FacetParamType = {
const popupFacetNames = [
'2nd layer entity path', '2nd layer entity path 2', '2nd layer entity path with filter', '2nd layer entity path with shared prefix',
- '2nd layer entity path with shared prefix and filter', '2nd layer entity path with fast filter', '2nd layer timestamptz'
+ '2nd layer entity path with shared prefix and filter', '2nd layer entity path with fast filter',
+ '2nd layer timestamptz', '2nd layer choice scalar'
]
const getTestParams = (rootUIContext: string): {
@@ -220,6 +221,20 @@ const getTestParams = (rootUIContext: string): {
numRows: 6
}
},
+ {
+ index: 7,
+ name: popupFacetNames[7],
+ options: [
+ 'eight main_inbound1_inbound1', 'eleven main_inbound1_inbound1', 'five main_inbound1_inbound1',
+ 'four main_inbound1_inbound1', 'nine main_inbound1_inbound1', 'seven main_inbound1_inbound1',
+ 'six main_inbound1_inbound1', 'ten main_inbound1_inbound1', 'three main_inbound1_inbound1', 'twelve main_inbound1_inbound1'
+ ],
+ popup: {
+ uiContext: rootUIContext + 'Entity path:',
+ title: 'Select 2nd layer choice scalar',
+ numRows: 10
+ }
+ }
]
}
},
@@ -322,6 +337,20 @@ const getTestParams = (rootUIContext: string): {
numRows: 3
}
},
+ {
+ index: 7,
+ name: popupFacetNames[7],
+ options: [
+ 'eight main_inbound1_inbound2', 'eleven main_inbound1_inbound2', 'five main_inbound1_inbound2',
+ 'nine main_inbound1_inbound2', 'seven main_inbound1_inbound2', 'six main_inbound1_inbound2',
+ 'ten main_inbound1_inbound2', 'twelve main_inbound1_inbound2'
+ ],
+ popup: {
+ uiContext: rootUIContext + 'Entity path 2:',
+ title: 'Select 2nd layer choice scalar',
+ numRows: 8
+ }
+ }
]
}
},
@@ -390,6 +419,21 @@ const getTestParams = (rootUIContext: string): {
min: { date: '2006-06-06', time: '00:00:00' },
numRows: 3
}]
+ },
+ {
+ index: 7,
+ name: popupFacetNames[7],
+ options: ['eleven main_inbound1_inbound1', 'ten main_inbound1_inbound1', 'twelve main_inbound1_inbound1'],
+ select: [{
+ option: 2,
+ numRows: 1,
+ numFacetFilters: 3
+ }],
+ popup: {
+ uiContext: rootUIContext + 'Entity path with filter:',
+ title: 'Select 2nd layer choice scalar',
+ numRows: 3
+ }
}
]
}
@@ -741,6 +785,8 @@ test.describe('facet within facet', () => {
)
});
+ return;
+
test.describe('record link popup', () => {
testFacetWithinFacet(
'main association',
From cb5ea8b679d3b3c38865416f4cac28b03f1a30c8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 14 Oct 2024 14:26:36 -0700
Subject: [PATCH 07/30] Bump @playwright/test from 1.47.2 to 1.48.0 (#2559)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.47.2 to 1.48.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.47.2...v1.48.0)
---
updated-dependencies:
- dependency-name: "@playwright/test"
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index e88b5235d..e1dc37f21 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
},
"devDependencies": {
"@isrd-isi-edu/ermrest-data-utils": "^0.0.7",
- "@playwright/test": "*",
+ "@playwright/test": "latest",
"@typescript-eslint/eslint-plugin": "~7.18.0",
"@typescript-eslint/parser": "~7.18.0",
"chance": "x",
@@ -2227,12 +2227,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.47.2",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz",
- "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==",
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz",
+ "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==",
"dev": true,
"dependencies": {
- "playwright": "1.47.2"
+ "playwright": "1.48.0"
},
"bin": {
"playwright": "cli.js"
@@ -8143,12 +8143,12 @@
}
},
"node_modules/playwright": {
- "version": "1.47.2",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
- "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==",
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz",
+ "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==",
"dev": true,
"dependencies": {
- "playwright-core": "1.47.2"
+ "playwright-core": "1.48.0"
},
"bin": {
"playwright": "cli.js"
@@ -8161,9 +8161,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.47.2",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz",
- "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==",
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz",
+ "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
From e96d6115e353116f0974d35445bec81ef81f7540 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 21 Oct 2024 14:32:58 -0700
Subject: [PATCH 08/30] Bump @playwright/test from 1.48.0 to 1.48.1 (#2560)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.48.0 to 1.48.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.48.0...v1.48.1)
---
updated-dependencies:
- dependency-name: "@playwright/test"
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index e1dc37f21..bd4d0fae1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2227,12 +2227,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.48.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz",
- "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==",
+ "version": "1.48.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.1.tgz",
+ "integrity": "sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==",
"dev": true,
"dependencies": {
- "playwright": "1.48.0"
+ "playwright": "1.48.1"
},
"bin": {
"playwright": "cli.js"
@@ -8143,12 +8143,12 @@
}
},
"node_modules/playwright": {
- "version": "1.48.0",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz",
- "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==",
+ "version": "1.48.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.1.tgz",
+ "integrity": "sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==",
"dev": true,
"dependencies": {
- "playwright-core": "1.48.0"
+ "playwright-core": "1.48.1"
},
"bin": {
"playwright": "cli.js"
@@ -8161,9 +8161,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.48.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz",
- "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==",
+ "version": "1.48.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.1.tgz",
+ "integrity": "sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
From 75c0827e0168ce0be6f8d78d78ebccd8ccdac1a6 Mon Sep 17 00:00:00 2001
From: Josh Chudy
Date: Mon, 28 Oct 2024 10:55:41 -0700
Subject: [PATCH 09/30] End-to-end test for "bulk_create_foreign_key"
annotation property (#2561)
* tests cases for bulk_create_foreign_key annotation property
---
.../association_table_w_three_fks.json | 1 +
.../product/leaf_table_for_three_fks.json | 10 +
.../product/third_table_for_three_fks.json | 10 +
...roduct-unordered-related-tables-links.json | 235 ++++++++++++++++++
.../all-features/record/related-table.spec.ts | 26 +-
5 files changed, 277 insertions(+), 5 deletions(-)
create mode 100644 test/e2e/data_setup/data/product/association_table_w_three_fks.json
create mode 100644 test/e2e/data_setup/data/product/leaf_table_for_three_fks.json
create mode 100644 test/e2e/data_setup/data/product/third_table_for_three_fks.json
diff --git a/test/e2e/data_setup/data/product/association_table_w_three_fks.json b/test/e2e/data_setup/data/product/association_table_w_three_fks.json
new file mode 100644
index 000000000..3d397d670
--- /dev/null
+++ b/test/e2e/data_setup/data/product/association_table_w_three_fks.json
@@ -0,0 +1 @@
+[{"main_fk_col": 2004, "leaf_fk_col": 2, "third_fk_col": 2, "static_col1": 2}]
diff --git a/test/e2e/data_setup/data/product/leaf_table_for_three_fks.json b/test/e2e/data_setup/data/product/leaf_table_for_three_fks.json
new file mode 100644
index 000000000..0531f886f
--- /dev/null
+++ b/test/e2e/data_setup/data/product/leaf_table_for_three_fks.json
@@ -0,0 +1,10 @@
+[{"id": "1", "details": "Leaf 1"},
+{"id": "2", "details": "Leaf 2"},
+{"id": "3", "details": "Leaf 3"},
+{"id": "4", "details": "Leaf 4"},
+{"id": "5", "details": "Leaf 5"},
+{"id": "6", "details": "Leaf 6"},
+{"id": "7", "details": "Leaf 7"},
+{"id": "8", "details": "Leaf 8"},
+{"id": "9", "details": "Leaf 9"},
+{"id": "10", "details": "Leaf 10"}]
diff --git a/test/e2e/data_setup/data/product/third_table_for_three_fks.json b/test/e2e/data_setup/data/product/third_table_for_three_fks.json
new file mode 100644
index 000000000..756aa06f2
--- /dev/null
+++ b/test/e2e/data_setup/data/product/third_table_for_three_fks.json
@@ -0,0 +1,10 @@
+[{"id": "1", "details": "Other Leaf 1"},
+{"id": "2", "details": "Other Leaf 2"},
+{"id": "3", "details": "Other Leaf 3"},
+{"id": "4", "details": "Other Leaf 4"},
+{"id": "5", "details": "Other Leaf 5"},
+{"id": "6", "details": "Other Leaf 6"},
+{"id": "7", "details": "Other Leaf 7"},
+{"id": "8", "details": "Other Leaf 8"},
+{"id": "9", "details": "Other Leaf 9"},
+{"id": "10", "details": "Other Leaf 10"}]
diff --git a/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json b/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json
index 9606c5192..0634a37ab 100644
--- a/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json
+++ b/test/e2e/data_setup/schema/record/product-unordered-related-tables-links.json
@@ -781,6 +781,10 @@
[
"product-unordered-related-tables-links",
"static_to_accommodation_w_dropdown_fkey"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "three_fks_to_accommodation_fkey"
]
]
},
@@ -3234,6 +3238,237 @@
]
}
}
+ },
+ "association_table_w_three_fks": {
+ "comment": "For testing 'add records' prefill and using bulk create foreign key features with an annnotation defined since there are 3 foreign key relationships",
+ "kind": "table",
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_three_fks",
+ "column_definitions": [
+ {
+ "name": "static_col1",
+ "type": {
+ "typename": "int4"
+ }
+ },
+ {
+ "name": "main_fk_col",
+ "nullok": false,
+ "type": {
+ "typename": "int4"
+ }
+ },
+ {
+ "name": "leaf_fk_col",
+ "nullok": false,
+ "type": {
+ "typename": "text"
+ }
+ },
+ {
+ "name": "third_fk_col",
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "keys": [
+ {
+ "unique_columns": [
+ "RID"
+ ]
+ },
+ {
+ "unique_columns": [
+ "main_fk_col",
+ "leaf_fk_col"
+ ]
+ }
+ ],
+ "foreign_keys": [
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_three_fks",
+ "column_name": "leaf_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "leaf_table_for_three_fks",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "three_fks_to_leaf_fkey"
+ ]
+ ]
+ },
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_three_fks",
+ "column_name": "main_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "accommodation",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "three_fks_to_accommodation_fkey"
+ ]
+ ]
+ },
+ {
+ "foreign_key_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "association_table_w_three_fks",
+ "column_name": "third_fk_col"
+ }
+ ],
+ "referenced_columns": [
+ {
+ "schema_name": "product-unordered-related-tables-links",
+ "table_name": "third_table_for_three_fks",
+ "column_name": "id"
+ }
+ ],
+ "names": [
+ [
+ "product-unordered-related-tables-links",
+ "three_fks_to_third_fkey"
+ ]
+ ]
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "entry": [
+ "static_col1",
+ {
+ "source": [
+ {
+ "outbound": [
+ "product-unordered-related-tables-links",
+ "three_fks_to_accommodation_fkey"
+ ]
+ },
+ "RID"
+ ],
+ "display": {
+ "bulk_create_foreign_key": [
+ "product-unordered-related-tables-links",
+ "three_fks_to_leaf_fkey"
+ ]
+ }
+ },
+ [
+ "product-unordered-related-tables-links",
+ "three_fks_to_leaf_fkey"
+ ],
+ [
+ "product-unordered-related-tables-links",
+ "three_fks_to_third_fkey"
+ ]
+ ],
+ "compact": "entry",
+ "detailed": "entry"
+ }
+ }
+ },
+ "leaf_table_for_three_fks": {
+ "comment": "leaf table for testing an association with 3 foreign keys and a bulk_create_foreign_key property in visible columns annotation",
+ "kind": "table",
+ "table_name": "leaf_table_for_three_fks",
+ "schema_name": "product-unordered-related-tables-links",
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
+ "foreign_keys": [],
+ "column_definitions": [
+ {
+ "name": "id",
+ "type": {
+ "typename": "text"
+ }
+ },
+ {
+ "name": "details",
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:table-display": {
+ "row_name": {
+ "row_markdown_pattern": "{{{details}}}"
+ }
+ },
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "compact": [
+ "id",
+ "details"
+ ]
+ }
+ }
+ },
+ "third_table_for_three_fks": {
+ "comment": "third table for testing an association with 3 foreign keys and a bulk_create_foreign_key property in visible columns annotation",
+ "kind": "table",
+ "table_name": "third_table_for_three_fks",
+ "schema_name": "product-unordered-related-tables-links",
+ "keys": [
+ {
+ "unique_columns": [
+ "id"
+ ]
+ }
+ ],
+ "foreign_keys": [],
+ "column_definitions": [
+ {
+ "name": "id",
+ "type": {
+ "typename": "text"
+ }
+ },
+ {
+ "name": "details",
+ "type": {
+ "typename": "text"
+ }
+ }
+ ],
+ "annotations": {
+ "tag:isrd.isi.edu,2016:table-display": {
+ "row_name": {
+ "row_markdown_pattern": "{{{details}}}"
+ }
+ },
+ "tag:isrd.isi.edu,2016:visible-columns": {
+ "compact": [
+ "id",
+ "details"
+ ]
+ }
+ }
}
},
"comment": null,
diff --git a/test/e2e/specs/all-features/record/related-table.spec.ts b/test/e2e/specs/all-features/record/related-table.spec.ts
index ba19a984e..b51aaba07 100644
--- a/test/e2e/specs/all-features/record/related-table.spec.ts
+++ b/test/e2e/specs/all-features/record/related-table.spec.ts
@@ -44,8 +44,9 @@ const testParams = {
'association with filter on main table',
'association with filter on related table', // association with filter on related table
'path of length 3 with filters', // path of length 3 with filters
- 'association_table_w_static_column', // "almost" pure and binary multi create foreig key with fk input modals
- 'association_table_w_static_column_dropdown' // "almost" pure and binary multi create foreig key with fk input dropdowns
+ 'association_table_w_static_column', // "almost" pure and binary multi create foreign key with fk input modals
+ 'association_table_w_static_column_dropdown', // "almost" pure and binary multi create foreign key with fk input dropdowns
+ 'association_table_w_three_fks' // association table with 3 foreign keys and multi create foreign key with annotation
],
tocHeaders: [
'Summary', 'booking (6)', 'schedule (2)', 'media (1)', 'association_table (1)',
@@ -59,7 +60,8 @@ const testParams = {
'association with filter on related table (1)',
'path of length 3 with filters (1)',
'association_table_w_static_column (1)',
- 'association_table_w_static_column_dropdown (1)'
+ 'association_table_w_static_column_dropdown (1)',
+ 'association_table_w_three_fks (1)'
],
scrollToDisplayname: 'table_w_aggregates'
};
@@ -837,9 +839,12 @@ test.describe('Related tables', () => {
});
/**
- * The following 2 tests are for testing the prefill functionality when the inbound foreign key is part of a table that is "almost" pure and binary
+ * The following tests are for testing the prefill functionality when the inbound foreign key is part of a table that is "almost" pure and binary
*
- * This means there are 2 foreign keys that are part of the same key (making the pair unique) and there are other columns that are not foreign keys
+ * For the first 3 tests, this means there are 2 foreign keys that are part of the same key (making the pair unique) and there are other columns that are not foreign keys
+ *
+ * For the 4th test, there are still 2 foreign keys that are part of the same key but there is another foreign key on this table. This means the heuristics won't trigger
+ * for the bulk create foreign key functionality requiring an annotation to be defined instead.
*/
test.describe('for a table that is almost pure and binary and the foreign keys are a unique key', async () => {
const params = {
@@ -960,6 +965,17 @@ test.describe('Related tables', () => {
await testAddRelatedWithForeignKeyMultiPicker(page, params, RecordeditInputType.FK_DROPDOWN);
});
+
+ test('with a third foreign key and an annotation on main_fk_col', async ({ page }) => {
+ params.table_name = 'association_table_w_three_fks';
+ params.leaf_fk_name = 'leaf_fk_col';
+ params.bulk_modal_title = 'Select a set of leaf_fk_col for association_table_w_three_fks'
+
+ params.column_names = ['static1', 'main_fk_col', 'leaf_fk_col', 'third_fk_col'];
+ params.resultset_values = [['', '2004', '10', ''], ['', '2004', '7', '']];
+ params.related_table_values = [['2', 'Leaf 2', 'Other Leaf 2'], ['', 'Leaf 10', ''], ['', 'Leaf 7', '']];
+ await testAddRelatedWithForeignKeyMultiPicker(page, params, RecordeditInputType.FK_POPUP);
+ });
});
});
From 5a696d1b52dc13802cd4d2b12e7889b76743dc3c Mon Sep 17 00:00:00 2001
From: Josh Chudy
Date: Mon, 28 Oct 2024 12:41:28 -0700
Subject: [PATCH 10/30] use min height to adjust the height for the key column
and entity value similar when the height can't go below a certain threshold
(#2562)
---
src/assets/scss/_recordedit.scss | 8 +++++-
src/components/recordedit/form-row.tsx | 27 ++++++++++++++++---
.../recordedit/multi-form-input-row.tsx | 4 +--
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/src/assets/scss/_recordedit.scss b/src/assets/scss/_recordedit.scss
index 0e4881786..534e189ea 100644
--- a/src/assets/scss/_recordedit.scss
+++ b/src/assets/scss/_recordedit.scss
@@ -59,7 +59,6 @@
padding: 8px;
min-height: 47px;
- height: 47px;
flex: none;
position: relative;
@@ -121,6 +120,11 @@
&.highlighted-row {
background-color: map-get(variables.$color-map, 'recordedit-highlighted-row');
+
+ // reset the changed height when the multi form input row is open
+ .inputs-row {
+ min-height: unset;
+ }
}
&.highlighted-row, &.with-inline-tooltip {
@@ -129,6 +133,7 @@
}
.inputs-row {
+ min-height: inherit; // the min-height of the parent row element can be changed if the key column has a taller height than the form row. We want to inherit that changed element's height value
display: flex;
flex-direction: row;
@@ -171,6 +176,7 @@
}
.multi-form-input-row {
+ flex-grow: 1; // fill the space in case of a really tall row because the column name is long in the KeyColumn element (entity-key)
flex-direction: column;
align-items: center;
diff --git a/src/components/recordedit/form-row.tsx b/src/components/recordedit/form-row.tsx
index 3e9b685a1..10415ab4a 100644
--- a/src/components/recordedit/form-row.tsx
+++ b/src/components/recordedit/form-row.tsx
@@ -119,7 +119,21 @@ const FormRow = ({
cachedHeight = newHeight;
const header = document.querySelector(`.entity-key.entity-key-${columnModelIndex}`);
if (header) {
- header.style.height = `${cachedHeight}px`;
+ // unset header height since the header content may have changed
+ header.style.height = 'unset';
+ const headerHeight = header.getBoundingClientRect().height;
+
+ // if header is taller than the FormRow container, change the cached height and update the form row container's height instead
+ if (headerHeight > cachedHeight) {
+ cachedHeight = headerHeight;
+ if (isActiveForm) {
+ container.current.style.height = `${cachedHeight}px`;
+ } else {
+ container.current.style.minHeight = `${cachedHeight}px`;
+ }
+ } else {
+ header.style.height = `${cachedHeight}px`;
+ }
}
}
@@ -139,7 +153,14 @@ const FormRow = ({
return () => {
sensor.detach();
};
- }, []);
+ }, [forms.length]);
+
+ // if the current form row has it's multi form input state changed, unset the min-height of the `container` set in the above useLayoutEffect
+ useEffect(() => {
+ if (!isActiveForm || !container.current) return;
+
+ container.current.style.height = 'unset';
+ }, [isActiveForm]);
useEffect(() => {
// This condition is to remove the form from the acitve forms if we delete the form.
@@ -250,7 +271,7 @@ const FormRow = ({
const hasInlineComment = columnModel.column.comment && columnModel.column.comment.displayMode === CommentDisplayModes.INLINE;
/**
- * Returntrue if,
+ * Return true if,
* - columnModel is marked as disabled
* - based on dynamic ACLs the column cannot be updated (based on canUpdateValues)
* - show all
diff --git a/src/components/recordedit/multi-form-input-row.tsx b/src/components/recordedit/multi-form-input-row.tsx
index f8ecef8ca..c779dbffa 100644
--- a/src/components/recordedit/multi-form-input-row.tsx
+++ b/src/components/recordedit/multi-form-input-row.tsx
@@ -58,7 +58,7 @@ const MultiFormInputRow = ({
const colName = cm.column.name;
const colRID = cm.column.RID;
-
+
const inputName = `c_${MULTI_FORM_INPUT_FORM_VALUE}-${colRID}`;
const { formState: { errors }, getValues, setValue, setError, clearErrors } = useFormContext();
@@ -196,7 +196,7 @@ const MultiFormInputRow = ({
if (textareaArrayField) {
const addButton = document.querySelector('.multi-form-input .array-input-field-container-longtext .add-element-container .add-button') as HTMLElement
const addButtonWidth = addButton.getBoundingClientRect().width + addButton.offsetWidth;
-
+
let newContainerWidth;
if (windowRef.innerWidth < 1800) {
newContainerWidth = nonScrollableDiv.offsetWidth - addButtonWidth;
From 28abf7a14e29240ac4d96af75fed75f8bf0159f9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 28 Oct 2024 14:30:30 -0700
Subject: [PATCH 11/30] Bump @playwright/test from 1.48.1 to 1.48.2 (#2563)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.48.1 to 1.48.2.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.48.1...v1.48.2)
---
updated-dependencies:
- dependency-name: "@playwright/test"
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index bd4d0fae1..d715d1bec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2227,12 +2227,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.48.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.1.tgz",
- "integrity": "sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==",
+ "version": "1.48.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz",
+ "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==",
"dev": true,
"dependencies": {
- "playwright": "1.48.1"
+ "playwright": "1.48.2"
},
"bin": {
"playwright": "cli.js"
@@ -8143,12 +8143,12 @@
}
},
"node_modules/playwright": {
- "version": "1.48.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.1.tgz",
- "integrity": "sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==",
+ "version": "1.48.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz",
+ "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==",
"dev": true,
"dependencies": {
- "playwright-core": "1.48.1"
+ "playwright-core": "1.48.2"
},
"bin": {
"playwright": "cli.js"
@@ -8161,9 +8161,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.48.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.1.tgz",
- "integrity": "sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==",
+ "version": "1.48.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz",
+ "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
From 52b490a7b36965c9baa7bb77b362083f2f3bfa42 Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Fri, 1 Nov 2024 09:28:17 -0700
Subject: [PATCH 12/30] Update dependabot config
---
.github/dependabot.yml | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ec404a5e2..69612bdcf 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -8,14 +8,11 @@ updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
- interval: "weekly"
+ interval: "monthly"
allow:
- dependency-name: "@playwright/test"
# https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219
target-branch: master
- assignees:
- - "jrchudy"
- - "RFSH"
- package-ecosystem: "npm"
directory: "/"
schedule:
@@ -31,6 +28,8 @@ updates:
- dependency-name: "prettier"
- dependency-name: "patch-package"
- dependency-name: "uglify-js"
+ - dependency-name: "q"
+ - dependency-name: "@types/q"
groups:
babel:
patterns:
From 04e4ce917e014802d7caa523ef6e7a01430ffb1b Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Fri, 1 Nov 2024 09:36:29 -0700
Subject: [PATCH 13/30] Update dependabot.yml
---
.github/dependabot.yml | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 69612bdcf..0c75a51bf 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -47,6 +47,3 @@ updates:
- "@types/react-dom"
- "react"
- "react-dom"
- assignees:
- - "jrchudy"
- - "RFSH"
From 83c1c439ac24374db4a656e11fb6415141497447 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 11:01:54 -0800
Subject: [PATCH 14/30] Bump webpack from 5.95.0 to 5.96.1 (#2576)
Bumps [webpack](https://github.com/webpack/webpack) from 5.95.0 to 5.96.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.95.0...v5.96.1)
---
updated-dependencies:
- dependency-name: webpack
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 99 ++++++++++++++++++++++++-----------------------
1 file changed, 50 insertions(+), 49 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d715d1bec..adac84682 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
},
"devDependencies": {
"@isrd-isi-edu/ermrest-data-utils": "^0.0.7",
- "@playwright/test": "latest",
+ "@playwright/test": "*",
"@typescript-eslint/eslint-plugin": "~7.18.0",
"@typescript-eslint/parser": "~7.18.0",
"chance": "x",
@@ -2465,16 +2465,24 @@
"version": "8.56.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
"integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
- "dev": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
"node_modules/@types/estree": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
@@ -3011,9 +3019,9 @@
"peer": true
},
"node_modules/acorn": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
- "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"bin": {
"acorn": "bin/acorn"
},
@@ -3021,14 +3029,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/acorn-import-attributes": {
- "version": "1.9.5",
- "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
- "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
- "peerDependencies": {
- "acorn": "^8"
- }
- },
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -3542,9 +3542,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.23.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
- "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "version": "4.24.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
+ "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
"funding": [
{
"type": "opencollective",
@@ -3560,10 +3560,10 @@
}
],
"dependencies": {
- "caniuse-lite": "^1.0.30001587",
- "electron-to-chromium": "^1.4.668",
- "node-releases": "^2.0.14",
- "update-browserslist-db": "^1.0.13"
+ "caniuse-lite": "^1.0.30001669",
+ "electron-to-chromium": "^1.5.41",
+ "node-releases": "^2.0.18",
+ "update-browserslist-db": "^1.1.1"
},
"bin": {
"browserslist": "cli.js"
@@ -3613,9 +3613,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001612",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz",
- "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==",
+ "version": "1.0.30001676",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz",
+ "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==",
"funding": [
{
"type": "opencollective",
@@ -4520,9 +4520,9 @@
"peer": true
},
"node_modules/electron-to-chromium": {
- "version": "1.4.746",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.746.tgz",
- "integrity": "sha512-jeWaIta2rIG2FzHaYIhSuVWqC6KJYo7oSBX4Jv7g+aVujKztfvdpf+n6MGwZdC5hQXbax4nntykLH2juIQrfPg=="
+ "version": "1.5.50",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz",
+ "integrity": "sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw=="
},
"node_modules/element-size": {
"version": "1.1.1",
@@ -4820,9 +4820,9 @@
}
},
"node_modules/escalade": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
- "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"engines": {
"node": ">=6"
}
@@ -7743,9 +7743,9 @@
}
},
"node_modules/node-releases": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
- "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
+ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
@@ -8061,8 +8061,9 @@
"peer": true
},
"node_modules/picocolors": {
- "version": "1.0.0",
- "license": "ISC"
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -10114,9 +10115,9 @@
"peer": true
},
"node_modules/update-browserslist-db": {
- "version": "1.0.13",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
- "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
+ "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"funding": [
{
"type": "opencollective",
@@ -10132,8 +10133,8 @@
}
],
"dependencies": {
- "escalade": "^3.1.1",
- "picocolors": "^1.0.0"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.0"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -10218,17 +10219,17 @@
}
},
"node_modules/webpack": {
- "version": "5.95.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
- "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
+ "version": "5.96.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz",
+ "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==",
"dependencies": {
- "@types/estree": "^1.0.5",
+ "@types/eslint-scope": "^3.7.7",
+ "@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1",
"@webassemblyjs/wasm-parser": "^1.12.1",
- "acorn": "^8.7.1",
- "acorn-import-attributes": "^1.9.5",
- "browserslist": "^4.21.10",
+ "acorn": "^8.14.0",
+ "browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
From 7d7bc2573b700fb59fe33b6176cca6c159c997fe Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 11:20:31 -0800
Subject: [PATCH 15/30] Bump set-cookie-parser from 2.4.8 to 2.7.1 (#2574)
Bumps [set-cookie-parser](https://github.com/nfriedly/set-cookie-parser) from 2.4.8 to 2.7.1.
- [Changelog](https://github.com/nfriedly/set-cookie-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nfriedly/set-cookie-parser/compare/v2.4.8...v2.7.1)
---
updated-dependencies:
- dependency-name: set-cookie-parser
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index adac84682..59fc5812e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9196,9 +9196,10 @@
}
},
"node_modules/set-cookie-parser": {
- "version": "2.4.8",
- "dev": true,
- "license": "MIT"
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "dev": true
},
"node_modules/set-function-length": {
"version": "1.2.2",
From 55bd820bed100511f02cf341abfafc0745b3f5f1 Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Mon, 4 Nov 2024 12:40:41 -0800
Subject: [PATCH 16/30] update dependabot.yml
- make sure package.json is also updated
- add more dependencies to the ignore list
---
.github/dependabot.yml | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0c75a51bf..ce0b712b2 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -17,26 +17,30 @@ updates:
directory: "/"
schedule:
interval: "monthly"
+ versioning-strategy: "increase"
open-pull-requests-limit: 10
ignore:
- dependency-name: "@playwright/test"
- - dependency-name: "moment*"
- - dependency-name: "protractor"
+ # newer versions of eslint drop support for node 18:
- dependency-name: "@typescript-eslint/*"
- dependency-name: "eslint-*"
- dependency-name: "eslint"
- - dependency-name: "prettier"
- - dependency-name: "patch-package"
+ # the included version of typescript-eslint/typescript-estree doesn't support 5.6+ typescript:
+ - dependency-name: "typescript"
+ # dependencies for old code that we don't need to update:
- dependency-name: "uglify-js"
- dependency-name: "q"
- dependency-name: "@types/q"
+ - dependency-name: "moment*"
+ # the recent updates break the array support (https://github.com/informatics-isi-edu/chaise/pull/2517#issuecomment-2292041258)
+ - dependency-name: "react-hook-form"
+ # we don't need to update these dependencies as frequently:
+ - dependency-name: "@babel/*"
+ - dependency-name: "babel-loader"
+ - dependency-name: "css-loader"
+ - dependency-name: "sass-loader"
+ - dependency-name: "prettier"
groups:
- babel:
- patterns:
- - "@babel/*"
- - "babel-loader"
- - "css-loader"
- - "sass-loader"
bootstrap:
patterns:
- "react-bootstrap"
From 6bd4ab7c73a930c3a162a02da1f67cb98d4e2c38 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:53:21 -0800
Subject: [PATCH 17/30] Bump mini-css-extract-plugin from 2.6.0 to 2.9.2
(#2578)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.6.0 to 2.9.2.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.6.0...v2.9.2)
---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 10 ++++++----
package.json | 2 +-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 59fc5812e..417a9422d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"css-element-queries": "1.2.3",
"css-loader": "^5.2.7",
"html-webpack-plugin": "^5.3.2",
- "mini-css-extract-plugin": "^2.1.0",
+ "mini-css-extract-plugin": "^2.9.2",
"plotly.js-basic-dist-min": "^2.18.1",
"q": "^1.5.1",
"react": "^18.2.0",
@@ -7572,10 +7572,12 @@
}
},
"node_modules/mini-css-extract-plugin": {
- "version": "2.6.0",
- "license": "MIT",
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz",
+ "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==",
"dependencies": {
- "schema-utils": "^4.0.0"
+ "schema-utils": "^4.0.0",
+ "tapable": "^2.2.1"
},
"engines": {
"node": ">= 12.13.0"
diff --git a/package.json b/package.json
index c34ea72dd..b94e8ad57 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
"css-element-queries": "1.2.3",
"css-loader": "^5.2.7",
"html-webpack-plugin": "^5.3.2",
- "mini-css-extract-plugin": "^2.1.0",
+ "mini-css-extract-plugin": "^2.9.2",
"plotly.js-basic-dist-min": "^2.18.1",
"q": "^1.5.1",
"react": "^18.2.0",
From ee47856a05a38cd230fbd3279874e860ea81bea4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:53:59 -0800
Subject: [PATCH 18/30] Bump html-webpack-plugin from 5.5.0 to 5.6.3 (#2569)
Bumps [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) from 5.5.0 to 5.6.3.
- [Release notes](https://github.com/jantimon/html-webpack-plugin/releases)
- [Changelog](https://github.com/jantimon/html-webpack-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jantimon/html-webpack-plugin/compare/v5.5.0...v5.6.3)
---
updated-dependencies:
- dependency-name: html-webpack-plugin
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 417a9422d..e4cfa77c6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6486,8 +6486,9 @@
}
},
"node_modules/html-webpack-plugin": {
- "version": "5.5.0",
- "license": "MIT",
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz",
+ "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==",
"dependencies": {
"@types/html-minifier-terser": "^6.0.0",
"html-minifier-terser": "^6.0.2",
@@ -6503,7 +6504,16 @@
"url": "https://opencollective.com/html-webpack-plugin"
},
"peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
"webpack": "^5.20.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
}
},
"node_modules/htmlparser2": {
From ab05f56946a571042ed7a5c5a769dd0bb16c2d39 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:54:21 -0800
Subject: [PATCH 19/30] Bump sass from 1.58.3 to 1.80.6 (#2577)
Bumps [sass](https://github.com/sass/dart-sass) from 1.58.3 to 1.80.6.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.58.3...1.80.6)
---
updated-dependencies:
- dependency-name: sass
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 402 ++++++++++++++++++++++++++++++++++++++--------
package.json | 2 +-
2 files changed, 335 insertions(+), 69 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index e4cfa77c6..ca123953f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,7 +37,7 @@
"react-hook-form": "^7.36.1",
"react-imask": "^6.5.0-alpha.0",
"react-plotly.js": "^2.5.1",
- "sass": "^1.58.3",
+ "sass": "^1.80.6",
"sass-loader": "^13.2.0",
"spark-md5": "^3.0.2",
"typescript": "~5.5.4",
@@ -2226,6 +2226,288 @@
"node": ">= 8"
}
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz",
+ "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==",
+ "hasInstallScript": true,
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.0",
+ "@parcel/watcher-darwin-arm64": "2.5.0",
+ "@parcel/watcher-darwin-x64": "2.5.0",
+ "@parcel/watcher-freebsd-x64": "2.5.0",
+ "@parcel/watcher-linux-arm-glibc": "2.5.0",
+ "@parcel/watcher-linux-arm-musl": "2.5.0",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.0",
+ "@parcel/watcher-linux-arm64-musl": "2.5.0",
+ "@parcel/watcher-linux-x64-glibc": "2.5.0",
+ "@parcel/watcher-linux-x64-musl": "2.5.0",
+ "@parcel/watcher-win32-arm64": "2.5.0",
+ "@parcel/watcher-win32-ia32": "2.5.0",
+ "@parcel/watcher-win32-x64": "2.5.0"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz",
+ "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz",
+ "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz",
+ "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz",
+ "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz",
+ "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz",
+ "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz",
+ "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz",
+ "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz",
+ "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz",
+ "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz",
+ "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz",
+ "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz",
+ "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/@playwright/test": {
"version": "1.48.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz",
@@ -3116,17 +3398,6 @@
"node": ">=4"
}
},
- "node_modules/anymatch": {
- "version": "3.1.2",
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@@ -3475,13 +3746,6 @@
"node": "*"
}
},
- "node_modules/binary-extensions": {
- "version": "2.2.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/binary-search-bounds": {
"version": "2.0.5",
"license": "MIT",
@@ -3534,6 +3798,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "devOptional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -3658,38 +3923,17 @@
"license": "MIT"
},
"node_modules/chokidar": {
- "version": "3.5.3",
- "funding": [
- {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- ],
- "license": "MIT",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
+ "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
"dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^4.0.1"
},
"engines": {
- "node": ">= 8.10.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/chokidar/node_modules/glob-parent": {
- "version": "5.1.2",
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
+ "node": ">= 14.16.0"
},
- "engines": {
- "node": ">= 6"
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/chrome-trace-event": {
@@ -4373,6 +4617,18 @@
"license": "MIT",
"peer": true
},
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -5701,6 +5957,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "devOptional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -5900,6 +6157,7 @@
},
"node_modules/fsevents": {
"version": "2.3.2",
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -6741,16 +6999,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"dev": true,
@@ -6827,6 +7075,7 @@
},
"node_modules/is-extglob": {
"version": "2.1.1",
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -6880,6 +7129,7 @@
},
"node_modules/is-glob": {
"version": "4.0.3",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -6929,6 +7179,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "devOptional": true,
"engines": {
"node": ">=0.12.0"
}
@@ -7547,7 +7798,7 @@
},
"node_modules/micromatch": {
"version": "4.0.5",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.2",
@@ -7754,6 +8005,12 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "optional": true
+ },
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@@ -7761,6 +8018,7 @@
},
"node_modules/normalize-path": {
"version": "3.0.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -8079,6 +8337,7 @@
},
"node_modules/picomatch": {
"version": "2.3.1",
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -8691,13 +8950,15 @@
}
},
"node_modules/readdirp": {
- "version": "3.6.0",
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
+ "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
"engines": {
- "node": ">=8.10.0"
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/rechoir": {
@@ -9080,10 +9341,11 @@
"peer": true
},
"node_modules/sass": {
- "version": "1.58.3",
- "license": "MIT",
+ "version": "1.80.6",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz",
+ "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==",
"dependencies": {
- "chokidar": ">=3.0.0 <4.0.0",
+ "chokidar": "^4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
@@ -9091,7 +9353,10 @@
"sass": "sass.js"
},
"engines": {
- "node": ">=12.0.0"
+ "node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass-loader": {
@@ -9796,6 +10061,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "devOptional": true,
"dependencies": {
"is-number": "^7.0.0"
},
diff --git a/package.json b/package.json
index b94e8ad57..4ebef4363 100644
--- a/package.json
+++ b/package.json
@@ -77,7 +77,7 @@
"react-hook-form": "^7.36.1",
"react-imask": "^6.5.0-alpha.0",
"react-plotly.js": "^2.5.1",
- "sass": "^1.58.3",
+ "sass": "^1.80.6",
"sass-loader": "^13.2.0",
"spark-md5": "^3.0.2",
"typescript": "~5.5.4",
From 3e2794098eba0fb8c347f65870cd4d32694190b2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:55:01 -0800
Subject: [PATCH 20/30] Bump the react group with 2 updates (#2568)
Bumps the react group with 2 updates: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).
Updates `@types/react` from 18.3.10 to 18.3.12
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)
Updates `@types/react-dom` from 18.3.0 to 18.3.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)
---
updated-dependencies:
- dependency-name: "@types/react"
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: react
- dependency-name: "@types/react-dom"
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: react
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 16 ++++++++--------
package.json | 4 ++--
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ca123953f..f54024c35 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,9 +16,9 @@
"@fortawesome/fontawesome-free": "6.5.2",
"@types/bootstrap": "^5.1.9",
"@types/q": "^1.5.5",
- "@types/react": "^18.0.0",
+ "@types/react": "^18.3.12",
"@types/react-beautiful-dnd": "^13.1.4",
- "@types/react-dom": "^18.0.0",
+ "@types/react-dom": "^18.3.1",
"axios": "^1.6.0",
"babel-loader": "^9.1.3",
"bootstrap": "^5.1.3",
@@ -2833,9 +2833,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "18.3.10",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
- "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
+ "version": "18.3.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
+ "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2850,9 +2850,9 @@
}
},
"node_modules/@types/react-dom": {
- "version": "18.3.0",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
- "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
"dependencies": {
"@types/react": "*"
}
diff --git a/package.json b/package.json
index 4ebef4363..01c82f822 100644
--- a/package.json
+++ b/package.json
@@ -56,9 +56,9 @@
"@fortawesome/fontawesome-free": "6.5.2",
"@types/bootstrap": "^5.1.9",
"@types/q": "^1.5.5",
- "@types/react": "^18.0.0",
+ "@types/react": "^18.3.12",
"@types/react-beautiful-dnd": "^13.1.4",
- "@types/react-dom": "^18.0.0",
+ "@types/react-dom": "^18.3.1",
"axios": "^1.6.0",
"babel-loader": "^9.1.3",
"bootstrap": "^5.1.3",
From 93f3f955d506c55eb209721446af5d6bc4b76f6a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 17:03:49 -0800
Subject: [PATCH 21/30] Bump micromatch from 4.0.5 to 4.0.8 (#2579)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)
---
updated-dependencies:
- dependency-name: micromatch
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index f54024c35..a39d84adc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7797,11 +7797,12 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.5",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"devOptional": true,
- "license": "MIT",
"dependencies": {
- "braces": "^3.0.2",
+ "braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
From 8f612ca72f16eb0f4caa29f480f2cd880ba1a413 Mon Sep 17 00:00:00 2001
From: Keny Shah
Date: Mon, 4 Nov 2024 17:05:13 -0800
Subject: [PATCH 22/30] Added sticky scrollbar and intersection API to
recordset tables (#2564)
---
src/assets/scss/_recordset-table.scss | 9 +++
src/components/record/record-main-section.tsx | 1 +
src/components/record/record.tsx | 1 +
src/components/record/related-table.tsx | 17 ++++--
src/components/recordedit/recordedit.tsx | 6 +-
src/components/recordedit/resultset-table.tsx | 17 ++++--
src/components/recordset/recordset-table.tsx | 55 +++++++++++++++++--
7 files changed, 88 insertions(+), 18 deletions(-)
diff --git a/src/assets/scss/_recordset-table.scss b/src/assets/scss/_recordset-table.scss
index 7c9038931..9c7a86047 100644
--- a/src/assets/scss/_recordset-table.scss
+++ b/src/assets/scss/_recordset-table.scss
@@ -253,3 +253,12 @@
margin-left: 5px;
}
}
+
+.chaise-table-top-scroll-wrapper {
+ position: sticky;
+ top: 0;
+}
+.no-scroll-bar {
+ display: none !important;
+ position: absolute !important;
+}
diff --git a/src/components/record/record-main-section.tsx b/src/components/record/record-main-section.tsx
index d871d98dd..0f9fb42d6 100644
--- a/src/components/record/record-main-section.tsx
+++ b/src/components/record/record-main-section.tsx
@@ -128,6 +128,7 @@ const RecordMainSection = (): JSX.Element => {
diff --git a/src/components/record/record.tsx b/src/components/record/record.tsx
index bf9414eb7..ae5fc58c3 100644
--- a/src/components/record/record.tsx
+++ b/src/components/record/record.tsx
@@ -684,6 +684,7 @@ const RecordInner = ({
diff --git a/src/components/record/related-table.tsx b/src/components/record/related-table.tsx
index fa1f4c9c3..36b0e3053 100644
--- a/src/components/record/related-table.tsx
+++ b/src/components/record/related-table.tsx
@@ -17,14 +17,12 @@ import { RecordRelatedModel } from '@isrd-isi-edu/chaise/src/models/record';
// providers
import RecordsetProvider from '@isrd-isi-edu/chaise/src/providers/recordset';
-// services
-import $log from '@isrd-isi-edu/chaise/src/services/logger';
-
// utils
import { CLASS_NAMES } from '@isrd-isi-edu/chaise/src/utils/constants';
-import { determineScrollElement, displayCustomModeRelated } from '@isrd-isi-edu/chaise/src/utils/record-utils';
+import { displayCustomModeRelated } from '@isrd-isi-edu/chaise/src/utils/record-utils';
import { makeSafeIdAttr } from '@isrd-isi-edu/chaise/src/utils/string-utils';
+
type RelatedTableProps = {
/**
* the related model that we want to represent
@@ -34,6 +32,10 @@ type RelatedTableProps = {
* the displayname for the reference to be used in the id attached to the container
*/
displaynameForID: string
+ /**
+ * Determines if both horizontal scrollbars should always be visible, or if only one should appear at a time.
+ */
+ showSingleScrollbar?: boolean,
};
/**
@@ -44,19 +46,21 @@ type RelatedTableProps = {
const RelatedTable = ({
relatedModel,
displaynameForID,
+ showSingleScrollbar
}: RelatedTableProps): JSX.Element => {
return (
-
+
)
}
const RelatedTableInner = ({
relatedModel,
- displaynameForID
+ displaynameForID,
+ showSingleScrollbar
}: RelatedTableProps) => {
const {
page, isInitialized, hasTimeoutError, isLoading,
@@ -111,6 +115,7 @@ const RelatedTableInner = ({
diff --git a/src/components/recordedit/recordedit.tsx b/src/components/recordedit/recordedit.tsx
index 0542c5a70..8bafbb89a 100644
--- a/src/components/recordedit/recordedit.tsx
+++ b/src/components/recordedit/recordedit.tsx
@@ -804,7 +804,9 @@ const RecordeditInner = ({
}
-
+ {/* Intersecting behaviour of scroll should be visible if there are multiple tables
+ on one page which here seems to be the case when there are successful as well as failed records */}
+
{resultsetProps.failed &&
@@ -815,7 +817,7 @@ const RecordeditInner = ({
exploreLink={resultsetProps.failed.exploreLink}
/>
-
+
}
diff --git a/src/components/recordedit/resultset-table.tsx b/src/components/recordedit/resultset-table.tsx
index be3a32330..512b6dbfd 100644
--- a/src/components/recordedit/resultset-table.tsx
+++ b/src/components/recordedit/resultset-table.tsx
@@ -4,7 +4,7 @@ import RecordsetTable from '@isrd-isi-edu/chaise/src/components/recordset/record
// models
import { RecordsetConfig } from '@isrd-isi-edu/chaise/src/models/recordset'
import { RecordsetDisplayMode, RecordsetSelectMode } from '@isrd-isi-edu/chaise/src/models/recordset';
-import { LogActions, LogReloadCauses, LogStackPaths, LogStackTypes } from '@isrd-isi-edu/chaise/src/models/log';
+import { LogStackPaths } from '@isrd-isi-edu/chaise/src/models/log';
// providers
import RecordsetProvider from '@isrd-isi-edu/chaise/src/providers/recordset';
@@ -13,11 +13,16 @@ import RecordsetProvider from '@isrd-isi-edu/chaise/src/providers/recordset';
import { LogService } from '@isrd-isi-edu/chaise/src/services/log';
type ResultsetTableProps = {
- page: any
+ page: any,
+ /**
+ * Determines if both horizontal scrollbars should always be visible, or if only one should appear at a time.
+ */
+ showSingleScrollbar: boolean,
}
const ResultsetTable = ({
page,
+ showSingleScrollbar,
}: ResultsetTableProps) : JSX.Element => {
const logStack = [LogService.getStackNode(LogStackPaths.SET, page.reference.table, page.reference.filterInfo)];
@@ -47,21 +52,23 @@ const ResultsetTable = ({
}}
initialPage={page}
>
-
+
)
}
const ResultsetTableInner = ({
reference,
- config
+ config,
+ showSingleScrollbar,
}: {
config: RecordsetConfig
reference: any
+ showSingleScrollbar: boolean,
}) : JSX.Element => {
return (
-
+
)
}
diff --git a/src/components/recordset/recordset-table.tsx b/src/components/recordset/recordset-table.tsx
index 44e7d595e..9f5174904 100644
--- a/src/components/recordset/recordset-table.tsx
+++ b/src/components/recordset/recordset-table.tsx
@@ -29,12 +29,17 @@ import { addTopHorizontalScroll, fireCustomEvent } from '@isrd-isi-edu/chaise/sr
type RecordsetTableProps = {
config: RecordsetConfig,
initialSortObject: any,
+ /**
+ * Determines if both horizontal scrollbars should always be visible, or if only one should appear at a time.
+ */
+ showSingleScrollbar?: boolean,
sortCallback?: (sortColumn: SortColumn) => any
}
const RecordsetTable = ({
config,
- initialSortObject
+ initialSortObject,
+ showSingleScrollbar = false
}: RecordsetTableProps): JSX.Element => {
const {
@@ -53,6 +58,9 @@ const RecordsetTable = ({
} = useRecordset();
const tableContainer = useRef(null);
+ const stickyScrollbarRef = useRef(null);
+ const tableEndRef = useRef(null);
+
const [currSortColumn, setCurrSortColumn] = useState(
Array.isArray(initialSortObject) ? initialSortObject[0] : null
@@ -90,7 +98,7 @@ const RecordsetTable = ({
isDisabled: false,
disabledType: undefined
};
- // page.tuples.forEach((tuple: any, index: number) => {
+ // page.tuples.forEach((tuple: any, index: number) => {
if (hasSelectedRows) {
const row = selectedRows.find((obj: SelectedRow) => {
// ermrestjs always returns a string for uniqueId, but internally we don't
@@ -115,12 +123,45 @@ const RecordsetTable = ({
}
tempRowDetails[i] = rowConfig;
- // });
+ // });
}
rowDetails = tempRowDetails;
}
+ useEffect(() => {
+ //Only implement intersection observer for top scrollbar when showSingleScrollbar is true otherwise top scrollbar will be shown as sticky
+ if (!showSingleScrollbar) return;
+
+ // Create a new IntersectionObserver instance to track the visibility of the bottom scrollbar(end of table)
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ //Updating isBottomVisible when bottom scrollbar is visible in the viewport
+ if (stickyScrollbarRef.current) {
+ if (entry.isIntersecting) {
+ stickyScrollbarRef.current.classList.add('no-scroll-bar');
+ }
+ else {
+ stickyScrollbarRef.current.classList.remove('no-scroll-bar');
+ }
+ }
+ },
+ {
+ root: null, // Use viewport as the root
+ threshold: 0.1, // Triggers when 10% of the element is visible
+ }
+ );
+ //Observes when the table end is visible on viewport
+ if (tableEndRef.current) {
+ observer.observe(tableEndRef.current);
+ }
+
+ return () => {
+ observer.disconnect();
+ }
+ }, []);
+
+
/**
* add the top horizontal scroll if needed
*/
@@ -472,10 +513,10 @@ const RecordsetTable = ({
const tableSchemaNames = `s_${makeSafeIdAttr(reference.table.schema.name)} t_${makeSafeIdAttr(reference.table.name)}`;
return classNameString + ' ' + tableSchemaNames;
}
-
return (
-
+
@@ -491,6 +532,10 @@ const RecordsetTable = ({
+ {/* This div will be used as the target (end of table) for the intersection observer to hide the
+ top scrollbar when the bottom one is visible */}
+
+
{!hasTimeoutError && numHiddenRecords > 0 &&
setShowAllRows(!showAllRows)} className='show-all-rows-btn chaise-btn chaise-btn-primary'>
From f0d58177a171e3b5ef3616fe84d0c79ae2f12710 Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Wed, 6 Nov 2024 11:24:02 -0800
Subject: [PATCH 23/30] fix a typo in chaise-config-sample.js
---
config/chaise-config-sample.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/chaise-config-sample.js b/config/chaise-config-sample.js
index 9ce7dc089..e2360e6fd 100644
--- a/config/chaise-config-sample.js
+++ b/config/chaise-config-sample.js
@@ -24,7 +24,7 @@ var chaiseConfig = {
maxRecordsetRowHeight: 160,
navbarBanner: [
{
- markdown_pattern: "This is a development version of Chaise",
+ markdownPattern: "This is a development version of Chaise",
// // to make the banner dismissible:
// dismissible: true,
// // to ensure showing the banner only to certain users:
From f54b5d6ab4caf46732b46b944268d41df0900c0b Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Thu, 7 Nov 2024 16:16:35 -0800
Subject: [PATCH 24/30] bump chaise version
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a39d84adc..265cbf0fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@isrd-isi-edu/chaise",
- "version": "0.0.22",
+ "version": "0.0.23",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@isrd-isi-edu/chaise",
- "version": "0.0.22",
+ "version": "0.0.23",
"license": "Apache-2.0",
"dependencies": {
"@babel/core": "7.24.x",
diff --git a/package.json b/package.json
index 01c82f822..11845a329 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@isrd-isi-edu/chaise",
"description": "An adaptive User Interface for ERMrest data sources",
- "version": "0.0.22",
+ "version": "0.0.23",
"license": "Apache-2.0",
"engines": {
"node": ">= 18.18.0 || >= 20.0.0",
From aaf173c150a5b640554a933aca26bf0e6b97d2ec Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Thu, 7 Nov 2024 17:47:12 -0800
Subject: [PATCH 25/30] update sass version and make sure it works (#2580)
other changes,
- Introduced a new extraWebpackProps to the getWebPackConfig
so deriva-webapps can properly define the external Plotly object.
- Added diff-search to npm-publish.yml so it can detect version changes
by just looking at the pacakge.json.
---
.github/workflows/npm-publish.yml | 2 +
package-lock.json | 28 ++--
package.json | 2 +-
src/assets/scss/_alerts.scss | 4 +-
src/assets/scss/_button-group.scss | 2 +-
src/assets/scss/_buttons.scss | 25 ++--
src/assets/scss/_dropdown.scss | 17 +--
src/assets/scss/_faceting.scss | 6 +-
src/assets/scss/_input-switch.scss | 19 +--
src/assets/scss/_inputs.scss | 32 ++---
src/assets/scss/_markdown-container.scss | 8 +-
src/assets/scss/_modal.scss | 28 ++--
src/assets/scss/_navbar.scss | 46 +++----
src/assets/scss/_range-input.scss | 2 +-
src/assets/scss/_range-picker.scss | 2 +-
src/assets/scss/_record-main-section.scss | 20 +--
src/assets/scss/_record.scss | 2 +-
src/assets/scss/_recordedit.scss | 44 +++----
src/assets/scss/_recordset-table.scss | 30 ++---
src/assets/scss/_recordset.scss | 10 +-
src/assets/scss/_split-view.scss | 4 +-
src/assets/scss/_table-header.scss | 2 +-
src/assets/scss/_variables.scss | 16 ++-
src/assets/scss/_viewer.scss | 36 ++---
src/assets/scss/app.scss | 124 +++++++++---------
src/assets/scss/helpers.scss | 19 +--
src/assets/scss/maps/_chaise-accordion.scss | 2 +-
src/assets/scss/maps/_color-map.scss | 2 +-
.../scss/maps/_record-page-spacing.scss | 2 +-
src/assets/scss/maps/_z-index-map.scss | 2 +-
webpack/app.config.js | 13 +-
webpack/main.config.js | 10 +-
32 files changed, 291 insertions(+), 270 deletions(-)
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index c0d64add5..ceb1d8760 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -19,6 +19,8 @@ jobs:
- name: Check if version has been updated
id: version-check
uses: EndBug/version-check@v2
+ with:
+ diff-search: true
- name: Publish new version
if: steps.version-check.outputs.changed == 'true'
run: |
diff --git a/package-lock.json b/package-lock.json
index 265cbf0fc..3e70fd2e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -38,7 +38,7 @@
"react-imask": "^6.5.0-alpha.0",
"react-plotly.js": "^2.5.1",
"sass": "^1.80.6",
- "sass-loader": "^13.2.0",
+ "sass-loader": "^16.0.3",
"spark-md5": "^3.0.2",
"typescript": "~5.5.4",
"uglify-js": "3.9.2",
@@ -47,7 +47,7 @@
},
"devDependencies": {
"@isrd-isi-edu/ermrest-data-utils": "^0.0.7",
- "@playwright/test": "*",
+ "@playwright/test": "latest",
"@typescript-eslint/eslint-plugin": "~7.18.0",
"@typescript-eslint/parser": "~7.18.0",
"chance": "x",
@@ -7620,13 +7620,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/klona": {
- "version": "2.0.5",
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -9361,28 +9354,28 @@
}
},
"node_modules/sass-loader": {
- "version": "13.2.0",
- "license": "MIT",
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.3.tgz",
+ "integrity": "sha512-gosNorT1RCkuCMyihv6FBRR7BMV06oKRAs+l4UMp1mlcVg9rWN6KMmUj3igjQwmYys4mDP3etEYJgiHRbgHCHA==",
"dependencies": {
- "klona": "^2.0.4",
"neo-async": "^2.6.2"
},
"engines": {
- "node": ">= 14.15.0"
+ "node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "fibers": ">= 3.1.0",
- "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "@rspack/core": "0.x || 1.x",
+ "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
- "fibers": {
+ "@rspack/core": {
"optional": true
},
"node-sass": {
@@ -9393,6 +9386,9 @@
},
"sass-embedded": {
"optional": true
+ },
+ "webpack": {
+ "optional": true
}
}
},
diff --git a/package.json b/package.json
index 11845a329..e7eaed0ae 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,7 @@
"react-imask": "^6.5.0-alpha.0",
"react-plotly.js": "^2.5.1",
"sass": "^1.80.6",
- "sass-loader": "^13.2.0",
+ "sass-loader": "^16.0.3",
"spark-md5": "^3.0.2",
"typescript": "~5.5.4",
"uglify-js": "3.9.2",
diff --git a/src/assets/scss/_alerts.scss b/src/assets/scss/_alerts.scss
index 9901c7cf8..6a86ae676 100644
--- a/src/assets/scss/_alerts.scss
+++ b/src/assets/scss/_alerts.scss
@@ -12,8 +12,8 @@
// by default we're using red color for code blocks which can be misleading in a success alert
.alert-success code {
- color: map-get(variables.$color-map, 'code-block-success');
- background-color: map-get(variables.$color-map, 'code-block-background-alt');
+ color: map.get(variables.$color-map, 'code-block-success');
+ background-color: map.get(variables.$color-map, 'code-block-background-alt');
}
}
diff --git a/src/assets/scss/_button-group.scss b/src/assets/scss/_button-group.scss
index 744f329b3..b409eeacf 100644
--- a/src/assets/scss/_button-group.scss
+++ b/src/assets/scss/_button-group.scss
@@ -19,7 +19,7 @@
&:active,
&:focus,
&:hover {
- z-index: map-get(variables.$z-index-map, 'button-active');
+ z-index: map.get(variables.$z-index-map, 'button-active');
}
}
}
diff --git a/src/assets/scss/_buttons.scss b/src/assets/scss/_buttons.scss
index 7434d5463..e142a0333 100644
--- a/src/assets/scss/_buttons.scss
+++ b/src/assets/scss/_buttons.scss
@@ -1,3 +1,4 @@
+@use "sass:color";
@use 'sass:map';
@use 'variables';
@use 'helpers';
@@ -53,37 +54,37 @@
&.chaise-btn.chaise-btn-secondary[disabled],
&.chaise-btn.chaise-btn-primary.disabled,
&.chaise-btn.chaise-btn-secondary.disabled {
- color: map-get(variables.$color-map, 'disabled');
- background-color: map-get(variables.$color-map, 'disabled-background');
- border-color: map-get(variables.$color-map, 'disabled-background');
+ color: map.get(variables.$color-map, 'disabled');
+ background-color: map.get(variables.$color-map, 'disabled-background');
+ border-color: map.get(variables.$color-map, 'disabled-background');
}
&.chaise-btn-tertiary {
- color: map-get(variables.$color-map, 'primary');
+ color: map.get(variables.$color-map, 'primary');
border: none;
background: transparent;
&[disabled],
&.disabled {
- color: map-get(variables.$color-map, 'disabled');
+ color: map.get(variables.$color-map, 'disabled');
}
// the link color is more prominent than our button/control color,
// we should use this class if we want to make the button to be as prominent as links
&.chaise-btn-link:not([disabled]):not(.disabled) {
- color: map-get(variables.$color-map, 'link');
+ color: map.get(variables.$color-map, 'link');
}
}
&.chaise-btn-default {
- color: map-get(variables.$color-map, 'black');
- border-color: map-get(variables.$color-map, 'border');
+ color: map.get(variables.$color-map, 'black');
+ border-color: map.get(variables.$color-map, 'border');
}
&.chaise-btn-danger {
- color: map-get(variables.$color-map, 'white');
- background-color: map-get(variables.$color-map, 'danger');
- border-color: darken(map-get(variables.$color-map, 'danger'), 10%);
+ color: map.get(variables.$color-map, 'white');
+ background-color: map.get(variables.$color-map, 'danger');
+ border-color: color.adjust(map.get(variables.$color-map, 'danger'), $lightness: -10%);
}
&.chaise-download-btn {
@@ -99,7 +100,7 @@
// this is currently designed mostly for
&.chaise-btn-with-indicator:before {
content: '';
- background-color: map-get(variables.$color-map, 'primary');
+ background-color: map.get(variables.$color-map, 'primary');
position: absolute;
top: -1px;
right: -1px;
diff --git a/src/assets/scss/_dropdown.scss b/src/assets/scss/_dropdown.scss
index a34814c6d..12b49a5ef 100644
--- a/src/assets/scss/_dropdown.scss
+++ b/src/assets/scss/_dropdown.scss
@@ -1,3 +1,4 @@
+@use "sass:color";
@use 'sass:map';
@use 'variables';
@use 'helpers';
@@ -17,7 +18,7 @@
padding: 3px 15px 3px 15px;
cursor: default;
font-size: variables.$font-size;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
font-weight: 600;
}
@@ -44,7 +45,7 @@
border-style: solid;
border-width: 6px 0 6px 6px;
/* was 5px 0 5px 5px */
- border-left-color: map-get(variables.$color-map, 'navbar-dropdown-submenu-icon');
+ border-left-color: map.get(variables.$color-map, 'navbar-dropdown-submenu-icon');
// to center
margin: auto;
margin-right: -15px;
@@ -55,7 +56,7 @@
.dropdown-submenu:hover > a:after,
.dropdown-submenu:hover > div > div > a:after {
- border-left-color: map-get(variables.$color-map, 'navbar-dropdown-submenu');
+ border-left-color: map.get(variables.$color-map, 'navbar-dropdown-submenu');
}
.dropdown-submenu.pull-left {
@@ -133,7 +134,7 @@
cursor: pointer;
display: flex;
justify-content: space-between;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
margin-bottom: 2px;
padding: 3px 15px 3px 15px;
line-height: 1.4;
@@ -147,8 +148,8 @@
&:focus,
&:hover {
- color: lighten(map-get(variables.$color-map, 'black'), 10%);
- background-color: darken(map-get(variables.$color-map, 'white'), 10%);
+ color: color.adjust(map.get(variables.$color-map, 'black'), $lightness: 10%);
+ background-color: color.adjust(map.get(variables.$color-map, 'white'), $lightness: -10%);
}
}
@@ -182,11 +183,11 @@
.disable-link, .disabled {
pointer-events: none !important;
cursor: default !important;
- color: map-get(variables.$color-map, 'disabled') !important;
+ color: map.get(variables.$color-map, 'disabled') !important;
// the arrow icon
&:after {
- border-left-color: map-get(variables.$color-map, 'disabled') !important;
+ border-left-color: map.get(variables.$color-map, 'disabled') !important;
}
}
}
diff --git a/src/assets/scss/_faceting.scss b/src/assets/scss/_faceting.scss
index 3b37793cf..bf6e473d5 100644
--- a/src/assets/scss/_faceting.scss
+++ b/src/assets/scss/_faceting.scss
@@ -42,7 +42,7 @@
// each facet panel
.facet-panel {
border-radius: 0;
- border: 1px solid map-get(variables.$color-map, 'border');
+ border: 1px solid map.get(variables.$color-map, 'border');
// active style
&.active,
@@ -80,8 +80,8 @@
flex-direction: row-reverse;
justify-content: flex-end;
- background-color: map-get(variables.$color-map, 'table-header-background');
- color: map-get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'table-header-background');
+ color: map.get(variables.$color-map, 'black');
// Overriding accordian-button css to align right angle bracket icon to left
&::after {
diff --git a/src/assets/scss/_input-switch.scss b/src/assets/scss/_input-switch.scss
index c3c89bc90..c50457718 100644
--- a/src/assets/scss/_input-switch.scss
+++ b/src/assets/scss/_input-switch.scss
@@ -1,3 +1,4 @@
+@use "sass:map";
@use 'variables';
@@ -48,7 +49,7 @@
.input-switch-longtext {
.md-editor {
- border: 1px solid map-get(variables.$color-map, 'border');
+ border: 1px solid map.get(variables.$color-map, 'border');
border-radius: 4px;
.md-header {
@@ -109,7 +110,7 @@
}
.hex-sign {
- color: map-get(variables.$color-map, 'placeholder');
+ color: map.get(variables.$color-map, 'placeholder');
}
.chaise-color-picker-preview {
@@ -187,15 +188,15 @@
left: 0;
height: 100%;
width: 100%;
- z-index: map-get(variables.$z-index-map, 'recordedit-foreignkey-input-spinner-backdrop');
- background: map-get(variables.$color-map, 'disabled-background');
+ z-index: map.get(variables.$z-index-map, 'recordedit-foreignkey-input-spinner-backdrop');
+ background: map.get(variables.$color-map, 'disabled-background');
opacity: 0.55;
}
.spinner-border-sm {
top: 8px;
position: absolute;
- z-index: map-get(variables.$z-index-map, 'recordedit-column-cell-spinner');
+ z-index: map.get(variables.$z-index-map, 'recordedit-column-cell-spinner');
}
}
@@ -223,7 +224,7 @@
*/
>.chaise-input-group:not([aria-disabled="true"]),
.dropdown-menu {
- border: 2px solid map-get(variables.$color-map, 'primary');
+ border: 2px solid map.get(variables.$color-map, 'primary');
border-radius: 6px;
}
}
@@ -275,7 +276,7 @@
// bootstrap stylesheets have :hover defined before :disabled so disabled always takes precedence
// redefine with the same hover color to override bootstrap order
&:hover {
- background-color: map-get(variables.$color-map, 'recordedit-dropdown-hover');
+ background-color: map.get(variables.$color-map, 'recordedit-dropdown-hover');
}
}
@@ -341,11 +342,11 @@
.input-switch-error {
&.input-switch-error-danger {
- color: map-get(variables.$color-map, 'input-error-message-danger');
+ color: map.get(variables.$color-map, 'input-error-message-danger');
}
&.input-switch-error-warning {
- color: map-get(variables.$color-map, 'input-error-message-warning');
+ color: map.get(variables.$color-map, 'input-error-message-warning');
}
}
diff --git a/src/assets/scss/_inputs.scss b/src/assets/scss/_inputs.scss
index 7bc048df9..91a57ec49 100644
--- a/src/assets/scss/_inputs.scss
+++ b/src/assets/scss/_inputs.scss
@@ -69,11 +69,11 @@
// as our inputs.
.chaise-btn {
position: relative;
- z-index: map-get(variables.$z-index-map, 'input-group-append-btn'); // should be above input even if input is focused
+ z-index: map.get(variables.$z-index-map, 'input-group-append-btn'); // should be above input even if input is focused
@include helpers.border-left-radius(0);
&:focus {
- z-index: map-get(variables.$z-index-map, 'input-group-append-btn-focus');
+ z-index: map.get(variables.$z-index-map, 'input-group-append-btn-focus');
}
}
@@ -127,11 +127,11 @@
padding: variables.$btn-padding-y variables.$btn-padding-x;
margin-bottom: 0; // Allow use of elements by overriding our default margin-bottom
font-size: 1rem;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
text-align: center;
white-space: nowrap;
- background-color: map-get(variables.$color-map, 'table-striped-background');
- border: variables.$btn-border-width solid map-get(variables.$color-map, 'border');
+ background-color: map.get(variables.$color-map, 'table-striped-background');
+ border: variables.$btn-border-width solid map.get(variables.$color-map, 'border');
@include helpers.border-radius(variables.$btn-border-radius);
// dt meaning "datetime"
@@ -175,9 +175,9 @@
height: variables.$btn-height;
min-height: variables.$btn-height;
padding: variables.$btn-padding-y variables.$btn-padding-x;
- background-color: map-get(variables.$color-map, 'white');
+ background-color: map.get(variables.$color-map, 'white');
background-clip: padding-box;
- border: variables.$btn-border-width solid map-get(variables.$color-map, 'border');
+ border: variables.$btn-border-width solid map.get(variables.$color-map, 'border');
@include helpers.border-radius(variables.$btn-border-radius);
@@ -214,7 +214,7 @@
// Placeholder
&::placeholder,
input::placeholder {
- color: map-get(variables.$color-map, 'placeholder');
+ color: map.get(variables.$color-map, 'placeholder');
// Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.
opacity: 1;
}
@@ -229,9 +229,9 @@
&[readonly],
&[readonly][disabled],
&.input-disabled {
- background-color: map-get(variables.$color-map, 'disabled-background');
- border-color: map-get(variables.$color-map, 'disabled-background');
- color: map-get(variables.$color-map, 'disabled');
+ background-color: map.get(variables.$color-map, 'disabled-background');
+ border-color: map.get(variables.$color-map, 'disabled-background');
+ color: map.get(variables.$color-map, 'disabled');
// iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.
opacity: 1;
cursor: not-allowed;
@@ -247,7 +247,7 @@
}
.chaise-input-placeholder {
- color: map-get(variables.$color-map, 'disabled');
+ color: map.get(variables.$color-map, 'disabled');
}
}
@@ -258,8 +258,8 @@
// form-control:focus changes the z-index of the form control from 2 to 3.
// use z index 5 so it isn't hidden when focused
.chaise-input-control-feedback {
- z-index: map-get(variables.$z-index-map, 'input-control-feedback');
- color: map-get(variables.$color-map, 'black');
+ z-index: map.get(variables.$z-index-map, 'input-control-feedback');
+ color: map.get(variables.$color-map, 'black');
position: absolute;
right: 0;
top: 5px;
@@ -276,7 +276,7 @@
// NOTE: cannot be used for `input` tags or placeholders with HTML
div[contenteditable='false']:empty:not(:focus):before {
content: attr(data-placeholder); // required data-placeholder being present
- color: map-get(variables.$color-map, 'placeholder');
+ color: map.get(variables.$color-map, 'placeholder');
}
// adds a grey placeholder into input fields (inputs) that are also editable
@@ -285,7 +285,7 @@
position: absolute;
top: variables.$btn-padding-y;
left: variables.$btn-padding-x;
- color: map-get(variables.$color-map, 'placeholder');
+ color: map.get(variables.$color-map, 'placeholder');
// show ellipsis if there aren't enough space:
width: calc(100% - 10px); // we have to account for the padding-left of .button-text
diff --git a/src/assets/scss/_markdown-container.scss b/src/assets/scss/_markdown-container.scss
index feb8add99..6b45fea6b 100644
--- a/src/assets/scss/_markdown-container.scss
+++ b/src/assets/scss/_markdown-container.scss
@@ -89,12 +89,12 @@
height: 11px;
width: 11px;
border-radius: 3px;
- border: 1px solid map-get(variables.$color-map, 'black');
+ border: 1px solid map.get(variables.$color-map, 'black');
}
.vocab {
- color: map-get(variables.$color-map, 'vocab');
- background-color: map-get(variables.$color-map, 'vocab-background');
+ color: map.get(variables.$color-map, 'vocab');
+ background-color: map.get(variables.$color-map, 'vocab-background');
padding: 7px;
border-radius: 10px;
display: inline-block;
@@ -727,7 +727,7 @@
.markdown-container:checked + .radio-label {
position: relative;
- z-index: map-get(variables.$z-index-map, 'markdown-container-radio-label');
+ z-index: map.get(variables.$z-index-map, 'markdown-container-radio-label');
border-color: #0366d6;
}
diff --git a/src/assets/scss/_modal.scss b/src/assets/scss/_modal.scss
index c33b5b9d8..03a5001db 100644
--- a/src/assets/scss/_modal.scss
+++ b/src/assets/scss/_modal.scss
@@ -357,12 +357,12 @@ div.modal-title {
.entity-key-column .entity-key {
border: none;
- border-top: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
+ border-top: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
}
.recordedit-form .entity-value {
border: none;
- border-top: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
+ border-top: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
}
}
}
@@ -380,58 +380,58 @@ div.modal-title {
/*************** z-index for modal backdrops ***************/
div.modal-backdrop {
// override the default bootstrap value
- z-index: map-get(variables.$z-index-map, 'modal-backdrop');
+ z-index: map.get(variables.$z-index-map, 'modal-backdrop');
&.modal-error-backdrop {
- z-index: map-get(variables.$z-index-map, 'modal-error-backdrop');
+ z-index: map.get(variables.$z-index-map, 'modal-error-backdrop');
}
&.export-progress-backdrop {
- z-index: map-get(variables.$z-index-map, 'export-progress-backdrop');
+ z-index: map.get(variables.$z-index-map, 'export-progress-backdrop');
}
&.modal-login-instruction-backdrop {
- z-index: map-get(variables.$z-index-map, 'modal-login-instruction-backdrop');
+ z-index: map.get(variables.$z-index-map, 'modal-login-instruction-backdrop');
}
// allows modal on modal (only two levels)
~ .modal-backdrop {
- z-index: map-get(variables.$z-index-map, 'modal-backdrop-on-modal');
+ z-index: map.get(variables.$z-index-map, 'modal-backdrop-on-modal');
}
}
/*************** z-index for modals ***************/
div.modal {
// override the default bootstrap value
- z-index: map-get(variables.$z-index-map, 'modal');
+ z-index: map.get(variables.$z-index-map, 'modal');
&.modal-error {
- z-index: map-get(variables.$z-index-map, 'modal-error');
+ z-index: map.get(variables.$z-index-map, 'modal-error');
}
&.export-progress {
- z-index: map-get(variables.$z-index-map, 'export-progress');
+ z-index: map.get(variables.$z-index-map, 'export-progress');
}
&.modal-login-instruction {
- z-index: map-get(variables.$z-index-map, 'modal-login-instruction');
+ z-index: map.get(variables.$z-index-map, 'modal-login-instruction');
}
// make sure errors show up properly on top of search-popup
&.search-popup {
~ .modal-error-backdrop,
~ .modal-login-instruction-backdrop {
- z-index: map-get(variables.$z-index-map, 'modal-error-backdrop-on-search-popup');
+ z-index: map.get(variables.$z-index-map, 'modal-error-backdrop-on-search-popup');
}
~ .modal-error,
~ .modal-login-instruction {
- z-index: map-get(variables.$z-index-map, 'modal-error-on-search-popup');
+ z-index: map.get(variables.$z-index-map, 'modal-error-on-search-popup');
}
}
// allows modal on modal (only two levels)
~ .modal {
- z-index: map-get(variables.$z-index-map, 'modal-on-modal');
+ z-index: map.get(variables.$z-index-map, 'modal-on-modal');
}
}
diff --git a/src/assets/scss/_navbar.scss b/src/assets/scss/_navbar.scss
index 515d90d06..81c2122c1 100644
--- a/src/assets/scss/_navbar.scss
+++ b/src/assets/scss/_navbar.scss
@@ -14,36 +14,36 @@ body>.container {
// navheader includes navbar as well as banners
#navheader {
- z-index: map-get(variables.$z-index-map, 'navbar');
+ z-index: map.get(variables.$z-index-map, 'navbar');
position: relative; // for z-index to work, position must be non-static
}
.navbar-inverse {
- background-color: map-get(variables.$color-map, 'navbar-inverse');
- border-color: map-get(variables.$color-map, 'navbar-inverse');
+ background-color: map.get(variables.$color-map, 'navbar-inverse');
+ border-color: map.get(variables.$color-map, 'navbar-inverse');
}
.navbar-inverse .navbar-nav>div>a {
- color: map-get(variables.$color-map, 'navbar-inverse-menu-link');
+ color: map.get(variables.$color-map, 'navbar-inverse-menu-link');
}
.navbar-inverse .navbar-nav>.show>a {
// use #333 for this since navbar is #000 (even darker)
- background-color: map-get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'black');
}
// matches links at top level and dropdowns at top level
.navbar-inverse .navbar-nav>a:hover,
.navbar-inverse .navbar-nav>div>a:hover {
- background-color: map-get(variables.$color-map, 'navbar-dropdown-submenu-icon');
+ background-color: map.get(variables.$color-map, 'navbar-dropdown-submenu-icon');
}
.navbar-inverse .navbar-nav.navbar-right>div>a {
- color: map-get(variables.$color-map, 'navbar-inverse-link');
+ color: map.get(variables.$color-map, 'navbar-inverse-link');
}
.navbar-inverse .navbar-brand {
- color: map-get(variables.$color-map, 'navbar-inverse-link');
+ color: map.get(variables.$color-map, 'navbar-inverse-link');
float: left;
height: 50px;
padding: 15px 15px;
@@ -54,7 +54,7 @@ body>.container {
#live-btn {
margin-right: 0px; // offset marging-right: -15px from navbar-right
- color: map-get(variables.$color-map, 'navbar-inverse-link');
+ color: map.get(variables.$color-map, 'navbar-inverse-link');
padding: 15px;
text-decoration: none;
&:hover {
@@ -71,7 +71,7 @@ body>.container {
.navbar-inverse .navbar-nav.navbar-right>div>a:hover {
- color: map-get(variables.$color-map, 'navbar-inverse-menu-link-hover');
+ color: map.get(variables.$color-map, 'navbar-inverse-menu-link-hover');
}
.navbar-inverse .navbar-collapse,
@@ -104,13 +104,13 @@ body>.container {
.chaise-input-control-sm {
width: 100px;
- border-color: map-get(variables.$color-map, 'black');
+ border-color: map.get(variables.$color-map, 'black');
border-radius: 4px 0px 0px 4px;
}
}
#rid-search-input {
- background-color: map-get(variables.$color-map, 'table-striped-background');
+ background-color: map.get(variables.$color-map, 'table-striped-background');
overflow-x: auto;
}
@@ -149,13 +149,13 @@ body>.container {
display: block;
padding: 15px 15px !important; // so the property overrides a more specific rule from bootstrap
line-height: 20px;
- color: map-get(variables.$color-map, 'navbar-inverse-menu-link');
+ color: map.get(variables.$color-map, 'navbar-inverse-menu-link');
}
.chaise-navbar-banner-container {
margin-bottom: 0;
padding: 10px 20px 10px 20px;
- background-color: map-get(variables.$color-map, 'navbar-banner');
+ background-color: map.get(variables.$color-map, 'navbar-banner');
text-align: center;
// use the normal font instead of light and make it bigger
@@ -179,7 +179,7 @@ body>.container {
font-size: 21px;
font-weight: 700;
line-height: 1;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
text-shadow: 0 1px 0 #fff;
filter: alpha(opacity=20);
opacity: .2;
@@ -230,7 +230,7 @@ nav.navbar {
// Horizontal navbar
@media (min-width: 768px) {
.child-opened {
- background-color: map-get(variables.$color-map, 'table-header-background');
+ background-color: map.get(variables.$color-map, 'table-header-background');
}
.navbar-header {
@@ -270,28 +270,28 @@ nav.navbar {
.dropdown-menu>a,
.dropdown-menu>div>a {
- color: map-get(variables.$color-map, 'dropdown-menu-anchor');
+ color: map.get(variables.$color-map, 'dropdown-menu-anchor');
&:focus,
&:hover {
- color: map-get(variables.$color-map, 'white');
+ color: map.get(variables.$color-map, 'white');
}
}
// Menu Toggler css in smaller screens
.navbar-toggler {
- border: 1px solid map-get(variables.$color-map, 'white');
+ border: 1px solid map.get(variables.$color-map, 'white');
padding: 8px;
margin-right: 16px;
- color: map-get(variables.$color-map, 'white');
+ color: map.get(variables.$color-map, 'white');
&:hover {
- background-color: map-get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'black');
}
&:focus {
box-shadow: none;
- background-color: map-get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'black');
}
}
}
@@ -304,7 +304,7 @@ nav.navbar {
}
div.dropdown-submenu.show {
- background-color: map-get(variables.$color-map, 'navbar-collapsed-submenu');
+ background-color: map.get(variables.$color-map, 'navbar-collapsed-submenu');
}
}
}
diff --git a/src/assets/scss/_range-input.scss b/src/assets/scss/_range-input.scss
index 20a5eb730..d1e4cad90 100644
--- a/src/assets/scss/_range-input.scss
+++ b/src/assets/scss/_range-input.scss
@@ -57,5 +57,5 @@
}
.range-input-error {
- color: map-get(variables.$color-map, 'danger');
+ color: map.get(variables.$color-map, 'danger');
}
diff --git a/src/assets/scss/_range-picker.scss b/src/assets/scss/_range-picker.scss
index 2340cf483..f8c35053a 100644
--- a/src/assets/scss/_range-picker.scss
+++ b/src/assets/scss/_range-picker.scss
@@ -4,7 +4,7 @@
// range picker directive
.range-picker {
hr {
- color: map-get(variables.$color-map, 'border');
+ color: map.get(variables.$color-map, 'border');
width: 1px;
}
diff --git a/src/assets/scss/_record-main-section.scss b/src/assets/scss/_record-main-section.scss
index b94d168c4..7aac997df 100644
--- a/src/assets/scss/_record-main-section.scss
+++ b/src/assets/scss/_record-main-section.scss
@@ -20,17 +20,17 @@
// reduce the spacing of table cells (overrides bootstrap default)
> tbody > tr.row {
- border-top: map-get(variables.$record-spacing-map, 'record-table-cell-border-width') solid map-get(variables.$color-map, 'border');
+ border-top: map.get(variables.$record-spacing-map, 'record-table-cell-border-width') solid map.get(variables.$color-map, 'border');
margin: 0px;
> td {
border-top: 0;
- padding: map-get(variables.$record-spacing-map, 'record-table-cell-padding');
+ padding: map.get(variables.$record-spacing-map, 'record-table-cell-padding');
// make sure the inline related content are aligned with the related content
// so Explore button in inine related, table in inline related, Explore
// button in related, and table in related are all aligned.
- padding-right: map-get(variables.$chaise-accordion-spacing-map, 'header-padding');
+ padding-right: map.get(variables.$chaise-accordion-spacing-map, 'header-padding');
&.entity-value {
overflow-wrap: break-word;
@@ -42,7 +42,7 @@
// rest of headers
&.hidden-header {
> .entity-value {
- padding-left: map-get(variables.$record-spacing-map, 'record-header-padding-left');
+ padding-left: map.get(variables.$record-spacing-map, 'record-header-padding-left');
}
}
@@ -50,27 +50,27 @@
// row-focus will change the border top and bottom size,
// so we should adjust that and account for the default border
td {
- $_row_focus_padding: map-get(variables.$record-spacing-map, 'record-table-cell-padding') - (map-get(variables.$record-spacing-map, 'row-focus-border-width') - map-get(variables.$record-spacing-map, 'record-table-cell-border-width'));
+ $_row_focus_padding: map.get(variables.$record-spacing-map, 'record-table-cell-padding') - (map.get(variables.$record-spacing-map, 'row-focus-border-width') - map.get(variables.$record-spacing-map, 'record-table-cell-border-width'));
padding-top: $_row_focus_padding;
padding-bottom: $_row_focus_padding;
// if the size of row-focus is bigger than the current border width,
// we should adjust the content of inline tables so the whole page doesn't shift
- @if map-get(variables.$record-spacing-map, 'row-focus-border-width') > map-get(variables.$record-spacing-map, 'record-table-cell-border-width') {
+ @if map.get(variables.$record-spacing-map, 'row-focus-border-width') > map.get(variables.$record-spacing-map, 'record-table-cell-border-width') {
.inline-table-display {
- padding-bottom: map-get(variables.$record-spacing-map, 'row-focus-border-width');
+ padding-bottom: map.get(variables.$record-spacing-map, 'row-focus-border-width');
}
}
}
// when there are two of them, the bottom should use the default padding
+ tr.row-focus td {
- padding-top: map-get(variables.$record-spacing-map, 'record-table-cell-padding');
+ padding-top: map.get(variables.$record-spacing-map, 'record-table-cell-padding');
}
}
.entity-key {
- padding-left: map-get(variables.$record-spacing-map, 'record-header-padding-left');
+ padding-left: map.get(variables.$record-spacing-map, 'record-header-padding-left');
border-bottom-width: 0px;
position: relative;
padding-right: 15px;
@@ -88,7 +88,7 @@
}
.inline-tooltip {
- color: map-get(variables.$color-map, 'column-inline-tooltip');
+ color: map.get(variables.$color-map, 'column-inline-tooltip');
padding-bottom: 5px;
}
}
diff --git a/src/assets/scss/_record.scss b/src/assets/scss/_record.scss
index 07d56f299..3d7bb7e21 100644
--- a/src/assets/scss/_record.scss
+++ b/src/assets/scss/_record.scss
@@ -200,7 +200,7 @@
/******Related Entities in record **************/
.row-focus {
- border: map-get(variables.$record-spacing-map, 'row-focus-border-width') solid map-get(variables.$color-map, 'primary') !important;
+ border: map.get(variables.$record-spacing-map, 'row-focus-border-width') solid map.get(variables.$color-map, 'primary') !important;
}
.chaise-table-header {
diff --git a/src/assets/scss/_recordedit.scss b/src/assets/scss/_recordedit.scss
index 534e189ea..936be492e 100644
--- a/src/assets/scss/_recordedit.scss
+++ b/src/assets/scss/_recordedit.scss
@@ -51,9 +51,9 @@
.entity-key {
width: variables.$chaise-caption-column-width;
- border-left: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
- border-right: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
- border-bottom: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
+ border-left: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
+ border-right: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
+ border-bottom: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
// margin-left: 10px;
background: white;
padding: 8px;
@@ -89,8 +89,8 @@
position: relative;
display: flex;
flex-direction: column;
- color: map-get(variables.$color-map, 'black');
- background-color: map-get(variables.$color-map, 'white');
+ color: map.get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'white');
min-width: 250px;
overflow-x: auto;
@@ -119,7 +119,7 @@
max-width: 100%;
&.highlighted-row {
- background-color: map-get(variables.$color-map, 'recordedit-highlighted-row');
+ background-color: map.get(variables.$color-map, 'recordedit-highlighted-row');
// reset the changed height when the multi form input row is open
.inputs-row {
@@ -161,8 +161,8 @@
.inline-comment-row {
flex-direction: column;
- color: map-get(variables.$color-map, 'column-inline-tooltip');
- border-right: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
+ color: map.get(variables.$color-map, 'column-inline-tooltip');
+ border-right: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
padding: 8px 10px 0 8px;
@@ -180,7 +180,7 @@
flex-direction: column;
align-items: center;
- z-index: map-get(variables.$z-index-map, 'multi-form-input-row');
+ z-index: map.get(variables.$z-index-map, 'multi-form-input-row');
// Adding a div to align the content center always
.center-align {
@@ -215,7 +215,7 @@
}
.multi-form-input-how-to {
cursor: default;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
}
.multi-form-input-how-to:hover {
@@ -301,13 +301,13 @@
top: 0;
height: 100%;
width: 100%;
- z-index: map-get(variables.$z-index-map, 'recordedit-foreignkey-input-spinner-backdrop')
+ z-index: map.get(variables.$z-index-map, 'recordedit-foreignkey-input-spinner-backdrop')
}
.column-permission-warning {
margin: 0;
padding: 5px;
- color: map-get(variables.$color-map, 'recordedit-column-permission-warning');
+ color: map.get(variables.$color-map, 'recordedit-column-permission-warning');
}
}
@@ -315,8 +315,8 @@
.match-entity-value {
word-wrap: break-word;
min-width: 250px;
- border-right: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
- border-bottom: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
+ border-right: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
+ border-bottom: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
// border-left: none;
// border-top: none;
@@ -328,7 +328,7 @@
// This is added to show the form is selected to apply the change
.entity-active {
- outline: 2px solid map-get(variables.$color-map, 'primary');
+ outline: 2px solid map.get(variables.$color-map, 'primary');
outline-offset: -2px;
}
@@ -336,15 +336,15 @@
.column-cell {
border-left: none;
border-top: none;
- border-right: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
- border-bottom: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
+ border-right: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
+ border-bottom: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
border-radius: 0;
// height: 47px;
min-height: 47px;
padding: 8px 10px;
.chaise-input-control.column-cell-input {
- border: 1px solid map-get(variables.$color-map, 'border');
+ border: 1px solid map.get(variables.$color-map, 'border');
border-radius: 4px;
padding: 0 10px;
display: flex;
@@ -360,8 +360,8 @@
// in both entity-key-column and form
.form-header {
height: 47px;
- border-top: variables.$chaise-RE-border-width solid map-get(variables.$color-map, 'recordedit-border');
- color: map-get(variables.$color-map, 'disabled');
+ border-top: variables.$chaise-RE-border-width solid map.get(variables.$color-map, 'recordedit-border');
+ color: map.get(variables.$color-map, 'disabled');
.form-header-buttons-container {
float: right;
@@ -387,11 +387,11 @@
.record-number {
font-size: 14px;
margin-left: 10px;
- color: map-get(variables.$color-map, 'disabled');
+ color: map.get(variables.$color-map, 'disabled');
}
.chaise-input-control[readonly] {
- background-color: map-get(variables.$color-map, 'white');
+ background-color: map.get(variables.$color-map, 'white');
opacity: 1;
}
diff --git a/src/assets/scss/_recordset-table.scss b/src/assets/scss/_recordset-table.scss
index 9c7a86047..9276a2a76 100644
--- a/src/assets/scss/_recordset-table.scss
+++ b/src/assets/scss/_recordset-table.scss
@@ -6,8 +6,8 @@
}
.chaise-table.table {
- border-top: 1px solid map-get(variables.$color-map, 'table-border');
- border-bottom: 1px solid map-get(variables.$color-map, 'table-border');
+ border-top: 1px solid map.get(variables.$color-map, 'table-border');
+ border-bottom: 1px solid map.get(variables.$color-map, 'table-border');
margin: 0;
// override the default bootstrap styles as we're going to apply our own styles below
@@ -23,7 +23,7 @@
/* table hover */
> tr:hover {
- background-color: map-get(variables.$color-map, 'table-highlight-background') !important;
+ background-color: map.get(variables.$color-map, 'table-highlight-background') !important;
.hover-show {
visibility: visible;
@@ -32,18 +32,18 @@
/* odd row hover for striped table */
> tr:nth-child(odd):hover {
- background-color: map-get(variables.$color-map, 'table-striped-highlight-background') !important;
+ background-color: map.get(variables.$color-map, 'table-striped-highlight-background') !important;
}
}
tr {
- border-left: 1px solid map-get(variables.$color-map, 'table-border');
+ border-left: 1px solid map.get(variables.$color-map, 'table-border');
border-bottom: none;
}
.table-heading {
- background-color: map-get(variables.$color-map, 'table-header-background');
- color: map-get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'table-header-background');
+ color: map.get(variables.$color-map, 'black');
.actions-header {
width: 100px;
@@ -78,11 +78,11 @@
}
& > tbody > tr:nth-child(odd) {
- background: map-get(variables.$color-map, 'table-striped-background');
+ background: map.get(variables.$color-map, 'table-striped-background');
}
> thead > tr > th {
- border-bottom: 1px solid map-get(variables.$color-map, 'table-border');
+ border-bottom: 1px solid map.get(variables.$color-map, 'table-border');
white-space: nowrap;
}
@@ -94,7 +94,7 @@
> tfoot > tr > td {
border-top: none;
border-bottom: none;
- border-right: 1px solid map-get(variables.$color-map, 'table-border');
+ border-right: 1px solid map.get(variables.$color-map, 'table-border');
word-wrap: break-word;
}
@@ -105,7 +105,7 @@
th {
font-weight: 400;
- border-top: 1px solid map-get(variables.$color-map, 'table-border');
+ border-top: 1px solid map.get(variables.$color-map, 'table-border');
position: relative;
&.actions-header {
@@ -136,7 +136,7 @@
.spinner-border {
// NOTE: we should not customize the width/height since it causes wobbling effect
- color: map-get(variables.$color-map, 'table-header-spinner');
+ color: map.get(variables.$color-map, 'table-header-spinner');
border-width: .16em;
}
}
@@ -193,7 +193,7 @@
font-size: 1.6rem;
// added here instead of to the button as padding-top to only affect these buttons/icons
margin-top: 1px;
- background-color: map-get(variables.$color-map, 'white') !important;
+ background-color: map.get(variables.$color-map, 'white') !important;
border-radius: inherit;
}
}
@@ -227,11 +227,11 @@
.disabled {
cursor: not-allowed;
- color: map-get(variables.$color-map, 'disabled');
+ color: map.get(variables.$color-map, 'disabled');
}
.delete-loader {
- color: map-get(variables.$color-map, 'disabled');
+ color: map.get(variables.$color-map, 'disabled');
}
}
diff --git a/src/assets/scss/_recordset.scss b/src/assets/scss/_recordset.scss
index 22c8fa7a4..77efe15e7 100644
--- a/src/assets/scss/_recordset.scss
+++ b/src/assets/scss/_recordset.scss
@@ -57,11 +57,11 @@
}
.chaise-btn:focus {
- background-color: map-get(variables.$color-map, 'white');
+ background-color: map.get(variables.$color-map, 'white');
}
.chaise-btn.chaise-btn-secondary {
- border-color: map-get(variables.$color-map, 'border');
+ border-color: map.get(variables.$color-map, 'border');
height: 25px;
min-width: 25px;
padding: 2px 5px;
@@ -104,12 +104,12 @@
}
span {
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
cursor: default;
}
button span {
- color: map-get(variables.$color-map, 'primary');
+ color: map.get(variables.$color-map, 'primary');
cursor: pointer;
}
}
@@ -159,7 +159,7 @@
}
.modal-header-context {
- color: map-get(variables.$color-map, 'modal-header-context');
+ color: map.get(variables.$color-map, 'modal-header-context');
.modal-header-context-separator {
padding: 0 0.3em;
diff --git a/src/assets/scss/_split-view.scss b/src/assets/scss/_split-view.scss
index 6e02780c2..2a346a911 100644
--- a/src/assets/scss/_split-view.scss
+++ b/src/assets/scss/_split-view.scss
@@ -7,11 +7,11 @@
display: flex;
align-items: center;
top: 50%;
- color: map-get(variables.$color-map, 'split-view-divider');
+ color: map.get(variables.$color-map, 'split-view-divider');
left: 2px;
margin-right: -20px;
width: 20px;
- z-index: map-get(variables.$z-index-map, 'split-view-divider');
+ z-index: map.get(variables.$z-index-map, 'split-view-divider');
span {
padding-left: 3px;
diff --git a/src/assets/scss/_table-header.scss b/src/assets/scss/_table-header.scss
index b1ac6b737..a67ef5310 100644
--- a/src/assets/scss/_table-header.scss
+++ b/src/assets/scss/_table-header.scss
@@ -50,7 +50,7 @@
}
.chaise-table-header-spinner {
- color: map-get(variables.$color-map, 'spinner');
+ color: map.get(variables.$color-map, 'spinner');
margin-left: 4px;
}
}
diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss
index 94b25a949..be9cb5f13 100644
--- a/src/assets/scss/_variables.scss
+++ b/src/assets/scss/_variables.scss
@@ -50,9 +50,17 @@ $h4-font-size: 1.25rem;
/**
* map syntax doesn't allow multiline, so we have to have them in separate files.
+ * NOTE: these lines used to use @import, but since it was deprecated I had to rewrite them with @use.
+ * but that broke the way we're using our variables, so I had to rename them with the second lines.
*/
- @import 'maps/record-page-spacing';
- @import 'maps/chaise-accordion';
- @import 'maps/z-index-map';
- @import 'maps/color-map';
+@use 'maps/record-page-spacing';
+$record-spacing-map: record-page-spacing.$map;
+@use 'maps/chaise-accordion';
+$chaise-accordion-spacing-map: chaise-accordion.$map;
+
+@use 'maps/z-index-map';
+$z-index-map: z-index-map.$map;
+
+@use 'maps/color-map';
+$color-map: color-map.$map;
diff --git a/src/assets/scss/_viewer.scss b/src/assets/scss/_viewer.scss
index 13513a3f9..30e81dedd 100644
--- a/src/assets/scss/_viewer.scss
+++ b/src/assets/scss/_viewer.scss
@@ -71,7 +71,7 @@
left: 0;
height: 100%;
width: 100%;
- background-color: map-get(variables.$color-map, 'disabled-background');
+ background-color: map.get(variables.$color-map, 'disabled-background');
opacity: 0.5;
z-index: 9; // higher than form
}
@@ -98,7 +98,7 @@
.stroke-value {
padding: 0 5px;
- background: map-get(variables.$color-map, 'viewer-annotation-stroke-slider-value');
+ background: map.get(variables.$color-map, 'viewer-annotation-stroke-slider-value');
border-radius: 3px;
font-size: 12px;
}
@@ -110,7 +110,7 @@
width: 100%;
height: 4px;
margin-top: 5px;
- background: map-get(variables.$color-map, 'viewer-annotation-stroke-slider-input');
+ background: map.get(variables.$color-map, 'viewer-annotation-stroke-slider-input');
outline: none;
opacity: 0.7;
-webkit-transition: 0.2s;
@@ -127,14 +127,14 @@
width: 10px;
height: 10px;
border-radius: 3px;
- background: map-get(variables.$color-map, 'primary');
+ background: map.get(variables.$color-map, 'primary');
cursor: pointer;
}
&::-moz-range-thumb {
width: 10px;
height: 10px;
- background: map-get(variables.$color-map, 'primary');
+ background: map.get(variables.$color-map, 'primary');
cursor: pointer;
}
}
@@ -151,7 +151,7 @@
justify-content: center;
text-align: center;
width: 1px;
- background: map-get(variables.$color-map, 'viewer-annotation-stroke-slider-tick');
+ background: map.get(variables.$color-map, 'viewer-annotation-stroke-slider-tick');
height: 5px;
line-height: 25px;
font-size: 10px;
@@ -161,13 +161,13 @@
}
.annotation-form-container {
- border: 1px solid map-get(variables.$color-map, 'border');
+ border: 1px solid map.get(variables.$color-map, 'border');
border-radius: 3px;
- background: map-get(variables.$color-map, 'viewer-annotation-form-background');
+ background: map.get(variables.$color-map, 'viewer-annotation-form-background');
padding: 10px;
.drawing-hint {
- background: map-get(variables.$color-map, 'warning-background');
+ background: map.get(variables.$color-map, 'warning-background');
width: 100%;
padding: 5px;
margin: 5px 0;
@@ -179,7 +179,7 @@
}
.viewer-annotation-form-row {
- border-bottom: 1px solid map-get(variables.$color-map, 'border');
+ border-bottom: 1px solid map.get(variables.$color-map, 'border');
padding: 10px 0;
.viewer-annotation-form-row-header {
@@ -216,7 +216,7 @@
}
.no-annotation-message {
- background: map-get(variables.$color-map, 'table-striped-background');
+ background: map.get(variables.$color-map, 'table-striped-background');
padding: 8px 5px;
min-height: 39px;
text-align: center;
@@ -228,26 +228,26 @@
padding: 8px 5px;
&.highlighted {
- background: map-get(variables.$color-map, 'primary-hover');
- color: map-get(variables.$color-map, 'white');
+ background: map.get(variables.$color-map, 'primary-hover');
+ color: map.get(variables.$color-map, 'white');
a,
.chaise-btn {
- color: map-get(variables.$color-map, 'white');
+ color: map.get(variables.$color-map, 'white');
}
}
&:not(.highlighted) {
&:hover {
- background: map-get(variables.$color-map, 'table-highlight-background');
+ background: map.get(variables.$color-map, 'table-highlight-background');
}
.annotation-row-btn:hover {
- color: map-get(variables.$color-map, 'primary-hover');
+ color: map.get(variables.$color-map, 'primary-hover');
}
&:nth-child(odd) {
- background-color: map-get(variables.$color-map, 'table-striped-background');
+ background-color: map.get(variables.$color-map, 'table-striped-background');
&:hover {
- background: map-get(variables.$color-map, 'table-striped-highlight-background');
+ background: map.get(variables.$color-map, 'table-striped-highlight-background');
}
}
}
diff --git a/src/assets/scss/app.scss b/src/assets/scss/app.scss
index 30c3525a7..7d181ece8 100644
--- a/src/assets/scss/app.scss
+++ b/src/assets/scss/app.scss
@@ -3,13 +3,13 @@
@use 'helpers';
// globally used
-@import "chaise-icon";
-@import "markdown-container";
-@import "buttons";
-@import "button-group";
-@import "inputs";
-@import "dropdown";
-@import "modal";
+@use "chaise-icon";
+@use "markdown-container";
+@use "buttons";
+@use "button-group";
+@use "inputs";
+@use "dropdown";
+@use "modal";
// if we want to hide the page and then show navbar and content together
.wait-for-navbar {
@@ -43,8 +43,8 @@ html {
height: 100%;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande",
sans-serif;
- background-color: map-get(variables.$color-map, 'white');
- color: map-get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'white');
+ color: map.get(variables.$color-map, 'black');
font-size: variables.$font-size;
font-weight: 400;
line-height: 1.428;
@@ -110,7 +110,7 @@ html {
.h4-class small {
font-weight: 400;
line-height: 1;
- color: map-get(variables.$color-map, 'header-small');
+ color: map.get(variables.$color-map, 'header-small');
font-size: 65%;
}
@@ -131,7 +131,7 @@ html {
a,
a:not([href]):not([class]) {
cursor: pointer;
- color: map-get(variables.$color-map, 'link');
+ color: map.get(variables.$color-map, 'link');
text-decoration: none;
}
// the last selector is added to override bootstrap default behavior
@@ -145,8 +145,8 @@ html {
code {
padding: 2px 4px;
font-size: 90%;
- color: map-get(variables.$color-map, 'code-block');
- background-color: map-get(variables.$color-map, 'code-block-background');
+ color: map.get(variables.$color-map, 'code-block');
+ background-color: map.get(variables.$color-map, 'code-block-background');
border-radius: 4px;
}
// make sure the pre style is consistent with old bootstrap styles
@@ -156,11 +156,11 @@ html {
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
word-break: break-all;
word-wrap: break-word;
- background-color: map-get(variables.$color-map, 'code-block-background-alt');
- border: 1px solid map-get(variables.$color-map, 'border');
+ background-color: map.get(variables.$color-map, 'code-block-background-alt');
+ border: 1px solid map.get(variables.$color-map, 'border');
border-radius: 4px;
}
@@ -255,15 +255,15 @@ html {
}
.tooltip code,
.tooltip-inner code {
- background: map-get(variables.$color-map, 'code-block-background-in-tooltip');
- color: map-get(variables.$color-map, 'white');
+ background: map.get(variables.$color-map, 'code-block-background-in-tooltip');
+ color: map.get(variables.$color-map, 'white');
padding: 0.5px;
}
// disabled row and form
.disabled-form,
.disabled-row {
- color: map-get(variables.$color-map, 'disabled') !important;
+ color: map.get(variables.$color-map, 'disabled') !important;
}
.disabled-form *,
@@ -273,7 +273,7 @@ html {
.disabled-form a,
.disabled-row a {
- color: map-get(variables.$color-map, 'disabled') !important;
+ color: map.get(variables.$color-map, 'disabled') !important;
}
.disabled-form .remove-input-btn {
@@ -288,8 +288,8 @@ html {
}
.disabled-form .btn.btn-inverted[disabled]:hover {
- background-color: map-get(variables.$color-map, 'white') !important;
- border-color: map-get(variables.$color-map, 'border') !important;
+ background-color: map.get(variables.$color-map, 'white') !important;
+ border-color: map.get(variables.$color-map, 'border') !important;
}
.disabled-element {
@@ -302,13 +302,13 @@ html {
// spinner
.spinner-container:not(.bottom-left-spinner) {
- background: map-get(variables.$color-map, 'white');
- color: map-get(variables.$color-map, 'spinner');
+ background: map.get(variables.$color-map, 'white');
+ color: map.get(variables.$color-map, 'spinner');
width: 130px;
margin: 0 auto;
text-align: center;
padding: 14px 0;
- border: 1px solid map-get(variables.$color-map, 'border');
+ border: 1px solid map.get(variables.$color-map, 'border');
border-radius: 7px;
&:not(.manual-position-spinner) {
top: 50vh;
@@ -316,22 +316,22 @@ html {
left: 50%;
margin-top: -75px;
margin-left: -75px;
- z-index: map-get(variables.$z-index-map, 'spinner');
+ z-index: map.get(variables.$z-index-map, 'spinner');
}
.spinner-message {
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
margin-top: 15px;
}
}
.spinner-container.bottom-left-spinner {
float: left;
position: fixed;
- z-index: map-get(variables.$z-index-map, 'spinner');
+ z-index: map.get(variables.$z-index-map, 'spinner');
bottom: 0;
- background: map-get(variables.$color-map, 'spinner-background');
+ background: map.get(variables.$color-map, 'spinner-background');
margin: 5px;
padding: 5px 20px;
- border: solid map-get(variables.$color-map, 'spinner-border') 1.5px;
+ border: solid map.get(variables.$color-map, 'spinner-border') 1.5px;
.spinner-message {
display: inline-block;
margin-left: 5px;
@@ -347,7 +347,7 @@ html {
position: sticky;
position: -webkit-sticky;
top: min(50px, 50%);
- z-index: map-get(variables.$z-index-map, 'spinner');
+ z-index: map.get(variables.$z-index-map, 'spinner');
.spinner-container {
height: variables.$main-spinner-height;
}
@@ -373,7 +373,7 @@ html {
margin: 0;
.link-decoration {
- color: map-get(variables.$color-map, 'link');
+ color: map.get(variables.$color-map, 'link');
}
}
@@ -411,7 +411,7 @@ html {
// record and recordedit column headers
.entity-key {
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
font-weight: normal;
font-size: 1rem;
word-wrap: break-word;
@@ -420,7 +420,7 @@ html {
// record and recordedit column values
.entity-value {
font-size: 1rem;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
}
// show more/show less ellipsis
@@ -460,11 +460,11 @@ html {
.footer-content {
height: 30px;
- background-color: map-get(variables.$color-map, 'footer-background');
+ background-color: map.get(variables.$color-map, 'footer-background');
padding-right: 5px;
padding-left: 5px;
padding-top: 5px;
- color: map-get(variables.$color-map, 'footer');
+ color: map.get(variables.$color-map, 'footer');
font-size: 11px;
li {
@@ -482,8 +482,8 @@ html {
code {
padding-left: 0;
padding-right: 0;
- color: map-get(variables.$color-map, 'black');
- background-color: map-get(variables.$color-map, 'code-block-background-alt');
+ color: map.get(variables.$color-map, 'black');
+ background-color: map.get(variables.$color-map, 'code-block-background-alt');
}
}
}
@@ -691,7 +691,7 @@ html {
// copy to clipboard btn (used in share dialog)
.chaise-copy-to-clipboard-btn {
- color: map-get(variables.$color-map, 'primary');
+ color: map.get(variables.$color-map, 'primary');
font-size: 65%;
cursor: pointer;
}
@@ -717,7 +717,7 @@ html {
appearance: none;
position: absolute;
- z-index: map-get(variables.$z-index-map, 'checkbox-input');
+ z-index: map.get(variables.$z-index-map, 'checkbox-input');
cursor: pointer;
width: variables.$chaise-checkbox-width;
height: variables.$chaise-checkbox-height;
@@ -729,15 +729,15 @@ html {
// we have to attach the checkmark to the label otherwise firefox won't show it.
& + label:before,
&:checked + label:after {
- color: map-get(variables.$color-map, 'disabled');
- border-color: map-get(variables.$color-map, 'disabled');
+ color: map.get(variables.$color-map, 'disabled');
+ border-color: map.get(variables.$color-map, 'disabled');
}
}
// we have to attach the checkmark to the label otherwise firefox won't show it.
& + label:before,
&:checked + label:after {
- color: map-get(variables.$color-map, 'primary');
+ color: map.get(variables.$color-map, 'primary');
-webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
-o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
@@ -752,9 +752,9 @@ html {
height: variables.$chaise-checkbox-height;
left: 0;
top: 0;
- border: 1px solid map-get(variables.$color-map, 'primary');
+ border: 1px solid map.get(variables.$color-map, 'primary');
border-radius: 3px;
- background-color: map-get(variables.$color-map, 'white');
+ background-color: map.get(variables.$color-map, 'white');
}
&:indeterminate + label:after {
@@ -797,7 +797,7 @@ html {
font-size: 18px;
&.fa-solid.fa-star {
- color: map-get(variables.$color-map, 'favorite');
+ color: map.get(variables.$color-map, 'favorite');
}
&.hover-show {
@@ -819,13 +819,13 @@ html {
// changes the color of alert fa-triangle-exclamation (used for timeout error)
.fa-triangle-exclamation {
- color: map-get(variables.$color-map, 'warning') !important;
+ color: map.get(variables.$color-map, 'warning') !important;
border: none;
}
// spinner
.fa-circle-notch {
- color: map-get(variables.$color-map, 'spinner');
+ color: map.get(variables.$color-map, 'spinner');
}
/* Rule for Chrome */
@@ -855,7 +855,7 @@ html {
position: sticky;
width: -moz-available;
width: -webkit-fill-available;
- z-index: map-get(variables.$z-index-map, 'top-horzontal-scroll-wrapper');
+ z-index: map.get(variables.$z-index-map, 'top-horzontal-scroll-wrapper');
display: none;
}
@@ -881,7 +881,7 @@ html {
display: inline-block;
cursor: pointer;
padding-right: 3px;
- color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'black');
font-size: 1.3em;
vertical-align: middle;
}
@@ -917,8 +917,8 @@ html {
// section accordion (used both in record and recordedit)
.chaise-accordions {
- $_space-between-accordions: map-get(variables.$chaise-accordion-spacing-map, 'space-between');
- $_row-focus-border-width: map-get(variables.$chaise-accordion-spacing-map, 'row-focus-border-width');
+ $_space-between-accordions: map.get(variables.$chaise-accordion-spacing-map, 'space-between');
+ $_row-focus-border-width: map.get(variables.$chaise-accordion-spacing-map, 'row-focus-border-width');
.panel-group {
margin-bottom: 20px;
@@ -961,11 +961,11 @@ html {
cursor: pointer;
height: 44px;
text-transform: inherit;
- padding: map-get(variables.$chaise-accordion-spacing-map, 'header-padding');
- color: map-get(variables.$color-map, 'white');
+ padding: map.get(variables.$chaise-accordion-spacing-map, 'header-padding');
+ color: map.get(variables.$color-map, 'white');
border: none;
border-radius: inherit;
- background: map-get(variables.$color-map, 'section-background');
+ background: map.get(variables.$color-map, 'section-background');
// Overriding accordian-button css to align right angle bracket icon to left
flex-direction: row-reverse;
justify-content: flex-end;
@@ -1028,9 +1028,9 @@ html {
.accordion-body {
// make sure content is alidned with header on the left
- padding-left: map-get(variables.$chaise-accordion-spacing-map, 'body-padding-left');
+ padding-left: map.get(variables.$chaise-accordion-spacing-map, 'body-padding-left');
// make sure content is aligned with header on the right.
- padding-right: map-get(variables.$chaise-accordion-spacing-map, 'header-padding');
+ padding-right: map.get(variables.$chaise-accordion-spacing-map, 'header-padding');
padding-top: 10px;
}
@@ -1052,7 +1052,7 @@ html {
width: 35px;
cursor: pointer;
overflow: hidden;
- z-index: map-get(variables.$z-index-map, 'back-to-top-btn');
+ z-index: map.get(variables.$z-index-map, 'back-to-top-btn');
}
// an spinner with backdrop blocking the whole app content
@@ -1063,13 +1063,13 @@ html {
left: 0;
height: 100%;
width: 100%;
- z-index: map-get(variables.$z-index-map, 'app-blocking-spinner-backdrop');
+ z-index: map.get(variables.$z-index-map, 'app-blocking-spinner-backdrop');
background: black;
opacity: 0.5;
}
.spinner-container {
top: 50% !important;
- z-index: map-get(variables.$z-index-map, 'app-blocking-spinner') !important;
+ z-index: map.get(variables.$z-index-map, 'app-blocking-spinner') !important;
}
}
@@ -1098,7 +1098,7 @@ html {
position: relative;
display: block;
- border: 1px solid map-get(variables.$color-map, 'border');
+ border: 1px solid map.get(variables.$color-map, 'border');
text-align: center;
/**
@@ -1195,5 +1195,5 @@ html {
// change default bootstap variables
:root {
// interpolation (#{}) is needed here, more info: https://sass-lang.com/documentation/breaking-changes/css-vars/
- --bs-body-color: #{map-get(variables.$color-map, 'black')};
+ --bs-body-color: #{map.get(variables.$color-map, 'black')};
}
diff --git a/src/assets/scss/helpers.scss b/src/assets/scss/helpers.scss
index 264bd6b34..d79d03392 100644
--- a/src/assets/scss/helpers.scss
+++ b/src/assets/scss/helpers.scss
@@ -1,3 +1,4 @@
+@use "sass:color";
@use 'sass:map';
@use 'variables';
@@ -38,7 +39,7 @@
}
@mixin download-btn() {
- color: map-get(variables.$color-map, 'primary');
+ color: map.get(variables.$color-map, 'primary');
white-space: nowrap;
// touch action
-ms-touch-action: manipulation;
@@ -99,23 +100,23 @@
}
@mixin chaise-btn-primary() {
- color: map-get(variables.$color-map, 'white');
- background-color: map-get(variables.$color-map, 'primary');
- border-color: map-get(variables.$color-map, 'black');
+ color: map.get(variables.$color-map, 'white');
+ background-color: map.get(variables.$color-map, 'primary');
+ border-color: map.get(variables.$color-map, 'black');
&:focus {
- background-color: darken(map-get(variables.$color-map, 'primary'), 10%);
+ background-color: color.adjust(map.get(variables.$color-map, 'primary'), $lightness: -10%);
box-shadow: none;
}
}
@mixin chaise-btn-secondary {
- color: map-get(variables.$color-map, 'primary');
- background-color: map-get(variables.$color-map, 'white');
- border: 1px solid map-get(variables.$color-map, 'primary');
+ color: map.get(variables.$color-map, 'primary');
+ background-color: map.get(variables.$color-map, 'white');
+ border: 1px solid map.get(variables.$color-map, 'primary');
&:focus {
- background-color: darken(map-get(variables.$color-map, 'white'), 10%);
+ background-color: color.adjust(map.get(variables.$color-map, 'white'), $lightness: -10%);
box-shadow: none;
}
}
diff --git a/src/assets/scss/maps/_chaise-accordion.scss b/src/assets/scss/maps/_chaise-accordion.scss
index 64d0e4829..d2cb57074 100644
--- a/src/assets/scss/maps/_chaise-accordion.scss
+++ b/src/assets/scss/maps/_chaise-accordion.scss
@@ -1,4 +1,4 @@
-$chaise-accordion-spacing-map: (
+$map: (
'space-between': 5px,
'row-focus-border-width': 2px,
'body-padding-left': 40px,
diff --git a/src/assets/scss/maps/_color-map.scss b/src/assets/scss/maps/_color-map.scss
index cfcdd99dc..483afeb7f 100644
--- a/src/assets/scss/maps/_color-map.scss
+++ b/src/assets/scss/maps/_color-map.scss
@@ -1,4 +1,4 @@
-$color-map: (
+$map: (
'black': #333,
'border': #ccc,
'code-block': #c7254e,
diff --git a/src/assets/scss/maps/_record-page-spacing.scss b/src/assets/scss/maps/_record-page-spacing.scss
index b0f71d3f1..405140ddd 100644
--- a/src/assets/scss/maps/_record-page-spacing.scss
+++ b/src/assets/scss/maps/_record-page-spacing.scss
@@ -1,4 +1,4 @@
-$record-spacing-map: (
+$map: (
'record-table-cell-padding': 5px,
'record-table-cell-border-width': 1px,
'space-between-related-sections': 5px,
diff --git a/src/assets/scss/maps/_z-index-map.scss b/src/assets/scss/maps/_z-index-map.scss
index 4af51ac74..cac8baecd 100644
--- a/src/assets/scss/maps/_z-index-map.scss
+++ b/src/assets/scss/maps/_z-index-map.scss
@@ -1,5 +1,5 @@
// z-index
-$z-index-map: (
+$map: (
// make sure it's above the displayed checkbox, so we can click on it
'checkbox-input': 1,
'button-active': 1,
diff --git a/webpack/app.config.js b/webpack/app.config.js
index 806c6f9f4..6776a2aff 100644
--- a/webpack/app.config.js
+++ b/webpack/app.config.js
@@ -28,6 +28,8 @@ const webpack = require('webpack');
* if missing, we will use CHAISE_BASE_PATH.
* - rootFolderLocation: the location of root folder. used to find the code (e.g. ../src)
* - resolveAliases: the aliases that will be used in import statements.
+ * - extraWebpackProps: props that you want to directly pass to webpack. be mindful what you're passing as you might break the configuration
+ * by overriding the props that we already rely on. Currently only used for passing externals (https://webpack.js.org/configuration/externals/)
*
* @param {Object} appConfigs
* the app configurations
@@ -55,6 +57,10 @@ const getWebPackConfig = (appConfigs, mode, env, options) => {
options.resolveAliases = {};
}
+ if (!options.extraWebpackProps || typeof options.extraWebpackProps !== 'object') {
+ options.extraWebpackProps = {};
+ }
+
const entries = {}, appHTMLPlugins = [];
// define entries and add plugins based on appConfigs
@@ -256,16 +262,13 @@ const getWebPackConfig = (appConfigs, mode, env, options) => {
},
},
},
- externals: {
- // treat plotly as an external dependency and don't compute it
- 'plotly.js-basic-dist-min': 'Plotly'
- },
performance: {
assetFilter: (assetFilename) => {
// ignore the fonts
return !/\.(woff(2)?|eot|ttf|otf|svg|)$/.test(assetFilename);
}
- }
+ },
+ ...options.extraWebpackProps
}
}
diff --git a/webpack/main.config.js b/webpack/main.config.js
index 0e026969e..79c0c6b46 100644
--- a/webpack/main.config.js
+++ b/webpack/main.config.js
@@ -66,6 +66,14 @@ module.exports = (env) => {
}
],
mode,
- env
+ env,
+ {
+ extraWebpackProps: {
+ externals: {
+ // treat plotly as an external dependency and don't compute it
+ 'plotly.js-basic-dist-min': 'Plotly'
+ }
+ }
+ }
);
}
From 78f26cd8def9bd2fb84d385b294982e62332add6 Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Thu, 7 Nov 2024 18:00:15 -0800
Subject: [PATCH 26/30] bump chaise version
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3e70fd2e3..e02308a68 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@isrd-isi-edu/chaise",
- "version": "0.0.23",
+ "version": "0.0.24",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@isrd-isi-edu/chaise",
- "version": "0.0.23",
+ "version": "0.0.24",
"license": "Apache-2.0",
"dependencies": {
"@babel/core": "7.24.x",
diff --git a/package.json b/package.json
index e7eaed0ae..fe9d0033c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@isrd-isi-edu/chaise",
"description": "An adaptive User Interface for ERMrest data sources",
- "version": "0.0.23",
+ "version": "0.0.24",
"license": "Apache-2.0",
"engines": {
"node": ">= 18.18.0 || >= 20.0.0",
From a00aa8d837bf0dcc0b45d2694269e544bc2e3f12 Mon Sep 17 00:00:00 2001
From: Aref Shafaei
Date: Wed, 20 Nov 2024 17:29:35 -0800
Subject: [PATCH 27/30] change the default behavior of deleteRecord
chaise-config property (#2582)
---
config/chaise-config-sample.js | 21 ++++++----------
docs/user-docs/chaise-config.md | 10 ++++----
src/components/record/record.tsx | 2 +-
src/components/recordedit/recordedit.tsx | 4 ++--
src/pages/recordset.tsx | 6 ++---
src/utils/constants.ts | 3 ++-
src/utils/record-utils.ts | 7 +-----
.../specs/default-config/record/links.spec.ts | 24 +++++++++++++++++--
test/e2e/utils/page-utils.ts | 2 +-
9 files changed, 43 insertions(+), 36 deletions(-)
diff --git a/config/chaise-config-sample.js b/config/chaise-config-sample.js
index e2360e6fd..ba68ab2ff 100644
--- a/config/chaise-config-sample.js
+++ b/config/chaise-config-sample.js
@@ -1,26 +1,19 @@
// Configure deployment-specific data here
+// for more info: https://github.com/informatics-isi-edu/chaise/blob/master/docs/user-docs/chaise-config.md
var chaiseConfig = {
- name: "Sample",
defaultCatalog: "1",
resolverImplicitCatalog: "1",
- allowErrorDismissal: true,
- confirmDelete: true,
headTitle: 'Chaise',
customCSS: '/assets/css/chaise.css',
navbarBrand: '/',
- navbarBrandImage: null,
- logoutURL: '/image-annotation',
// signUpURL: '', The URL at a which a user can create a new account
- dataBrowser: '',
shareCite: {
acls: {
show: ["*"], // [] <- hide
enable: ["*"] // [] <- disable
}
},
- editRecord: true,
- deleteRecord: true,
maxRecordsetRowHeight: 160,
navbarBanner: [
{
@@ -89,16 +82,16 @@ var chaiseConfig = {
footerMarkdown: "**Please check** [Privacy Policy](/privacy-policy/){target='_blank'}",
configRules: [
{
- host: ["www.rebuildingakidney.org", "staging.rebuildingakidney.org", "dev.rebuildingakidney.org"], // array of host names
+ host: ["www.example.org", "staging.example.org", "dev.example.org"], // array of host names
config: {
- headTitle: "RBK/GUDMAP",
- navbarBrand: "/resources/"
+ headTitle: "Example",
+ navbarBrandText: "Example website"
}
}, {
- host: ["www.gudmap.org", "staging.gudmap.org", "dev.gudmap.org"], // array of host names
+ host: ["www.example-2.org", "staging.examle-2.org", "dev.example-2.org"], // array of host names
config: {
- headTitle: "GUDMAP/RBK",
- navbarBrand: "/"
+ headTitle: "Example 2",
+ navbarBrandText: "Example 2 website"
}
}
],
diff --git a/docs/user-docs/chaise-config.md b/docs/user-docs/chaise-config.md
index a0b66231e..2958d50a9 100644
--- a/docs/user-docs/chaise-config.md
+++ b/docs/user-docs/chaise-config.md
@@ -409,21 +409,21 @@ If a property appears in the same configuration twice, the property defined late
```
#### editRecord
- If not present or equal to `true`, the recordedit page allows for inserting records and editing records. The record page will have an edit button for both of these cases as well. If equal to `false`, a dialog appears on recordedit that disallows use of the app for both create and edit, and the create/edit button does not appear in the record app.
+If present and equal to `false`, the chaise pages will hide all the edit and create buttons regardless of user ACLs. Chaise will also disallow users from accessing recordedit app in this case.
- Type: Boolean
- - Default behavior: Allows for inserting and editing records through the recordedit page
+ - Default behavior: Allows for inserting and editing records through the recordedit page if the user has proper ACLs.
- Sample syntax:
```
editRecord: false
```
#### deleteRecord
- If present and equal to `true`, the recordedit page will show delete button if editRecord is also true, and record page will show delete button if this is true. Otherwise, hide delete buttons.
+ If present and equal to `false`, the chaise pages will hide all the delete buttons regardless of user ACLs. Otherwise chaise will consult the user ACL for conditonally hiding or showing delete buttons.
- Type: Boolean
- - Default behavior: recordset and record page will not show a delete buttons
+ - Default behavior: Chaise pages display delete buttons based on user ACLs.
- Sample syntax:
```
- deleteRecord: true
+ deleteRecord: false
```
#### allowErrorDismissal
diff --git a/src/components/record/record.tsx b/src/components/record/record.tsx
index ae5fc58c3..4d86bb423 100644
--- a/src/components/record/record.tsx
+++ b/src/components/record/record.tsx
@@ -380,7 +380,7 @@ const RecordInner = ({
const canCreate = reference.canCreate && modifyRecord;
const canEdit = tuple.canUpdate && modifyRecord;
- const canDelete = tuple.canDelete && modifyRecord && ConfigService.chaiseConfig.deleteRecord === true;
+ const canDelete = tuple.canDelete && ConfigService.chaiseConfig.deleteRecord !== false;
const copyRecord = () => {
const appLink = reference.contextualize.entryCreate.appLink;
diff --git a/src/components/recordedit/recordedit.tsx b/src/components/recordedit/recordedit.tsx
index 8bafbb89a..b039b3dcc 100644
--- a/src/components/recordedit/recordedit.tsx
+++ b/src/components/recordedit/recordedit.tsx
@@ -185,7 +185,7 @@ const RecordeditInner = ({
delayError: undefined
});
- const canShowBulkDelete = appMode === appModes.EDIT && ConfigService.chaiseConfig.deleteRecord === true;
+ const canShowBulkDelete = appMode === appModes.EDIT && ConfigService.chaiseConfig.deleteRecord !== false;
/**
* enable the button if at least one row can be deleted
*/
@@ -804,7 +804,7 @@ const RecordeditInner = ({
}
- {/* Intersecting behaviour of scroll should be visible if there are multiple tables
+ {/* Intersecting behaviour of scroll should be visible if there are multiple tables
on one page which here seems to be the case when there are successful as well as failed records */}
diff --git a/src/pages/recordset.tsx b/src/pages/recordset.tsx
index 3abe8c5ac..976047dc2 100644
--- a/src/pages/recordset.tsx
+++ b/src/pages/recordset.tsx
@@ -98,13 +98,11 @@ const RecordsetApp = (): JSX.Element => {
}
const chaiseConfig = ConfigService.chaiseConfig;
- const modifyEnabled = chaiseConfig.editRecord !== false;
- const deleteEnabled = chaiseConfig.deleteRecord === true;
const recordsetConfig: RecordsetConfig = {
viewable: true,
- editable: modifyEnabled,
- deletable: modifyEnabled && deleteEnabled,
+ editable: chaiseConfig.editRecord !== false,
+ deletable: chaiseConfig.deleteRecord !== false,
sortable: true,
selectMode: RecordsetSelectMode.NO_SELECT,
disableFaceting: false,
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 9d2cb4539..17b835438 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -47,7 +47,8 @@ export const DEFAULT_CHAISE_CONFIG = {
dataBrowser: '/',
maxRecordsetRowHeight: 160,
confirmDelete: true,
- deleteRecord: false,
+ editRecord: true,
+ deleteRecord: true,
signUpURL: '',
allowErrorDismissal: false,
hideTableOfContents: false,
diff --git a/src/utils/record-utils.ts b/src/utils/record-utils.ts
index 2af2ff42e..10cc0321d 100644
--- a/src/utils/record-utils.ts
+++ b/src/utils/record-utils.ts
@@ -73,12 +73,7 @@ export function canEditRelated(relatedRef: any): boolean {
* Whether we can delete instances of a related reference
*/
export function canDeleteRelated(relatedRef: any): boolean {
- /**
- * TODO this is replicating the Angularjs behavior but we should consider the following:
- * - why do we need to check editRecord?
- * - why deleteRecord check is backwards?
- */
- if (ConfigService.chaiseConfig.editRecord === false || ConfigService.chaiseConfig.deleteRecord !== true) {
+ if (ConfigService.chaiseConfig.deleteRecord === false) {
return false;
}
diff --git a/test/e2e/specs/default-config/record/links.spec.ts b/test/e2e/specs/default-config/record/links.spec.ts
index c523d984a..f7009885d 100644
--- a/test/e2e/specs/default-config/record/links.spec.ts
+++ b/test/e2e/specs/default-config/record/links.spec.ts
@@ -4,6 +4,7 @@ import { test, expect, TestInfo, Page } from '@playwright/test';
import ModalLocators from '@isrd-isi-edu/chaise/test/e2e/locators/modal';
import NavbarLocators from '@isrd-isi-edu/chaise/test/e2e/locators/navbar';
import RecordLocators from '@isrd-isi-edu/chaise/test/e2e/locators/record';
+import RecordeditLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit';
import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset';
// utils
@@ -100,6 +101,27 @@ test('export button', async ({ page, baseURL }, testInfo) => {
await testExportDropdown(page, ['links-table.csv', `links-table_${ridValue}.zip`], APP_NAMES.RECORD);
});
+
+test('default behavior of editRecord and deleteRecord chaise-config props', async ({page, baseURL}, testInfo) => {
+ await goToPage(page, baseURL, testInfo, testParams.table_name);
+
+ await test.step('delete button should be visible', async () => {
+ const deleteBtn = RecordLocators.getDeleteRecordButton(page);
+ await expect.soft(deleteBtn).toBeVisible();
+ // actual delete tests are done in other specs
+ });
+
+ // this must be the last step here as it will change the page location
+ await test.step('edit button should be visible', async () => {
+ const editBtn = RecordLocators.getEditRecordButton(page);
+ await expect.soft(editBtn).toBeVisible();
+ await editBtn.click();
+ await expect.soft(page).toHaveURL(/recordedit/);
+ await RecordeditLocators.waitForRecordeditPageReady(page);
+ // actual edit tests are done in other specs
+ });
+});
+
test('hide_column_header support', async ({ page, baseURL }, testInfo) => {
await goToPage(page, baseURL, testInfo, testParams.table_name);
@@ -116,8 +138,6 @@ test('hide_column_header support', async ({ page, baseURL }, testInfo) => {
});
-
-
/********************** helper functions ************************/
const goToPage = async (page: Page, baseURL: string | undefined, testInfo: TestInfo, tableName: string, dontWrapAroundStep?: boolean) => {
const steps = async () => {
diff --git a/test/e2e/utils/page-utils.ts b/test/e2e/utils/page-utils.ts
index 1cfcc15d7..323189348 100644
--- a/test/e2e/utils/page-utils.ts
+++ b/test/e2e/utils/page-utils.ts
@@ -48,7 +48,7 @@ export function generateChaiseURL(appName: APP_NAMES, schemaName: string, tableN
*
* Example:
*
- * const newPage = await PageLocators.clickNewTabLink(someButton, context);
+ * const newPage = await clickNewTabLink(someButton, context);
*
* await newPage.waitForURL('someURL');
*
From bf0d4dc03d398972d372a51288c51319971d1527 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 21 Nov 2024 11:53:00 -0800
Subject: [PATCH 28/30] Bump cross-spawn from 7.0.3 to 7.0.6 (#2581)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)
---
updated-dependencies:
- dependency-name: cross-spawn
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aref Shafaei
---
package-lock.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index e02308a68..581b292c4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
},
"devDependencies": {
"@isrd-isi-edu/ermrest-data-utils": "^0.0.7",
- "@playwright/test": "latest",
+ "@playwright/test": "*",
"@typescript-eslint/eslint-plugin": "~7.18.0",
"@typescript-eslint/parser": "~7.18.0",
"chance": "x",
@@ -4149,8 +4149,9 @@
"dev": true
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "license": "MIT",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
From 302f006fcf15c97fc76ce486db95ba7f11652cb6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:18:10 -0800
Subject: [PATCH 29/30] Bump @playwright/test from 1.48.2 to 1.49.0 (#2586)
* Bump @playwright/test from 1.48.2 to 1.49.0
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.48.2 to 1.49.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.48.2...v1.49.0)
---
updated-dependencies:
- dependency-name: "@playwright/test"
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
* use the new chromium headless mode
---------
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aref Shafaei
---
Makefile | 5 ++--
package-lock.json | 28 ++++++++++++----------
test/e2e/setup/playwright.configuration.ts | 17 +++++++++++--
test/e2e/utils/constants.ts | 2 +-
4 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/Makefile b/Makefile
index bd02e661d..7f8e75bea 100644
--- a/Makefile
+++ b/Makefile
@@ -108,7 +108,7 @@ ALL_MANUAL_TESTS=$(Manualrecordset)
define make_test
rc=0; \
for file in $(1); do \
- npx playwright test --project=chrome $(2) --config $$file || rc=1; \
+ npx playwright test --project=chromium $(2) --config $$file || rc=1; \
done; \
exit $$rc;
endef
@@ -400,10 +400,11 @@ npm-install-modules:
# install packages needed for production and development (including testing)
# --include=dev makes sure to ignore NODE_ENV and install everything
+# --no-shell: https://github.com/microsoft/playwright/issues/33566
.PHONY: npm-install-all-modules
npm-install-all-modules:
@npm clean-install --include=dev
- @npx playwright install --with-deps
+ @npx playwright install --with-deps --no-shell
# for test cases we have to make sure we're installing dev dependencies and playwright is installed
.PHONY: deps-test
diff --git a/package-lock.json b/package-lock.json
index 581b292c4..c41c55485 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
},
"devDependencies": {
"@isrd-isi-edu/ermrest-data-utils": "^0.0.7",
- "@playwright/test": "*",
+ "@playwright/test": "latest",
"@typescript-eslint/eslint-plugin": "~7.18.0",
"@typescript-eslint/parser": "~7.18.0",
"chance": "x",
@@ -2509,12 +2509,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.48.2",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz",
- "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==",
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz",
+ "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==",
"dev": true,
"dependencies": {
- "playwright": "1.48.2"
+ "playwright": "1.49.0"
},
"bin": {
"playwright": "cli.js"
@@ -6158,8 +6158,10 @@
},
"node_modules/fsevents": {
"version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
- "license": "MIT",
+ "hasInstallScript": true,
"optional": true,
"os": [
"darwin"
@@ -8410,12 +8412,12 @@
}
},
"node_modules/playwright": {
- "version": "1.48.2",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz",
- "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==",
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz",
+ "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==",
"dev": true,
"dependencies": {
- "playwright-core": "1.48.2"
+ "playwright-core": "1.49.0"
},
"bin": {
"playwright": "cli.js"
@@ -8428,9 +8430,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.48.2",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz",
- "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==",
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz",
+ "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
diff --git a/test/e2e/setup/playwright.configuration.ts b/test/e2e/setup/playwright.configuration.ts
index 67812bad4..38c9dc5be 100644
--- a/test/e2e/setup/playwright.configuration.ts
+++ b/test/e2e/setup/playwright.configuration.ts
@@ -86,7 +86,19 @@ const getConfig = (options: TestOptions) => {
{
name: PW_PROJECT_NAMES.PRETEST,
testDir: __dirname,
- testMatch: 'playwright.pretest.ts'
+ testMatch: 'playwright.pretest.ts',
+ use: {
+ /**
+ * To reduce the download size, I've added "no-shell" to the npm-install-all-modules make target.
+ * so only the new headless mode is available and that's why we need to define the channel.
+ *
+ * Once we decided to run the test cases on all browsers, we should see if defining this channel
+ * causes any issues or not. If it does, we should remove this property from here and "no-shell" from make target.
+ *
+ * https://github.com/microsoft/playwright/issues/33566
+ */
+ channel: 'chromium',
+ }
},
{
name: PW_PROJECT_NAMES.CHROME,
@@ -94,7 +106,8 @@ const getConfig = (options: TestOptions) => {
use: {
...devices['Desktop Chrome'],
...extraBrowserParams,
- permissions: ['clipboard-read', 'clipboard-write']
+ permissions: ['clipboard-read', 'clipboard-write'],
+ channel: 'chromium', // https://github.com/microsoft/playwright/issues/33566
},
},
// {
diff --git a/test/e2e/utils/constants.ts b/test/e2e/utils/constants.ts
index 7ceb83363..354d1d846 100644
--- a/test/e2e/utils/constants.ts
+++ b/test/e2e/utils/constants.ts
@@ -43,7 +43,7 @@ export const ENTITIES_PATH = 'entities.json';
export enum PW_PROJECT_NAMES {
PRETEST = 'pretest',
- CHROME = 'chrome',
+ CHROME = 'chromium',
FIREFOX = 'firefox',
SAFARI = 'safari'
}
From 331a2a7b5c59289a0996be85d2f3fed775eb7080 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 6 Dec 2024 11:08:15 -0800
Subject: [PATCH 30/30] Bump sass from 1.80.6 to 1.81.0 (#2585)
* Bump sass from 1.80.6 to 1.81.0
Bumps [sass](https://github.com/sass/dart-sass) from 1.80.6 to 1.81.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.80.6...1.81.0)
---
updated-dependencies:
- dependency-name: sass
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
* hide the warning messages from old sass command (I didn't remove them from webpack
since we're actively working on those sass files and the warning allows us to
keep the repo up to date).
* hide npm error messages from make dist (make deps-test will continue to show
the warning since I think seeing warnings during development is useful).
---------
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aref Shafaei
---
Makefile | 6 +++---
package-lock.json | 15 ++++++++-------
package.json | 2 +-
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/Makefile b/Makefile
index 7f8e75bea..762520e2d 100644
--- a/Makefile
+++ b/Makefile
@@ -269,8 +269,8 @@ SHARED_CSS_SOURCE=$(CSS)/vendor/bootstrap.min.css \
SASS=$(COMMON)/styles/app.css
$(SASS): $(shell find $(COMMON)/styles/scss/)
$(info - creating app.css and navbar.css)
- @npx sass --style=compressed --embed-source-map --source-map-urls=relative $(COMMON)/styles/scss/app.scss $(COMMON)/styles/app.css
- @npx sass --load-path=$(COMMON)/styles/scss/_variables.scss --style=compressed --embed-source-map --source-map-urls=relative $(COMMON)/styles/scss/_navbar.scss $(COMMON)/styles/navbar.css
+ @npx sass --quiet --style=compressed --embed-source-map --source-map-urls=relative $(COMMON)/styles/scss/app.scss $(COMMON)/styles/app.css
+ @npx sass --quiet --load-path=$(COMMON)/styles/scss/_variables.scss --style=compressed --embed-source-map --source-map-urls=relative $(COMMON)/styles/scss/_navbar.scss $(COMMON)/styles/navbar.css
# should eventually be removed
DEPRECATED_JS_CONFIG=chaise-config.js
@@ -396,7 +396,7 @@ $(BUILD_VERSION):
# using clean-install instead of install to ensure usage of pacakge-lock.json
.PHONY: npm-install-modules
npm-install-modules:
- @npm clean-install
+ @npm clean-install --loglevel=error
# install packages needed for production and development (including testing)
# --include=dev makes sure to ignore NODE_ENV and install everything
diff --git a/package-lock.json b/package-lock.json
index c41c55485..95dc068e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,7 +37,7 @@
"react-hook-form": "^7.36.1",
"react-imask": "^6.5.0-alpha.0",
"react-plotly.js": "^2.5.1",
- "sass": "^1.80.6",
+ "sass": "^1.81.0",
"sass-loader": "^16.0.3",
"spark-md5": "^3.0.2",
"typescript": "~5.5.4",
@@ -6859,8 +6859,9 @@
}
},
"node_modules/immutable": {
- "version": "4.0.0",
- "license": "MIT"
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
+ "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw=="
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -9338,12 +9339,12 @@
"peer": true
},
"node_modules/sass": {
- "version": "1.80.6",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz",
- "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==",
+ "version": "1.81.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.81.0.tgz",
+ "integrity": "sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==",
"dependencies": {
"chokidar": "^4.0.0",
- "immutable": "^4.0.0",
+ "immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
diff --git a/package.json b/package.json
index fe9d0033c..37d64adfb 100644
--- a/package.json
+++ b/package.json
@@ -77,7 +77,7 @@
"react-hook-form": "^7.36.1",
"react-imask": "^6.5.0-alpha.0",
"react-plotly.js": "^2.5.1",
- "sass": "^1.80.6",
+ "sass": "^1.81.0",
"sass-loader": "^16.0.3",
"spark-md5": "^3.0.2",
"typescript": "~5.5.4",