diff --git a/doc/well_completions_storybooks.PNG b/doc/well_completions_storybooks.PNG new file mode 100644 index 000000000..d898e4b17 Binary files /dev/null and b/doc/well_completions_storybooks.PNG differ diff --git a/package-lock.json b/package-lock.json index f3ada33fe..84f7648b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "leaflet": "^1.6.0", "leaflet-draw": "^1.0.4", "lodash": "^4.17.21", + "mathjs": "^9.4.2", "nebula.gl": "^0.22.3", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -1500,10 +1501,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.14.0", - "license": "MIT", + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { @@ -12590,6 +12595,14 @@ "dev": true, "license": "MIT" }, + "node_modules/complex.js": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.13.tgz", + "integrity": "sha512-UEWd3G3/kd3lJmsdLsDh9qfinJlujL4hIFn3Vo4/G5eqehPsgCHf2CLhFs77tVkOp2stt/jbNit7Q1XFANFltA==", + "engines": { + "node": "*" + } + }, "node_modules/component-classes": { "version": "1.2.6", "license": "MIT", @@ -13981,9 +13994,9 @@ } }, "node_modules/decimal.js": { - "version": "10.2.1", - "dev": true, - "license": "MIT" + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.0.tgz", + "integrity": "sha512-MrQRs2gyD//7NeHi9TtsfClkf+cFAewDz+PZHR8ILKglLmBMyVX3ymQ+oeznE3tjrS7beTN+6JXb2C3JDHm7ug==" }, "node_modules/deck.gl": { "version": "8.4.16", @@ -14972,6 +14985,11 @@ "dev": true, "license": "MIT" }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "license": "MIT", @@ -16724,6 +16742,18 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.1.tgz", + "integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "dev": true, @@ -19043,6 +19073,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "node_modules/jest": { "version": "26.6.3", "dev": true, @@ -21247,6 +21282,28 @@ "@math.gl/core": "3.4.2" } }, + "node_modules/mathjs": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-9.4.3.tgz", + "integrity": "sha512-UgIalR0ICCu1Me4kVPEXb8Xtw7S62XMpP7qSXfImQsoIc1pOX/IxZkLw33VdFYlmXpma3zcRoMq5auwB2fg1AA==", + "dependencies": { + "@babel/runtime": "^7.14.6", + "complex.js": "^2.0.13", + "decimal.js": "^10.3.0", + "escape-latex": "^1.2.0", + "fraction.js": "^4.1.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/md5.js": { "version": "1.3.5", "dev": true, @@ -26198,6 +26255,11 @@ "version": "2.0.1", "license": "ISC" }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/select": { "version": "1.1.2", "dev": true, @@ -28009,9 +28071,7 @@ }, "node_modules/tiny-emitter": { "version": "2.1.0", - "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -28287,6 +28347,14 @@ "node": ">= 0.6" } }, + "node_modules/typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/typedarray": { "version": "0.0.6", "dev": true, @@ -31159,7 +31227,9 @@ } }, "@babel/runtime": { - "version": "7.14.0", + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -39073,6 +39143,11 @@ "version": "1.0.1", "dev": true }, + "complex.js": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.13.tgz", + "integrity": "sha512-UEWd3G3/kd3lJmsdLsDh9qfinJlujL4hIFn3Vo4/G5eqehPsgCHf2CLhFs77tVkOp2stt/jbNit7Q1XFANFltA==" + }, "component-classes": { "version": "1.2.6", "requires": { @@ -40085,8 +40160,9 @@ "dev": true }, "decimal.js": { - "version": "10.2.1", - "dev": true + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.0.tgz", + "integrity": "sha512-MrQRs2gyD//7NeHi9TtsfClkf+cFAewDz+PZHR8ILKglLmBMyVX3ymQ+oeznE3tjrS7beTN+6JXb2C3JDHm7ug==" }, "deck.gl": { "version": "8.4.16", @@ -40802,6 +40878,11 @@ "version": "1.0.3", "dev": true }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5" }, @@ -42011,6 +42092,11 @@ "version": "0.2.0", "dev": true }, + "fraction.js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.1.tgz", + "integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==" + }, "fragment-cache": { "version": "0.2.1", "dev": true, @@ -43507,6 +43593,11 @@ "iterate-iterator": "^1.0.1" } }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "jest": { "version": "26.6.3", "dev": true, @@ -44961,6 +45052,22 @@ "@math.gl/core": "3.4.2" } }, + "mathjs": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-9.4.3.tgz", + "integrity": "sha512-UgIalR0ICCu1Me4kVPEXb8Xtw7S62XMpP7qSXfImQsoIc1pOX/IxZkLw33VdFYlmXpma3zcRoMq5auwB2fg1AA==", + "requires": { + "@babel/runtime": "^7.14.6", + "complex.js": "^2.0.13", + "decimal.js": "^10.3.0", + "escape-latex": "^1.2.0", + "fraction.js": "^4.1.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + } + }, "md5.js": { "version": "1.3.5", "dev": true, @@ -48282,6 +48389,11 @@ "scrollparent": { "version": "2.0.1" }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "select": { "version": "1.1.2", "dev": true, @@ -49548,9 +49660,7 @@ "dev": true }, "tiny-emitter": { - "version": "2.1.0", - "dev": true, - "optional": true + "version": "2.1.0" }, "tiny-warning": { "version": "1.0.3" @@ -49731,6 +49841,11 @@ "mime-types": "~2.1.24" } }, + "typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==" + }, "typedarray": { "version": "0.0.6", "dev": true diff --git a/src/lib/components/GroupTree/components/GroupTreeViewer.tsx b/src/lib/components/GroupTree/components/GroupTreeViewer.tsx index b6b142bc7..cdd4aa201 100644 --- a/src/lib/components/GroupTree/components/GroupTreeViewer.tsx +++ b/src/lib/components/GroupTree/components/GroupTreeViewer.tsx @@ -1,4 +1,5 @@ import { createStyles, makeStyles } from "@material-ui/core"; +import { cloneDeep } from "lodash"; import React, { useContext, useEffect, useRef } from "react"; import { useSelector } from "react-redux"; import { GroupTreeState } from "../redux/store"; @@ -36,7 +37,11 @@ const GroupTreeViewer: React.FC = () => { (state: GroupTreeState) => state.ui.currentFlowRate ); useEffect(() => { - renderer.current = new GroupTree("#grouptree_tree", data, "oilrate"); + renderer.current = new GroupTree( + "#grouptree_tree", + cloneDeep(data), + "oilrate" + ); }, [data]); useEffect(() => { diff --git a/src/lib/components/GroupTree/components/Plot/group_tree.js b/src/lib/components/GroupTree/components/Plot/group_tree.js index 9b49fac85..03ac7770a 100644 --- a/src/lib/components/GroupTree/components/Plot/group_tree.js +++ b/src/lib/components/GroupTree/components/Plot/group_tree.js @@ -1,3 +1,7 @@ +/** This code is copied directly from + * https://github.com/anders-kiaer/webviz-subsurface-components/blob/dynamic_tree/src/lib/components/DynamicTree/group_tree.js + * This needs to be refactored to develop further + */ import * as d3 from "d3"; /* eslint camelcase: "off" */ /* eslint array-callback-return: "off" */ diff --git a/src/lib/components/WellCompletions/WellCompletions.jsx b/src/lib/components/WellCompletions/WellCompletions.jsx index 0c54375d1..b0665a149 100644 --- a/src/lib/components/WellCompletions/WellCompletions.jsx +++ b/src/lib/components/WellCompletions/WellCompletions.jsx @@ -11,7 +11,13 @@ import WellCompletionComponent from "./components/WellCompletionComponent"; const WellCompletions = (props) => { return ; }; - +/** + * Typescript and PropTypes serve different purposes. Typescript validates types at compile time, + * whereas PropTypes are checked at runtime. + * PropTypes are useful when you test how the components interact with external data, for example + * when you load JSON from an API. + * This is the only place in this component that propTypes definition is really needed. + */ WellCompletions.propTypes = { id: PropTypes.string.isRequired, data: PropTypes.object, diff --git a/src/lib/components/WellCompletions/WellCompletions.stories.jsx b/src/lib/components/WellCompletions/WellCompletions.stories.jsx index 0ed52faad..44c086086 100644 --- a/src/lib/components/WellCompletions/WellCompletions.stories.jsx +++ b/src/lib/components/WellCompletions/WellCompletions.stories.jsx @@ -1,7 +1,9 @@ import React from "react"; import { exampleData } from "./test/storybookDataDecorator"; import WellCompletions from "./WellCompletions"; - +/** + * Storybook test for the whole well completion component + */ export default { component: WellCompletions, title: "WellCompletions/Demo", @@ -16,7 +18,7 @@ export default { const Template = (data) => ; export const WellCompletion = Template.bind({}); - +//Inject test input data WellCompletion.args = { data: exampleData, }; diff --git a/src/lib/components/WellCompletions/components/DataLoader.tsx b/src/lib/components/WellCompletions/components/DataLoader.tsx index 0a8279ca9..bdbfdc183 100644 --- a/src/lib/components/WellCompletions/components/DataLoader.tsx +++ b/src/lib/components/WellCompletions/components/DataLoader.tsx @@ -21,7 +21,9 @@ const defaultData = { timeSteps: [], }; export const DataContext = React.createContext(defaultData); - +/** + * A data loading layer to ready the input data and redux store + */ const DataProvider: React.FC = ({ children, id, @@ -32,6 +34,7 @@ const DataProvider: React.FC = ({ data.stratigraphy.forEach((zone) => findSubzones(zone, subzones)); return subzones.map((zone) => zone.name); }, [data.stratigraphy]); + const preloadedState = useMemo(() => { //Setup attributes const attributeKeys = new Set(); diff --git a/src/lib/components/WellCompletions/components/Settings/FilterButton.stories.tsx b/src/lib/components/WellCompletions/components/Settings/FilterButton.stories.tsx new file mode 100644 index 000000000..85a34b28a --- /dev/null +++ b/src/lib/components/WellCompletions/components/Settings/FilterButton.stories.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { withReduxDecorator } from "../../test/storybookReduxAddon"; +import FilterButton from "./FilterButton"; + +export default { + component: FilterButton, + title: "WellCompletions/Components/Buttons/Filter", +}; + +const Template = () => ; +export const Button = Template.bind({}); +//Wrap with redux store +Button.decorators = [withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/FilterMenu.test.tsx b/src/lib/components/WellCompletions/components/Settings/FilterButton.test.tsx similarity index 89% rename from src/lib/components/WellCompletions/components/Settings/FilterMenu.test.tsx rename to src/lib/components/WellCompletions/components/Settings/FilterButton.test.tsx index 801a6ce79..f321870c5 100644 --- a/src/lib/components/WellCompletions/components/Settings/FilterMenu.test.tsx +++ b/src/lib/components/WellCompletions/components/Settings/FilterButton.test.tsx @@ -3,15 +3,15 @@ import { fireEvent, render, screen } from "@testing-library/react"; import "jest-styled-components"; import React from "react"; import { testStore, Wrapper } from "../../test/TestWrapper"; -import FilterMenu from "./FilterMenu"; +import FilterButton from "./FilterButton"; describe("Test Filter Menu", () => { it("snapshot test", () => { - const { container } = render(Wrapper({ children: })); + const { container } = render(Wrapper({ children: })); expect(container.firstChild).toMatchSnapshot(); }); it("click to open filter menu and dispatch redux action", async () => { - render(, { + render(, { wrapper: Wrapper, }); fireEvent.click(screen.getByTestId("filter_button")); @@ -22,7 +22,7 @@ describe("Test Filter Menu", () => { }); }); it("Test tooltip title when menu not open", async () => { - render(, { + render(, { wrapper: Wrapper, }); fireEvent.mouseOver(screen.getByTestId("filter_button")); diff --git a/src/lib/components/WellCompletions/components/Settings/FilterMenu.tsx b/src/lib/components/WellCompletions/components/Settings/FilterButton.tsx similarity index 64% rename from src/lib/components/WellCompletions/components/Settings/FilterMenu.tsx rename to src/lib/components/WellCompletions/components/Settings/FilterButton.tsx index f23bb20f6..c263b8996 100644 --- a/src/lib/components/WellCompletions/components/Settings/FilterMenu.tsx +++ b/src/lib/components/WellCompletions/components/Settings/FilterButton.tsx @@ -8,23 +8,33 @@ import { WellCompletionsState } from "../../redux/store"; // Use library approach Icon.add({ filter_alt }); // (this needs only be done once) -const FilterMenu: React.FC = React.memo(() => { +/** + * A button for toggle on and off the filter functions in the side drawer + */ +const FilterButton: React.FC = React.memo(() => { + //Redux const dispatch = useDispatch(); - const isDrawerOpen = useSelector( (state: WellCompletionsState) => state.ui.isDrawerOpen ); - const openDrawer = useCallback( + + // Handlers + const onClick = useCallback( () => dispatch(updateIsDrawerOpen(!isDrawerOpen)), [dispatch, isDrawerOpen] ); + + //Render return (
@@ -33,5 +43,5 @@ const FilterMenu: React.FC = React.memo(() => { ); }); -FilterMenu.displayName = "FilterMenu"; -export default FilterMenu; +FilterButton.displayName = "FilterMenu"; +export default FilterButton; diff --git a/src/lib/components/WellCompletions/components/Settings/FilterByAttributesButton.tsx b/src/lib/components/WellCompletions/components/Settings/FilterByAttributesButton.tsx deleted file mode 100644 index 1e8c2a038..000000000 --- a/src/lib/components/WellCompletions/components/Settings/FilterByAttributesButton.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable react/display-name */ -import { Button, Dialog, Icon, Menu, Scrim } from "@equinor/eds-core-react"; -import { sort } from "@equinor/eds-icons"; -import { createStyles, makeStyles } from "@material-ui/core"; -import React, { useState } from "react"; -import WellAttributesSelector from "./WellAttributesSelector"; - -// Use library approach -Icon.add({ sort }); // (this needs only be done once) -const useStyles = makeStyles(() => - createStyles({ - dialog: { - minWidth: "400px", - }, - action: { margin: "5px" }, - }) -); - -const FilterByAttributesButton: React.FC = React.memo(() => { - const classes = useStyles(); - // Dialogs - - const [visibleScrim, setVisibleScrim] = useState(false); - const handleClose = () => { - setVisibleScrim(!visibleScrim); - }; - return ( - <> - setVisibleScrim(true)}> - Filter by Attributes - - {visibleScrim && ( - - - - - - - - - - - - )} - - ); -}); - -FilterByAttributesButton.displayName = "FilterByAttributesButton"; -export default FilterByAttributesButton; diff --git a/src/lib/components/WellCompletions/components/Settings/FilterMenu.stories.tsx b/src/lib/components/WellCompletions/components/Settings/FilterMenu.stories.tsx deleted file mode 100644 index b2c5bed6b..000000000 --- a/src/lib/components/WellCompletions/components/Settings/FilterMenu.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { exampleDataDecorator } from "../../test/storybookDataDecorator"; -import { withReduxDecorator } from "../../test/storybookReduxAddon"; -import FilterMenu from "./FilterMenu"; - -export default { - component: FilterMenu, - title: "WellCompletions/Components/Menus/Filter", -}; - -const Template = () => ; -export const Menu = Template.bind({}); -Menu.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.stories.tsx b/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.stories.tsx index f7315ce31..b9a023662 100644 --- a/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.stories.tsx @@ -8,4 +8,5 @@ export default { const Template = () => ; export const Switch = Template.bind({}); +//Wrap with redux store Switch.decorators = [withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.tsx b/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.tsx index 0984c0f1c..22535dd1e 100644 --- a/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.tsx +++ b/src/lib/components/WellCompletions/components/Settings/HideZeroCompletionsSwitch.tsx @@ -3,14 +3,17 @@ import React, { useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; import { updateHideZeroCompletions } from "../../redux/actions"; import { WellCompletionsState } from "../../redux/store"; - +/** + * A switch for showing/hiding wells with zero completion + */ const HideZeroCompletionsSwitch: React.FC = React.memo(() => { // Redux const dispatch = useDispatch(); const hideZeroCompletions = useSelector( (st: WellCompletionsState) => st.ui.hideZeroCompletions ); - // handlers + + // Handlers const handleSwitchChange = useCallback( (event) => dispatch(updateHideZeroCompletions(event.target.checked)), [dispatch] diff --git a/src/lib/components/WellCompletions/components/Settings/SettingsBar.stories.tsx b/src/lib/components/WellCompletions/components/Settings/SettingsBar.stories.tsx index 8be7bfb91..1e00fa763 100644 --- a/src/lib/components/WellCompletions/components/Settings/SettingsBar.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/SettingsBar.stories.tsx @@ -10,4 +10,6 @@ export default { const Template = () => ; export const TopBar = Template.bind({}); +//Wrap with redux store +//Settings bar also need to use the input data therefore wrapping with exampleDataDecorator TopBar.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/SettingsBar.tsx b/src/lib/components/WellCompletions/components/Settings/SettingsBar.tsx index 44c8a6949..5cd7ed8e2 100644 --- a/src/lib/components/WellCompletions/components/Settings/SettingsBar.tsx +++ b/src/lib/components/WellCompletions/components/Settings/SettingsBar.tsx @@ -1,9 +1,9 @@ import { TopBar } from "@equinor/eds-core-react"; import { createStyles, makeStyles } from "@material-ui/core"; import React from "react"; -import FilterMenu from "./FilterMenu"; +import FilterButton from "./FilterButton"; import TimeRangeSelector from "./TimeRangeSelector"; -import ViewMenu from "./ViewMenu"; +import ViewButton from "./ViewButton"; const useStyles = makeStyles(() => createStyles({ @@ -17,6 +17,9 @@ const useStyles = makeStyles(() => }, }) ); +/** + * A settings bar that offers time selection and other viewing/filtering functions + */ const SettingsBar: React.FC = React.memo(() => { const classes = useStyles(); return ( @@ -25,8 +28,8 @@ const SettingsBar: React.FC = React.memo(() => { - - + + ); diff --git a/src/lib/components/WellCompletions/components/Settings/SortButton.stories.tsx b/src/lib/components/WellCompletions/components/Settings/SortButton.stories.tsx index 55f0e01a1..7b4d2065a 100644 --- a/src/lib/components/WellCompletions/components/Settings/SortButton.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/SortButton.stories.tsx @@ -5,9 +5,10 @@ import SortButton from "./SortButton"; export default { component: SortButton, - title: "WellCompletions/Components/Settings/Sort by Attributes", + title: "WellCompletions/Components/Buttons/Sort by Attributes", }; const Template = () => ; export const Button = Template.bind({}); +//Wrap with example intpu data and redux store Button.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/SortButton.tsx b/src/lib/components/WellCompletions/components/Settings/SortButton.tsx index cc2f5527b..466c3f1a9 100644 --- a/src/lib/components/WellCompletions/components/Settings/SortButton.tsx +++ b/src/lib/components/WellCompletions/components/Settings/SortButton.tsx @@ -12,11 +12,12 @@ const useStyles = makeStyles(() => action: { margin: "5px" }, }) ); - +/** + * A menu button that shows a dialog for sorting wells by attributes + */ const SortButton: React.FC = React.memo(() => { const classes = useStyles(); // Dialogs - const [visibleScrim, setVisibleScrim] = useState(false); const handleClose = () => { setVisibleScrim(!visibleScrim); diff --git a/src/lib/components/WellCompletions/components/Settings/SortTable.stories.tsx b/src/lib/components/WellCompletions/components/Settings/SortTable.stories.tsx index 3ab0cb01d..6e9e88230 100644 --- a/src/lib/components/WellCompletions/components/Settings/SortTable.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/SortTable.stories.tsx @@ -9,4 +9,5 @@ export default { const Template = () => ; export const Table = Template.bind({}); +//Wrap with redux store Table.decorators = [withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/SortTable.tsx b/src/lib/components/WellCompletions/components/Settings/SortTable.tsx index e33fca02f..65495abf9 100644 --- a/src/lib/components/WellCompletions/components/Settings/SortTable.tsx +++ b/src/lib/components/WellCompletions/components/Settings/SortTable.tsx @@ -42,18 +42,29 @@ const useStyles = makeStyles((theme: Theme) => ); Icon.add({ add_box }); // (this needs only be done once) Icon.add({ delete_to_trash }); // (this needs only be done once) - +/** + * A table that allows adding or removing sorting layers. + * There is also an option per row to sort by ascending or descending order + */ const SortTable: React.FC = React.memo(() => { const classes = useStyles(); + // Local states + // Sort key in the placeholder row const [sortKeyToAdd, setSortKeyToAdd] = useState(); + // Sort direction in the placeholder row const [sortDirectionToAdd, setSortDirectionToAdd] = useState("Ascending"); + // Redux const dispatch = useDispatch(); + // The attributes that we are currently sorting by const sortBy = useSelector((st: WellCompletionsState) => st.ui.sortBy); + // All the attribute keys const attributeKeys = useSelector( (st: WellCompletionsState) => st.attributes.attributeKeys ); + // Memo + // Apart from the user defined attribute, we can also sort by well name, stratigraphy depth etc const sortKeys = useMemo(() => { const keys = new Set([ SORT_BY_NAME, @@ -63,11 +74,14 @@ const SortTable: React.FC = React.memo(() => { attributeKeys.forEach((key) => keys.add(key)); return keys; }, [attributeKeys]); - + // The attribute keys that are not yet added to the current sorting set const availableToAdd = useMemo( () => Array.from(sortKeys).filter((key) => !(key in sortBy)), [sortKeys, sortBy] ); + + // Effects + // Update the sort key in the placeholder row to be the first item in the available attribute key set useEffect(() => { if ( availableToAdd.length > 0 && @@ -75,11 +89,14 @@ const SortTable: React.FC = React.memo(() => { ) setSortKeyToAdd(availableToAdd[0]); }, [availableToAdd, sortKeyToAdd]); - // handlers + + // Handlers + // Update the sort key in the placeholder row const onSortKeyToAddChange = useCallback( (event) => setSortKeyToAdd(event.target.value), [setSortKeyToAdd] ); + // Update the sort direction in the placeholder row const onSortDirectionToAddChange = useCallback( () => setSortDirectionToAdd( @@ -88,16 +105,19 @@ const SortTable: React.FC = React.memo(() => { [setSortDirectionToAdd, sortDirectionToAdd] ); + // Add or update sort key and direction const onUpdateSortKey = useCallback( (sortKey, sortDirection) => dispatch(updateSortKey({ sortKey, sortDirection })), [dispatch] ); + // Remove sort key and direction const onDeleteSortKey = useCallback( (sortKey) => dispatch(deleteSortKey(sortKey)), [dispatch] ); + // Render return ( ; export const Selector = Template.bind({}); +//Wrap with redux store Selector.decorators = [withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/TimeAggregationSelector.tsx b/src/lib/components/WellCompletions/components/Settings/TimeAggregationSelector.tsx index d867e75f3..2620323ff 100644 --- a/src/lib/components/WellCompletions/components/Settings/TimeAggregationSelector.tsx +++ b/src/lib/components/WellCompletions/components/Settings/TimeAggregationSelector.tsx @@ -15,7 +15,9 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); - +/** + * A dropdown for selecting the time aggregation mode + */ const TimeAggregationSelector: React.FC = React.memo(() => { const classes = useStyles(); // Redux @@ -23,11 +25,12 @@ const TimeAggregationSelector: React.FC = React.memo(() => { const timeAggregation = useSelector( (st: WellCompletionsState) => st.ui.timeAggregation ); - // handlers + // Handlers const handleSelectedItemChange = useCallback( (event) => dispatch(updateTimeAggregation(event.target.value)), [dispatch] ); + //Render return ( ; export const Selector = Template.bind({}); +//Wrap with example intpu data and redux store Selector.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/TimeRangeSelector.tsx b/src/lib/components/WellCompletions/components/Settings/TimeRangeSelector.tsx index b97b163e1..44d7e5276 100644 --- a/src/lib/components/WellCompletions/components/Settings/TimeRangeSelector.tsx +++ b/src/lib/components/WellCompletions/components/Settings/TimeRangeSelector.tsx @@ -41,9 +41,14 @@ const EdsSlider = withStyles({ }, }, })(Slider); +/** + * A React component for selecting time step(s) to display in the plot + */ const TimeRangeSelector: React.FC = React.memo(() => { const classes = useStyles(); + // Direct access to the input data const data = useContext(DataContext); + // Redux const dispatch = useDispatch(); const timeAggregation = useSelector( @@ -53,16 +58,25 @@ const TimeRangeSelector: React.FC = React.memo(() => { (state: WellCompletionsState) => state.ui.timeIndexRange, isEqual ) as [number, number]; + + // Memo + // Arry of date time strings const times = useMemo(() => data.timeSteps, [data]); - // handlers + + // Handlers + // Get date time string by index const outputFunction = useCallback((step: number) => times[step], [times]); + // Update time range in redux. When the time aggregation is off, + // only the upper bound of the range will be used in computing the plot data const onChange = useCallback( (range) => dispatch(updateTimeIndexRange(range)), [dispatch] ); - //If number of time step is small + + // Render return (
+ {/* This only appears when time aggregation is on */} {timeAggregation !== "None" && ( { ]) } > + {/* Show the full list of date times */} {times.map((time, index) => ( )} + {/* Slider that is easy to use when the number of time steps is not too large */}
Time Steps { valueLabelDisplay="on" onChange={(_, value) => onChange( + //If time aggregation is off, we only need to know the end date timeAggregation === "None" ? [0, value] - : [ + : // This is due to a feature (or a bug) in EdsSlider that the first + //value in the range is not necessarily the lower bound + [ Math.min(...(value as number[])), Math.max(...(value as number[])), ] @@ -129,6 +148,7 @@ const TimeRangeSelector: React.FC = React.memo(() => { ]) } > + {/* If time aggregation is on, we only show the time steps that >= the start date */} {(timeAggregation === "None" ? times : times.filter( diff --git a/src/lib/components/WellCompletions/components/Settings/ViewMenu.stories.tsx b/src/lib/components/WellCompletions/components/Settings/ViewButton.stories.tsx similarity index 58% rename from src/lib/components/WellCompletions/components/Settings/ViewMenu.stories.tsx rename to src/lib/components/WellCompletions/components/Settings/ViewButton.stories.tsx index 1825af498..15fa04442 100644 --- a/src/lib/components/WellCompletions/components/Settings/ViewMenu.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/ViewButton.stories.tsx @@ -1,13 +1,14 @@ import React from "react"; import { exampleDataDecorator } from "../../test/storybookDataDecorator"; import { withReduxDecorator } from "../../test/storybookReduxAddon"; -import ViewMenu from "./ViewMenu"; +import ViewButton from "./ViewButton"; export default { - component: ViewMenu, - title: "WellCompletions/Components/Menus/View", + component: ViewButton, + title: "WellCompletions/Components/Buttons/View", }; -const Template = () => ; +const Template = () => ; export const Menu = Template.bind({}); +//Wrap with example intpu data and redux store Menu.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/ViewMenu.test.tsx b/src/lib/components/WellCompletions/components/Settings/ViewButton.test.tsx similarity index 80% rename from src/lib/components/WellCompletions/components/Settings/ViewMenu.test.tsx rename to src/lib/components/WellCompletions/components/Settings/ViewButton.test.tsx index b086e6510..353758a74 100644 --- a/src/lib/components/WellCompletions/components/Settings/ViewMenu.test.tsx +++ b/src/lib/components/WellCompletions/components/Settings/ViewButton.test.tsx @@ -3,16 +3,16 @@ import { fireEvent, render, screen } from "@testing-library/react"; import "jest-styled-components"; import React from "react"; import { Wrapper } from "../../test/TestWrapper"; -import ViewMenu from "./ViewMenu"; +import ViewButton from "./ViewButton"; describe("test view menu", () => { it("snapshot test", () => { - const { container } = render(Wrapper({ children: })); + const { container } = render(Wrapper({ children: })); expect(container.firstChild).toMatchSnapshot(); }); it("click to display menu", async () => { - render(, { + render(, { wrapper: Wrapper, }); fireEvent.click(screen.getByRole("button", { name: "" })); diff --git a/src/lib/components/WellCompletions/components/Settings/ViewMenu.tsx b/src/lib/components/WellCompletions/components/Settings/ViewButton.tsx similarity index 89% rename from src/lib/components/WellCompletions/components/Settings/ViewMenu.tsx rename to src/lib/components/WellCompletions/components/Settings/ViewButton.tsx index bd1b63e81..62c54f576 100644 --- a/src/lib/components/WellCompletions/components/Settings/ViewMenu.tsx +++ b/src/lib/components/WellCompletions/components/Settings/ViewButton.tsx @@ -24,19 +24,22 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); -const ViewMenu: React.FC = React.memo(() => { +/** + * A menu button that shows a list of viewing options + */ +const ViewButton: React.FC = React.memo(() => { const classes = useStyles(); const [anchorEl, setAnchorEl] = React.useState(null); - // handlers + // Handlers const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; - const handleClose = () => { setAnchorEl(null); }; + // Render return (
@@ -62,5 +65,5 @@ const ViewMenu: React.FC = React.memo(() => { ); }); -ViewMenu.displayName = "ViewMenu"; -export default ViewMenu; +ViewButton.displayName = "ViewMenu"; +export default ViewButton; diff --git a/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.stories.tsx b/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.stories.tsx index 9e377fce5..f6d217646 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.stories.tsx @@ -10,4 +10,5 @@ export default { const Template = () => ; export const Filter = Template.bind({}); +//Wrap with example intpu data and redux store Filter.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.tsx b/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.tsx index 2bbd0375b..4eec6c289 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellAttributesSelector.tsx @@ -23,12 +23,17 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); +/** + * A react component to allow the users to select wells by attribute values + */ const WellAttributesSelector: React.FC = React.memo(() => { // Style const classes = useStyles(); + // Direct access to the input data const data = useContext(DataContext); // Redux const dispatch = useDispatch(); + // All the attribute keys available const attributeKeys = useSelector( (st: WellCompletionsState) => st.attributes.attributeKeys ); @@ -36,10 +41,12 @@ const WellAttributesSelector: React.FC = React.memo(() => { (st: WellCompletionsState) => st.ui.filterByAttributes ); const wells = useMemo(() => data.wells, [data]); + //Create the tree that the SmartNodeSelector can accept as input. const attributesTree = useMemo( () => extractAttributesTree(wells, attributeKeys), [wells] ); + //Create the hint text for the user to better understand how the filter applies. const hintText = useMemo(() => { const allowedValues = computeAllowedAttributeValues(filterByAttributes); return ( @@ -47,21 +54,24 @@ const WellAttributesSelector: React.FC = React.memo(() => { Array.from(allowedValues.entries()) .map( ([key, values]) => + //Within the same attribute, we use OR relation `"${key}" ${ values.size === 1 ? ` is "${Array.from(values)[0]}"` : ` is in [${Array.from(values)}]` }` ) + //In between different attribute key, we use AND relation .join(" and ") ); }, [filterByAttributes]); - // handlers + // Handlers const handleSelectionChange = useCallback( (selection) => dispatch(updateFilterByAttributes(selection.selectedNodes)), [dispatch] ); + // Render return (
; export const Filter = Template.bind({}); -Filter.decorators = [exampleDataDecorator, withReduxDecorator]; +//Wrap with redux store +Filter.decorators = [withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/WellFilter.tsx b/src/lib/components/WellCompletions/components/Settings/WellFilter.tsx index b9e64fa0d..cf9ad8733 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellFilter.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellFilter.tsx @@ -13,12 +13,16 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); +/** + * A search textfield to search wells by their names + */ const WellFilter: React.FC = React.memo(() => { const classes = useStyles(); // Redux const dispatch = useDispatch(); - // handlers + // Handlers const onChange = useCallback( + // Reduce the update frequency to 0.2 second throttle( (event: React.ChangeEvent) => dispatch(updateWellSearchText(event.target.value)), diff --git a/src/lib/components/WellCompletions/components/Settings/WellPagination.stories.tsx b/src/lib/components/WellCompletions/components/Settings/WellPagination.stories.tsx index 696f603e5..d84379824 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellPagination.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellPagination.stories.tsx @@ -5,9 +5,10 @@ import WellPagination from "./WellPagination"; export default { component: WellPagination, - title: "WellCompletions/Components/Settings/WellPagination", + title: "WellCompletions/Components/Settings/Well Pagination", }; const Template = () => ; export const Bar = Template.bind({}); +//Wrap with example intpu data and redux store Bar.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/WellPagination.tsx b/src/lib/components/WellCompletions/components/Settings/WellPagination.tsx index 4186777de..7b9250de8 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellPagination.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellPagination.tsx @@ -27,6 +27,9 @@ const useStyles = makeStyles(() => }, }) ); +/** + * Divide wells into pages + */ const WellPagination: React.FC = React.memo(() => { const classes = useStyles(); // Redux @@ -38,6 +41,7 @@ const WellPagination: React.FC = React.memo(() => { const wellsPerPage = useSelector( (st: WellCompletionsState) => st.ui.wellsPerPage ); + // Memo const wellsCount = useMemo(() => plotData.wells.length, [plotData]); const pageCount = useMemo( () => Math.ceil(plotData.wells.length / wellsPerPage), @@ -56,19 +60,22 @@ const WellPagination: React.FC = React.memo(() => { () => Math.min(wellsCount, currentClampedPage * wellsPerPage), [currentClampedPage, wellsPerPage, wellsCount] ); - // handlers + // Handlers const onCurrentPageChange = useCallback( (...arg) => dispatch(updateCurrentPage(arg[1])), [dispatch] ); - //effects + + // Effects useEffect(() => { dispatch(updateCurrentPage(currentClampedPage)); }, [currentClampedPage]); + // Render return (
+ {/* Indicates what the current page is displaying*/} {`${startItem} - ${endItem} of ${wellsCount} items`} diff --git a/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.stories.tsx b/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.stories.tsx index a36310685..1032290d4 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.stories.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.stories.tsx @@ -4,9 +4,10 @@ import WellsPerPageSelector from "./WellsPerPageSelector"; export default { component: WellsPerPageSelector, - title: "WellCompletions/Components/Settings/WellsPerPage", + title: "WellCompletions/Components/Settings/Wells Per Page", }; const Template = () => ; export const Selector = Template.bind({}); +//Wrap with redux store Selector.decorators = [withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.tsx b/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.tsx index 4e564460d..a277414b2 100644 --- a/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.tsx +++ b/src/lib/components/WellCompletions/components/Settings/WellsPerPageSelector.tsx @@ -14,7 +14,9 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); - +/** + * A drop down for selecting how many wells to display per page + */ const WellsPerPageSelector: React.FC = React.memo(() => { const classes = useStyles(); // Redux @@ -22,11 +24,12 @@ const WellsPerPageSelector: React.FC = React.memo(() => { const wellsPerPage = useSelector( (st: WellCompletionsState) => st.ui.wellsPerPage ); - // handlers + // Handlers const onWellsPerPageChange = useCallback( (event) => dispatch(updateWellsPerPage(event.target.value)), [dispatch] ); + // Render return ( ; export const Selector = Template.bind({}); +//Wrap with example intpu data and redux store Selector.decorators = [exampleDataDecorator, withReduxDecorator]; diff --git a/src/lib/components/WellCompletions/components/Settings/ZoneSelector.tsx b/src/lib/components/WellCompletions/components/Settings/ZoneSelector.tsx index d61f32c03..1e8a9f1fd 100644 --- a/src/lib/components/WellCompletions/components/Settings/ZoneSelector.tsx +++ b/src/lib/components/WellCompletions/components/Settings/ZoneSelector.tsx @@ -8,6 +8,7 @@ import { Zone } from "../../redux/types"; import { findSubzones } from "../../utils/dataUtil"; import { DataContext } from "../DataLoader"; +//Construct a stratigraphy tree as the input of react-dropdown-tree const extractStratigraphyTree = (stratigraphy: Zone[]): TreeNodeProps => { const root: TreeNodeProps = { label: "All", @@ -32,7 +33,7 @@ const extractStratigraphyTree = (stratigraphy: Zone[]): TreeNodeProps => { return root; }; -//DFS +//Find an array of the selected subzones names from the given selectedNodes export const findSelectedZones = ( stratigraphy: Zone[], selectedNodes: TreeNodeProps[] @@ -61,18 +62,21 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); - +/** + * A react component for selecting zones to display in the completions plot + */ const ZoneSelector: React.FC = React.memo(() => { const classes = useStyles(); + // Use input data directly const data = useContext(DataContext); // Redux const dispatch = useDispatch(); - + // Memo const stratigraphyTree = useMemo( () => extractStratigraphyTree(data.stratigraphy), [data.stratigraphy] ); - // handlers + // Handlers const handleSelectionChange = useCallback( (_, selectedNodes) => dispatch( @@ -82,6 +86,7 @@ const ZoneSelector: React.FC = React.memo(() => { ), [dispatch, data.stratigraphy] ); + // Render return ( = React.memo( ({ id, data }: Props) => { const validate = useMemo(() => ajv.compile(inputSchema), []); diff --git a/src/lib/components/WellCompletions/components/WellCompletionsViewer.tsx b/src/lib/components/WellCompletions/components/WellCompletionsViewer.tsx index b8d5f4864..771f7d96c 100644 --- a/src/lib/components/WellCompletions/components/WellCompletionsViewer.tsx +++ b/src/lib/components/WellCompletions/components/WellCompletionsViewer.tsx @@ -80,9 +80,12 @@ const useStyles = makeStyles((theme: Theme) => const WellCompletionsViewer: React.FC = () => { const classes = useStyles(); + // Use input data directly const data = useContext(DataContext); + // Create plot data with the selected time step(s) const plotData = usePlotData(); + // Redux const isDrawerOpen = useSelector( (state: WellCompletionsState) => state.ui.isDrawerOpen ); @@ -92,6 +95,7 @@ const WellCompletionsViewer: React.FC = () => { const currentPage = useSelector( (state: WellCompletionsState) => state.ui.currentPage ); + // Memo const dataInCurrentPage = useMemo(() => { return { ...plotData, @@ -111,8 +115,10 @@ const WellCompletionsViewer: React.FC = () => { //If no data is available if (!data) return
; + // Render return (
+ {/* We detect the resize of the element and resize the plot accordingly */} {({ width }) => ( <> @@ -142,6 +148,7 @@ const WellCompletionsViewer: React.FC = () => { />
+ {/* Drawer on the right-hand side (hidden by default) that shows the filter options */} { return { ...data, wells: data.wells.map((well) => { + //The earliest completion date for the given well let earliestCompDateIndex = Number.POSITIVE_INFINITY; subzones.forEach((zone) => { if (zone in well.completions) { - //store earliest completion date const completion = well.completions[zone]; + //Find the earliest date for the given completion const earliestDate = completion.t.find( (_, index) => completion.open[index] > 0 ); @@ -33,11 +34,11 @@ export const preprocessData = (subzones: string[], data: Data): Data => { ); } }); + //store the earliest completion date return { ...well, earliestCompDateIndex }; }), }; }; - export interface AttributeNode { name: string; children: { name: AttributeType; key: string }[]; @@ -59,13 +60,14 @@ export const extractAttributesTree = ( ); wells.forEach((well) => - attributes.forEach((values, key) => - values.add( + attributes.forEach((valuesSet, key) => + //If the well doesnt have the given attribute key, it means the attribute is undefined for this well + valuesSet.add( key in well.attributes ? well.attributes[key] : "undefined" ) ) ); - + //Return an array of nodes return Array.from(attributes.entries()).map(([key, values]) => ({ name: key, children: Array.from(values) @@ -76,7 +78,11 @@ export const extractAttributesTree = ( })), })); }; - +/** + * Compute the selected nodes from the node selector into a map of attribute keys with their allowed values + * @param filterByAttributes an array of selected node, e.g ["name:Ann","age:37"] + * @returns + */ export const computeAllowedAttributeValues = ( filterByAttributes: string[] ): Map> => { @@ -89,7 +95,11 @@ export const computeAllowedAttributeValues = ( }); return allowValues; }; - +/** + * Create a attribute predicate for well selection + * @param filterByAttributes an array of selected node, e.g ["name:Ann","age:37"] + * @returns + */ export const createAttributePredicate = ( filterByAttributes: string[] ): ((well: Well) => boolean) => { @@ -111,12 +121,18 @@ export const createAttributePredicate = ( } }); }); + //Use an AND logic between different attribute keys return (well: Well) => { return filters.every((filter) => filter(well)); }; }; -//DFS +/** + * DFS to find all leaf nodes + * @param zone + * @param result + * @returns + */ export const findSubzones = (zone: Zone, result: Zone[]): void => { if (zone === undefined) return; if (zone.subzones === undefined || zone.subzones.length === 0) @@ -130,7 +146,6 @@ export const findSubzones = (zone: Zone, result: Zone[]): void => { * @param range * @param timeAggregation * @param hideZeroCompletions - * @param filterByAttributes * @returns */ export const computeDataToPlot = (