diff --git a/.storybook/main.js b/.storybook/main.mjs similarity index 77% rename from .storybook/main.js rename to .storybook/main.mjs index e37d482385..c90b1ac53b 100644 --- a/.storybook/main.js +++ b/.storybook/main.mjs @@ -17,17 +17,17 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -const path = require('path') -module.exports = { - babel: async options => { - options.plugins.push('babel-plugin-inline-react-svg') - return options +const config = { + framework: { + name: '@storybook/react-vite', + options: {} }, stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - '@storybook/preset-scss' - ] + addons: ['@storybook/addon-links', '@storybook/preset-scss', '@storybook/addon-docs'], + core: { + builder: '@storybook/builder-vite' + } } + +export default config diff --git a/.stylelintrc.json b/.stylelintrc.json index ddee2a574c..6b6282220c 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,10 +1,40 @@ { - "extends": ["stylelint-config-standard", "stylelint-config-rational-order"], + "extends": ["stylelint-config-standard", "stylelint-config-standard-scss", "stylelint-config-rational-order"], "plugins": [ "stylelint-scss" ], "rules": { "at-rule-no-unknown": null, - "scss/at-rule-no-unknown": true + "selector-no-vendor-prefix": null, + "scss/at-mixin-argumentless-call-parentheses": null, + "scss/dollar-variable-empty-line-before": null, + "scss/at-rule-no-unknown": true, + "media-feature-range-notation": null, + "property-no-vendor-prefix": null, + "selector-class-pattern": [ + "^[a-z0-9]+(?:-[a-z0-9]+)*(?:__(?:[a-z0-9]+(?:-[a-z0-9]+)*))*(?:_(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:_[a-z0-9]+(?:-[a-z0-9]+)*)?)?$", + { + "resolveNestedSelectors": true, + "message": "Expected kebab-case or BEM: block-name, block-name__elem-name(__sub-elem…), block-name_mod-name[_mod-val], block-name__elem-name_mod-name[_mod-val]" + } + ], + "scss/at-mixin-pattern": [ + "^(?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)$", + { + "message": "Expected mixin name to be kebab-case (my-mixin) or camelCase (myMixin)" + } + ], + "custom-property-pattern": [ + "^(?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)$", + { + "message": "Expected variable name to be kebab-case (my-variable) or camelCase (myVariable)" + } + ], + "scss/dollar-variable-pattern": [ + "^(?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)$", + { + "message": "Expected variable name to be kebab-case (my-variable) or camelCase (myVariable)" + } + ] } } diff --git a/config/paths.js b/config/paths.js index b17443273e..ea07637455 100644 --- a/config/paths.js +++ b/config/paths.js @@ -83,7 +83,6 @@ module.exports = { appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveModule(resolveApp, 'src/setupTests'), - proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), appWebpackCache: resolveApp('node_modules/.cache'), appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'), diff --git a/eslint.config.mjs b/eslint.config.mjs index 7931152bd2..da24470c03 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,7 +16,7 @@ export default [ globals: { ...globals.browser, ...globals.jest, - ...globals.node, + ...globals.node }, parserOptions: { ecmaFeatures: { diff --git a/package.json b/package.json index 3c321a5fd9..af141ee1c3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@dagrejs/dagre": "^1.1.5", "@monaco-editor/react": "^4.7.0", "@reduxjs/toolkit": "^1.9.5", - "axios": "1.12.0", + "axios": "1.12.2", "bfj": "^7.0.2", "camelcase": "^6.3.0", "chart.js": "^4.4.2", @@ -32,14 +32,14 @@ "prompts": "^2.4.2", "prop-types": "^15.8.1", "qs": "^6.9.6", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", "react-final-form": "^6.5.9", "react-final-form-arrays": "^3.1.4", "react-modal-promise": "^1.0.2", "react-redux": "^7.2.9", "react-refresh": "^0.11.0", - "react-router-dom": "6.22.3", + "react-router-dom": "7.9.4", "react-text-mask": "^5.4.3", "react-transition-group": "^4.4.5", "reactflow": "^11.11.1", @@ -57,12 +57,13 @@ "start": "vite", "build": "vite build", "lint": "eslint .", + "stylelint": "stylelint '**/*.{css,scss}'", "preview": "vite preview", "preinstall": "npx force-resolutions", "test:coverage": "npm run test -- --coverage --watchAll=false", "docker": "docker build -t ${MLRUN_DOCKER_REGISTRY}${MLRUN_DOCKER_REPO:-mlrun}/mlrun-ui:${MLRUN_DOCKER_TAG:-latest} --build-arg COMMIT_HASH=\"`git rev-parse --short HEAD`\" --build-arg DATE=\"`date -u`\" -f Dockerfile .", "generate-rn": "./generate-release-notes.js ${MLRUN_OLD_VERSION} ${MLRUN_VERSION} ${MLRUN_RELEASE_BRANCH} ${MLRUN_RELEASE_TYPE}", - "storybook": "start-storybook -p 6006", + "storybook": "storybook dev -p 6006", "build-storybook": "build-storybook", "mock-server": "node scripts/mockServer.js", "mock-server:dev": "nodemon --watch tests/mockServer scripts/mockServer.js", @@ -77,89 +78,78 @@ "nui": "npm unlink iguazio.dashboard-react-controls" }, "devDependencies": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.24.1", - "@babel/node": "^7.14.9", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/core": "^7.28.4", + "@babel/node": "^7.28.0", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", "@babel/polyfill": "^7.12.1", - "@babel/preset-env": "^7.13.12", - "@babel/register": "^7.13.14", + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", + "@babel/register": "^7.28.3", "@cucumber/cucumber": "^10.3.1", "@d4c/numjs": "^0.17.34", - "@eslint/js": "^9.19.0", - "@storybook/addon-actions": "^8.0.1", - "@storybook/addon-essentials": "^8.0.1", - "@storybook/addon-links": "^8.0.1", + "@eslint/js": "^9.37.0", + "@storybook/addon-docs": "9.1.10", + "@storybook/addon-links": "^9.1.10", + "@storybook/builder-vite": "^9.1.10", "@storybook/preset-scss": "^1.0.3", + "@storybook/react-vite": "^9.1.10", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.0", "@storybook/react": "^8.0.1", "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react-swc": "^3.8.0", - "acorn": "^7.4.1", - "babel-jest": "^29.7.0", - "babel-loader": "^8.2.3", + "acorn": "^8.15.0", + "babel-jest": "^30.2.0", "babel-node": "0.0.1-security", - "babel-plugin-inline-react-svg": "^2.0.1", - "babel-plugin-jest-hoist": "^26.2.0", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-plugin-prismjs": "^2.1.0", - "babel-plugin-react-remove-properties": "^0.3.0", - "babel-preset-react-app": "^10.0.1", + "babel-plugin-inline-react-svg": "^2.0.2", + "babel-plugin-jest-hoist": "^30.0.1", "babel-runtime": "^6.26.0", - "body-parser": "^1.19.0", + "body-parser": "^2.2.0", "chai": "^4.3.4", - "chromedriver": "^142.0.0", - "cross-env": "^7.0.3", - "css-loader": "^6.5.1", - "cucumber-html-reporter": "^5.3.0", - "eslint": "^9.13.0", - "eslint-config-prettier": "^9.1.0", + "chromedriver": "^142.0.4", + "cross-env": "^10.0.0", + "cucumber-html-reporter": "^7.2.0", + "eslint": "^9.37.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.14", - "express": "^4.17.1", - "file-loader": "^6.2.0", - "geckodriver": "^3.0.1", - "globals": "^15.14.0", - "http-proxy-middleware": "^2.0.3", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.0", + "eslint-plugin-react-refresh": "^0.4.23", + "eslint-plugin-storybook": "9.1.10", + "express": "^5.1.0", + "geckodriver": "^6.0.1", + "globals": "^16.4.0", "jsdom": "^27.2.0", - "mime-types": "^2.1.35", + "mime-types": "^3.0.1", "node": "^21.6.2", - "nodemon": "^3.1.2", + "nodemon": "^3.1.10", "pandas-js": "^0.2.4", - "postcss": "^8.4.36", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^9.5.2", - "postcss-safe-parser": "7.0.0", - "prettier": "^3.3.3", + "prettier": "^3.6.2", "randexp": "^0.5.3", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", - "sass": "^1.72.0", - "sass-loader": "^12.3.2", + "sass": "^1.93.2", "selenium-webdriver": "^4.35.0", - "source-map-loader": "^5.0.0", - "stylelint": "^13.3.3", + "stylelint": "^16.24.0", "stylelint-config-rational-order": "^0.1.2", - "stylelint-config-standard": "^20.0.0", - "stylelint-order": "^4.0.0", - "stylelint-scss": "^3.17.2", + "stylelint-config-standard": "^39.0.0", + "stylelint-config-standard-scss": "^16.0.0", + "stylelint-order": "^7.0.0", + "stylelint-scss": "^6.12.1", "url-loader": "4.1.1", - "vite": "^6.2.0", + "vite": "^6.3.6", "vite-plugin-commonjs": "^0.10.4", "vite-plugin-eslint": "^1.8.1", - "vite-plugin-svgr": "^4.3.0", + "vite-plugin-svgr": "^4.5.0", "vitest": "^4.0.10" }, "babel": { "plugins": [ - "@babel/plugin-proposal-logical-assignment-operators" + "@babel/plugin-transform-logical-assignment-operators" ], "presets": [ [ @@ -168,6 +158,10 @@ "useBuiltIns": "usage", "corejs": "^3.23.3" } + ], + [ + "@babel/preset-react", + {} ] ] } diff --git a/src/common/ChipInput/chipInput.scss b/src/common/ChipInput/chipInput.scss index 564960b6b2..b467c79301 100644 --- a/src/common/ChipInput/chipInput.scss +++ b/src/common/ChipInput/chipInput.scss @@ -19,11 +19,11 @@ left: 0; z-index: 5; width: 100%; + max-height: 200px; + overflow-y: auto; background-color: colors.$white; border: borders.$primaryBorder; border-radius: variables.$mainBorderRadius; - max-height: 200px; - overflow-y: auto; .suggestion-row { display: flex; diff --git a/src/common/CodeBlock/Button.stories.js b/src/common/CodeBlock/CodeBlock.stories.jsx similarity index 96% rename from src/common/CodeBlock/Button.stories.js rename to src/common/CodeBlock/CodeBlock.stories.jsx index b976ff22e6..586dd32928 100644 --- a/src/common/CodeBlock/Button.stories.js +++ b/src/common/CodeBlock/CodeBlock.stories.jsx @@ -21,6 +21,8 @@ import React from 'react' import CodeBlock from './CodeBlock' +import 'prismjs/components/prism-json' + export default { title: 'Example/CodeBlock', component: CodeBlock diff --git a/src/common/CodeBlock/codeBlock.scss b/src/common/CodeBlock/codeBlock.scss index 20f43fb612..2595b8e4d3 100644 --- a/src/common/CodeBlock/codeBlock.scss +++ b/src/common/CodeBlock/codeBlock.scss @@ -5,10 +5,10 @@ .code-block { width: 100%; height: 200px; + padding: 18px 16px; overflow: auto; border: borders.$primaryBorder; border-radius: variables.$mainBorderRadius; - padding: 18px 16px; &__label { margin-bottom: 5px; diff --git a/src/common/Combobox/combobox.scss b/src/common/Combobox/combobox.scss index c5540b2cc9..48622ec1c2 100644 --- a/src/common/Combobox/combobox.scss +++ b/src/common/Combobox/combobox.scss @@ -12,8 +12,8 @@ border-radius: 4px; &_disabled { - cursor: not-allowed; border-color: colors.$spunPearl; + cursor: not-allowed; } &_invalid { @@ -22,10 +22,10 @@ &-list { min-width: 140px; - margin: 0; - list-style-type: none; max-height: 300px; + margin: 0; overflow: auto; + list-style-type: none; &__option { padding: 8px 15px; @@ -90,8 +90,8 @@ top: 100%; z-index: 5; display: none; - margin-top: 5px; max-width: 220px; + margin-top: 5px; background-color: colors.$white; box-shadow: shadows.$previewBoxShadow; @@ -167,8 +167,8 @@ } &:disabled { - background-color: transparent; color: colors.$spunPearl; + background-color: transparent; } } diff --git a/src/common/DatePicker/DatePicker.stories.js b/src/common/DatePicker/DatePicker.stories.jsx similarity index 100% rename from src/common/DatePicker/DatePicker.stories.js rename to src/common/DatePicker/DatePicker.stories.jsx diff --git a/src/common/DatePicker/DatePickerView.jsx b/src/common/DatePicker/DatePickerView.jsx index 1b9f143d04..663151720c 100644 --- a/src/common/DatePicker/DatePickerView.jsx +++ b/src/common/DatePicker/DatePickerView.jsx @@ -104,13 +104,10 @@ const DatePickerView = React.forwardRef( isInputInvalid && 'input_invalid' ) const inputLabelClassNames = classnames('input__label', label && 'active-label') + const { datePickerRef, datePickerViewRef } = ref return ( -
+
-
+
{datePickerOptions.map(option => ( -
+
{config.map(item => (
diff --git a/src/common/ExpandableText/ExpandableText.jsx b/src/common/ExpandableText/ExpandableText.jsx index fe38a4260f..fce738e0ae 100644 --- a/src/common/ExpandableText/ExpandableText.jsx +++ b/src/common/ExpandableText/ExpandableText.jsx @@ -35,7 +35,9 @@ const ExpandableText = ({ useEffect(() => { if (forceExpand || contextForceExpand) { - setExpanded(forceExpand || contextForceExpand) + queueMicrotask(() => { + setExpanded(forceExpand || contextForceExpand) + }) } }, [contextForceExpand, forceExpand]) diff --git a/src/common/ExpandableText/expandableText.scss b/src/common/ExpandableText/expandableText.scss index 5f5a1672ad..4b29b9fb39 100644 --- a/src/common/ExpandableText/expandableText.scss +++ b/src/common/ExpandableText/expandableText.scss @@ -5,47 +5,47 @@ } .expandable-text { - display: block; - transition: max-height 0.3s ease; position: relative; + display: block; overflow: hidden; + transition: max-height 0.3s ease; } .fade-overlay { - content: ''; position: absolute; + right: 0; bottom: 0; left: 0; - right: 0; height: 40px; - background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff); + background: linear-gradient(to bottom, rgb(255 255 255 / 0%), #fff); + content: ''; pointer-events: none; } .see-more-button-overlay { position: absolute; - bottom: 4px; right: 0; - background: white; - padding-left: 8px; + bottom: 4px; display: flex; - align-items: center; gap: 4px; + align-items: center; + padding-left: 8px; + background: white; } .dots { - font-weight: bold; color: colors.$doveGrayTwo; + font-weight: bold; } .see-more-button, .see-less-button { - background: none; - border: none; + padding: 0; color: colors.$cornflowerBlue; font-size: 14px; + background: none; + border: none; cursor: pointer; - padding: 0; } .see-less-container { diff --git a/src/common/Input/Input.stories.js b/src/common/Input/Input.stories.jsx similarity index 92% rename from src/common/Input/Input.stories.js rename to src/common/Input/Input.stories.jsx index 081e5ce056..8fdcc35de3 100644 --- a/src/common/Input/Input.stories.js +++ b/src/common/Input/Input.stories.jsx @@ -98,8 +98,8 @@ ChunkyMandatory.args = { requiredText: 'Field is required' } -export const withValidationRules = Template.bind({}) -withValidationRules.args = { +export const WithValidationRules = Template.bind({}) +WithValidationRules.args = { ...commonArgs, density: 'chunky', invalid: true, @@ -110,8 +110,8 @@ withValidationRules.args = { value: ' test#2!' } -export const withStaticLink = Template.bind({}) -withStaticLink.args = { +export const WithStaticLink = Template.bind({}) +WithStaticLink.args = { ...commonArgs, label: 'label with static link', link: { @@ -121,9 +121,9 @@ withStaticLink.args = { value: 'test' } -export const withDynamicLink = Template.bind({}) +export const WithDynamicLink = Template.bind({}) const value = 'some text' -withDynamicLink.args = { +WithDynamicLink.args = { ...commonArgs, label: 'label with dynamic link', link: { diff --git a/src/common/Input/input.scss b/src/common/Input/input.scss index b167982abf..85380f874e 100644 --- a/src/common/Input/input.scss +++ b/src/common/Input/input.scss @@ -50,10 +50,10 @@ } &__warning { - z-index: 2; position: absolute; top: 50%; right: 10px; + z-index: 2; display: block; transform: translateY(-45%); cursor: pointer; @@ -139,8 +139,8 @@ } &-mandatory { - color: colors.$amaranth; margin-left: 2px; + color: colors.$amaranth; &_disabled { color: colors.$spunPearl; diff --git a/src/common/NoData/noData.scss b/src/common/NoData/noData.scss index 489c58fafc..27c8a6623e 100644 --- a/src/common/NoData/noData.scss +++ b/src/common/NoData/noData.scss @@ -6,6 +6,6 @@ width: 100%; height: 100%; min-height: 150px; - word-break: break-word; text-align: center; + word-break: break-word; } diff --git a/src/common/Notifications/Notification.jsx b/src/common/Notifications/Notification.jsx index 23ce82c3c3..bbd754e08c 100644 --- a/src/common/Notifications/Notification.jsx +++ b/src/common/Notifications/Notification.jsx @@ -42,7 +42,7 @@ const Notification = ({ notification, timeoutMs = 10000, ...rest }) => { const nodeRef = useRef() const { pauseTimeout, resumeTimeout, cancelTimeout } = useTimeout( - () => handleRemoveNotification(notification.id), + () => dispatch(removeNotification(notification.id)), timeoutMs ) diff --git a/src/common/Notifications/notifications.scss b/src/common/Notifications/notifications.scss index 33858b5642..592ea52400 100644 --- a/src/common/Notifications/notifications.scss +++ b/src/common/Notifications/notifications.scss @@ -1,10 +1,10 @@ .notifications-wrapper { position: absolute; right: 1.5em; - margin-left: 1.5em; bottom: 1em; display: flex; flex: 1 1; flex-direction: column; max-height: 90vh; + margin-left: 1.5em; } diff --git a/src/common/Pagination/Pagination.jsx b/src/common/Pagination/Pagination.jsx index 1dedd9b324..1e32ce34af 100644 --- a/src/common/Pagination/Pagination.jsx +++ b/src/common/Pagination/Pagination.jsx @@ -57,8 +57,6 @@ const Pagination = ({ const [searchParams, setSearchParams] = useSearchParams() const navigate = useNavigate() const paginationPagesRef = useRef() - const leftSideRef = useRef(0) - const rightSideRef = useRef(0) // Total pages are now calculated based on start and end pages const totalPagesCount = useMemo( @@ -120,9 +118,6 @@ const Pagination = ({ leftSide = lastPage - 4 } - rightSideRef.current = rightSide - leftSideRef.current = leftSide - if (leftSide > firstPage + 1) { items.push(threeDotsString) } diff --git a/src/common/Pagination/pagination.scss b/src/common/Pagination/pagination.scss index 69966f57cb..dfbf2e1a40 100644 --- a/src/common/Pagination/pagination.scss +++ b/src/common/Pagination/pagination.scss @@ -2,11 +2,11 @@ @use 'igz-controls/scss/variables'; .pagination { + z-index: 1; display: flex; - justify-content: space-between; align-items: center; + justify-content: space-between; padding: 10px 4px 4px; - z-index: 1; .pagination-items-count, .pagination-items-selector { @@ -19,8 +19,8 @@ } .pagination-navigation { - flex-grow: 1; display: flex; + flex-grow: 1; justify-content: center; } @@ -30,18 +30,18 @@ } .pagination-btn { - color: colors.$primary; - cursor: default; - box-sizing: border-box; display: flex; justify-content: center; - border: none; - padding: 4px 5px; + box-sizing: border-box; margin: 0 2px; - font-size: 14px; + padding: 4px 5px; + color: colors.$primary; font-weight: normal; + font-size: 14px; + border: none; + cursor: default; - &:hover:not(.pagination-btn_active):not(:disabled) { + &:hover:not(.pagination-btn_active, :disabled) { cursor: pointer; } @@ -56,10 +56,10 @@ } .pagination-page-number { - padding: 2px 3px; box-sizing: content-box; - border-bottom: 2px solid transparent; + padding: 2px 3px; border-top: 2px solid transparent; + border-bottom: 2px solid transparent; } &.pagination-page-btn { @@ -71,8 +71,8 @@ } &.pagination-dots { - cursor: default; display: inline-flex; + cursor: default; } } diff --git a/src/common/RangeInput/RangeInput.stories.js b/src/common/RangeInput/RangeInput.stories.jsx similarity index 100% rename from src/common/RangeInput/RangeInput.stories.js rename to src/common/RangeInput/RangeInput.stories.jsx diff --git a/src/common/ReactFlow/MlReactFlow.jsx b/src/common/ReactFlow/MlReactFlow.jsx index 7bade64c25..37fe506443 100644 --- a/src/common/ReactFlow/MlReactFlow.jsx +++ b/src/common/ReactFlow/MlReactFlow.jsx @@ -64,7 +64,9 @@ const MlReactFlow = ({ alignTriggerItem = '', edges, nodes, onNodeClick = () => useEffect(() => { if (reactFlowInstance && !initialGraphViewGenerated && nodes.length > 0) { - setInitialGraphViewGenerated(true) + queueMicrotask(() => { + setInitialGraphViewGenerated(true) + }) } }, [nodes.length, initialGraphViewGenerated, reactFlowInstance]) diff --git a/src/common/ReactFlow/mlReactFlow.scss b/src/common/ReactFlow/mlReactFlow.scss index 1927c379a6..54b1195f2e 100644 --- a/src/common/ReactFlow/mlReactFlow.scss +++ b/src/common/ReactFlow/mlReactFlow.scss @@ -4,19 +4,19 @@ @mixin inputNode { color: colors.$white; background-color: colors.$hotPink; - fill: colors.$hotPink; border-color: colors.$hotPink; - stroke: colors.$hotPink; box-shadow: 0 3px 14px rgba(colors.$hotPink, 0.34); + fill: colors.$hotPink; + stroke: colors.$hotPink; } @mixin outputNode { color: colors.$white; background-color: colors.$malibu; - fill: colors.$malibu; border-color: colors.$malibu; - stroke: colors.$malibu; box-shadow: 0 3px 14px rgba(colors.$malibu, 0.5); + fill: colors.$malibu; + stroke: colors.$malibu; } @mixin primaryNode { @@ -80,20 +80,20 @@ @mixin selectedNode { &.selected { border-color: colors.$brightTurquoise; - stroke: colors.$brightTurquoise; box-shadow: 0 4px 20px rgba(colors.$brightTurquoise, 0.5); + stroke: colors.$brightTurquoise; &.status-error, &.status-failed { border-color: colors.$burntSienna; - stroke: colors.$burntSienna; box-shadow: 0 4px 20px rgba(colors.$burntSienna, 0.5); + stroke: colors.$burntSienna; } &.status-running { border-color: colors.$pictonBlue; - stroke: colors.$pictonBlue; box-shadow: 0 4px 20px rgba(colors.$pictonBlue, 0.5); + stroke: colors.$pictonBlue; } } } @@ -123,9 +123,9 @@ .react-flow__node-label { position: relative; width: 100%; + overflow: hidden; font-size: 1.5rem; text-align: center; - overflow: hidden; } .react-flow__node-sub-label { diff --git a/src/common/Search/Search.jsx b/src/common/Search/Search.jsx index e8cd1fc587..4e4e386130 100644 --- a/src/common/Search/Search.jsx +++ b/src/common/Search/Search.jsx @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useState, useEffect, useCallback, useRef } from 'react' +import React, { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' @@ -45,37 +45,30 @@ const Search = ({ wrapperClassName = '' }) => { const [searchValue, setSearchValue] = useState(value ?? '') - const [label, setLabel] = useState('') + // const [label, setLabel] = useState('') const [inputIsFocused, setInputFocused] = useState(false) + const [searchWidth, setSearchWidth] = useState(0) + const [hasMatch, setHasMatch] = useState(false) const searchRef = useRef() const popUpRef = useRef() - - const { width: searchWidth } = searchRef?.current?.getBoundingClientRect() || {} - const searchClassNames = classnames('search-container', className) - const handleSearchOnBlur = useCallback( - event => { - if ( - (event.type === 'click' && - searchRef.current && - !searchRef.current.contains(event.target)) || - (event.type === 'scroll' && popUpRef.current && !popUpRef?.current.contains(event.target)) - ) { - setInputFocused(false) - } - }, - [searchRef] - ) + // === Обчислення ширини інпута після монтування === + useLayoutEffect(() => { + if (searchRef.current) { + const { width } = searchRef.current.getBoundingClientRect() + setSearchWidth(width) + } + }, []) - useEffect(() => { - if (matches.length > 0 && searchValue.length > 0) { - setLabel( - matches.find(item => item.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())) ?? - '' - ) + const handleSearchOnBlur = useCallback(event => { + if ( + (event.type === 'click' && searchRef.current && !searchRef.current.contains(event.target)) || + (event.type === 'scroll' && popUpRef.current && !popUpRef.current.contains(event.target)) + ) { + setInputFocused(false) } - }, [matches, searchValue]) + }, []) useEffect(() => { window.addEventListener('click', handleSearchOnBlur) @@ -87,46 +80,53 @@ const Search = ({ } }, [handleSearchOnBlur]) - const searchOnChange = value => { - if (value.length === 0 && label.length > 0) { - setLabel('') - } - - onChange(value) + // === Основна логіка оновлення значення === + const handleSearchChange = value => { + const cleanValue = deleteUnsafeHtml(value) + setSearchValue(cleanValue) setInputFocused(true) - setSearchValue(deleteUnsafeHtml(value)) + onChange(cleanValue) + + const matchExists = matches.some(item => + item.toLocaleLowerCase().includes(cleanValue.toLocaleLowerCase()) + ) + setHasMatch(matchExists) } - const matchOnClick = item => { - setLabel('') + // === Клік на елемент зі списку === + const handleMatchClick = item => { setSearchValue(item) - onChange(item) + setHasMatch(false) setInputFocused(false) + onChange(item) } - const handleSearchIconClick = event => { + // === Клік по іконці пошуку === + const handleIconClick = event => { event.stopPropagation() - if (searchValue.length > 0) { + if (searchValue.trim().length > 0) { onChange(searchValue) setInputFocused(false) } } useEffect(() => { - if (searchValue.length > 0 && value !== searchValue) { - setSearchValue(value) + if (value !== searchValue) { + queueMicrotask(() => setSearchValue(value ?? '')) } }, [searchValue, value]) + const filteredMatches = inputIsFocused + ? matches.filter(item => item.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())) + : [] + return (
{ - setInputFocused(true) - }} + onClick={() => setInputFocused(true)} > } iconClass="search-icon" - iconOnClick={handleSearchIconClick} - onChange={searchOnChange} + iconOnClick={handleIconClick} + onChange={handleSearchChange} onFocus={onFocus} focused={inputIsFocused} onKeyDown={event => { - if (event.key === 'Enter' && !searchWhileTyping && searchValue !== '') { + if (event.key === 'Enter' && !searchWhileTyping && searchValue.trim() !== '') { onChange(searchValue) setInputFocused(false) } @@ -150,7 +150,8 @@ const Search = ({ value={searchValue} withoutBorder={withoutBorder} /> - {matches.length > 0 && label.length > 0 && inputIsFocused && ( + + {filteredMatches.length > 0 && hasMatch && (
    - {matches.reduce((options, item, index) => { - if (item?.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())) { - options.push( - (match ? `${match}` : match) - ) - }} - name={item} - key={item + index} - onClick={() => matchOnClick(item)} - tabIndex={index} - /> - ) - } - - return options - }, [])} + {filteredMatches.map((item, index) => ( + (match ? `${match}` : match) + ) + }} + name={item} + onClick={() => handleMatchClick(item)} + tabIndex={index} + /> + ))}
)} diff --git a/src/common/Search/search.scss b/src/common/Search/search.scss index 377b71fd9c..6d537e1384 100644 --- a/src/common/Search/search.scss +++ b/src/common/Search/search.scss @@ -19,6 +19,7 @@ &-input { z-index: 2; width: 100%; + // todo: delete `!important` after replacing `Input` with `FormInput` padding: 10px 20px 10px 35px !important; color: colors.$topaz; @@ -41,9 +42,9 @@ } &-matches { + max-height: 250px; margin: 0; padding: 0; - max-height: 250px; &__item { padding: 7px 15px; diff --git a/src/common/SearchNavigator/searchNavigator.scss b/src/common/SearchNavigator/searchNavigator.scss index 020ed961fd..b8a810408c 100644 --- a/src/common/SearchNavigator/searchNavigator.scss +++ b/src/common/SearchNavigator/searchNavigator.scss @@ -10,20 +10,20 @@ &__counter { display: flex; - font-size: 13px; color: colors.$topaz; + font-size: 13px; - &:after { + &::after { display: none; - content: ''; width: 5px; height: 15px; margin: 0 5px 0 8px; border-right: borders.$primaryBorder; + content: ''; } &_with-divider { - &:after { + &::after { display: block; } } diff --git a/src/common/Select/Select.jsx b/src/common/Select/Select.jsx index 6a0b9f1840..528ecbd92f 100644 --- a/src/common/Select/Select.jsx +++ b/src/common/Select/Select.jsx @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useState, useEffect, useCallback, useRef } from 'react' +import React, { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' @@ -56,7 +56,7 @@ const Select = ({ const [isConfirmDialogOpen, setConfirmDialogOpen] = useState(false) const [isOpen, setOpen] = useState(false) const [searchValue, setSearchValue] = useState('') - const { width: dropdownWidth } = selectRef?.current?.getBoundingClientRect() || {} + const [dropdownWidth, setDropdownWidth] = useState(0) const selectClassName = classNames( 'select', className, @@ -77,6 +77,18 @@ const Select = ({ ) const selectedOption = options.find(option => option.id === selectedId) + const clickHandler = event => { + if (selectRef.current !== event.target.closest('.select')) { + setOpen(false) + } + } + + const handleScroll = event => { + if (!event.target.closest('.select__body')) { + setOpen(false) + } + } + useEffect(() => { if (isOpen) { window.addEventListener('scroll', handleScroll, true) @@ -90,18 +102,6 @@ const Select = ({ } }, [isOpen]) - const clickHandler = event => { - if (selectRef.current !== event.target.closest('.select')) { - setOpen(false) - } - } - - const handleScroll = event => { - if (!event.target.closest('.select__body')) { - setOpen(false) - } - } - const toggleOpen = () => { !disabled && setOpen(!isOpen) } @@ -115,6 +115,13 @@ const Select = ({ } }, []) + useLayoutEffect(() => { + if (selectRef.current) { + const { width } = selectRef.current.getBoundingClientRect() + setDropdownWidth(width) + } + }, [selectRef]) + const handleSelectOptionClick = (selectedOption, option) => { if (selectedOption !== selectedId) { option.handler && option.handler() diff --git a/src/common/Select/Select.stories.js b/src/common/Select/Select.stories.jsx similarity index 100% rename from src/common/Select/Select.stories.js rename to src/common/Select/Select.stories.jsx diff --git a/src/common/Sort/Sort.jsx b/src/common/Sort/Sort.jsx index bc08550d2c..22c0774304 100644 --- a/src/common/Sort/Sort.jsx +++ b/src/common/Sort/Sort.jsx @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useEffect, useState } from 'react' +import React from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' @@ -35,16 +35,12 @@ const Sort = ({ selectedId, setIsDescendingOrder }) => { - const [selectedOption, setSelectedOption] = useState(null) + const selectedOption = options.find(option => option.id === selectedId) const arrowDirectionClassName = classNames( 'sort-icon', isDescendingOrder ? 'sort-icon_down' : 'sort-icon_up' ) - useEffect(() => { - setSelectedOption(options.find(option => option.id === selectedId)) - }, [options, selectedId]) - return (
* { - fill: currentColor; + fill: currentcolor; } } } diff --git a/src/common/StatsCard/statsCard.scss b/src/common/StatsCard/statsCard.scss index 6f34f01682..2deaed945a 100644 --- a/src/common/StatsCard/statsCard.scss +++ b/src/common/StatsCard/statsCard.scss @@ -69,9 +69,9 @@ transition: all 0.3s ease-in-out; @media screen and (min-width: 1600px) { + top: -2px; width: 20px; height: 20px; - top: -2px; } svg { diff --git a/src/common/TargetPath/TargetPath.jsx b/src/common/TargetPath/TargetPath.jsx index 84971086c1..d2f1e383d7 100644 --- a/src/common/TargetPath/TargetPath.jsx +++ b/src/common/TargetPath/TargetPath.jsx @@ -80,21 +80,35 @@ const TargetPath = ({ } } + const handlePathChange = useCallback( + value => { + if (value.length !== 0) { + formState.form.change(`${formStateFieldInfo}.value`, value.replace(/[^:/]*:[/]{2,3}/, '')) + formState.form.change(`${formStateFieldInfo}.pathType`, value.match(/^\w*:[/]{2,3}/)[0]) + + if (formStateDataInputState) { + formState.form.change(`${formStateDataInputState}`, dataInputState) + } + } + }, + [dataInputState, formState.form, formStateDataInputState, formStateFieldInfo] + ) + const handleGetProjectsNames = useCallback(() => { getProjectsNames(dispatch, setDataInputState, params.projectName) - }, [dispatch, params.projectName]) + }, [dispatch, setDataInputState, params.projectName]) const handleGetArtifacts = useCallback(() => { getArtifacts(dispatch, dataInputState.project, dataInputState.storePathType, setDataInputState) - }, [dataInputState.project, dataInputState.storePathType, dispatch]) + }, [dataInputState.project, dataInputState.storePathType, dispatch, setDataInputState]) const handleGetFeatureVectors = useCallback(() => { getFeatureVectors(dispatch, dataInputState.project, setDataInputState) - }, [dataInputState.project, dispatch]) + }, [dataInputState.project, dispatch, setDataInputState]) const handleGetArtifact = useCallback(() => { getArtifact(dispatch, dataInputState.project, dataInputState.projectItem, setDataInputState) - }, [dataInputState.project, dataInputState.projectItem, dispatch]) + }, [dataInputState.project, dataInputState.projectItem, dispatch, setDataInputState]) const handleGetFeatureVector = useCallback(() => { getFeatureVector( @@ -103,7 +117,7 @@ const TargetPath = ({ dataInputState.projectItem, setDataInputState ) - }, [dataInputState.project, dataInputState.projectItem, dispatch]) + }, [dataInputState.project, dataInputState.projectItem, dispatch, setDataInputState]) useEffect(() => { if (dataInputState.inputStorePathTypeEntered && dataInputState.projects.length === 0) { @@ -120,24 +134,27 @@ const TargetPath = ({ if ( get(formState.values, `${formStateFieldInfo}.pathType`) === MLRUN_STORAGE_INPUT_PATH_SCHEME ) { - setDataInputState(prev => ({ - ...prev, - comboboxMatches: generateComboboxMatchesList( - dataInputState.artifacts, - dataInputState.artifactsReferences, - dataInputState.featureVectors, - dataInputState.featureVectorsReferences, - dataInputState.inputProjectItemPathEntered, - dataInputState.inputProjectItemReferencePathEntered, - dataInputState.inputProjectPathEntered, - dataInputState.inputStorePathTypeEntered, - dataInputState.project, - dataInputState.projectItem, - dataInputState.projectItemReference, - dataInputState.projects, - dataInputState.storePathType - ) - })) + //TODO + setTimeout(() => { + setDataInputState(prev => ({ + ...prev, + comboboxMatches: generateComboboxMatchesList( + dataInputState.artifacts, + dataInputState.artifactsReferences, + dataInputState.featureVectors, + dataInputState.featureVectorsReferences, + dataInputState.inputProjectItemPathEntered, + dataInputState.inputProjectItemReferencePathEntered, + dataInputState.inputProjectPathEntered, + dataInputState.inputStorePathTypeEntered, + dataInputState.project, + dataInputState.projectItem, + dataInputState.projectItemReference, + dataInputState.projects, + dataInputState.storePathType + ) + })) + }, 0) } }, [ dataInputState.artifacts, @@ -215,20 +232,6 @@ const TargetPath = ({ handleGetFeatureVector ]) - const handlePathChange = useCallback( - value => { - if (value.length !== 0) { - formState.form.change(`${formStateFieldInfo}.value`, value.replace(/[^:/]*:[/]{2,3}/, '')) - formState.form.change(`${formStateFieldInfo}.pathType`, value.match(/^\w*:[/]{2,3}/)[0]) - - if (formStateDataInputState) { - formState.form.change(`${formStateDataInputState}`, dataInputState) - } - } - }, - [dataInputState, formState.form, formStateDataInputState, formStateFieldInfo] - ) - return ( <> { - const initialValues = { + return { [AUTO_REFRESH_ID]: autoRefreshIsEnabled, [INTERNAL_AUTO_REFRESH_ID]: internalAutoRefreshIsEnabled, ...formFiltersInitialValues } - - return initialValues }, [autoRefreshIsEnabled, formFiltersInitialValues, internalAutoRefreshIsEnabled]) - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues: formInitialValues, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) const filterMenuModalInitialState = useMemo(() => { return mapValues( @@ -187,7 +184,8 @@ const ActionBar = ({ const applyFilters = useCallback( async (formValues, filters, actionCanBePerformedChecked) => { - const actionCanBePerformed = actionCanBePerformedChecked || await performDetailsActionHelper(changes, dispatch, true) + const actionCanBePerformed = + actionCanBePerformedChecked || (await performDetailsActionHelper(changes, dispatch, true)) const newFilters = { ...filters, ...formValues } if (actionCanBePerformed) { @@ -277,14 +275,14 @@ const ActionBar = ({ } useEffect(() => { - if (!isEqual(formRef.current?.getState().values, filterMenu)) { - formRef.current?.batch(() => { + if (!isEqual(form.getState().values, filterMenu)) { + form.batch(() => { for (const filterName in filterMenu) { - formRef.current?.change(filterName, filterMenu[filterName]) + form.change(filterName, filterMenu[filterName]) } }) } - }, [filterMenu, filtersConfig]) + }, [filterMenu, filtersConfig, form]) useEffect(() => { if ( @@ -294,7 +292,7 @@ const ActionBar = ({ ) { const intervalId = setInterval(() => { if (!autoRefreshIsStopped) { - refresh(formRef.current.getState()) + refresh(form.getState()) } }, 30000) @@ -306,19 +304,24 @@ const ActionBar = ({ refresh, withInternalAutoRefresh, filtersStore.internalAutoRefresh, - filtersStore.autoRefresh + filtersStore.autoRefresh, + form ]) useEffect(() => { if (autoRefreshStopTrigger && filtersStore.internalAutoRefresh) { - formRef.current?.change(INTERNAL_AUTO_REFRESH_ID, false) - setInternalAutoRefreshPrevValue(true) + form.change(INTERNAL_AUTO_REFRESH_ID, false) + queueMicrotask(() => { + setInternalAutoRefreshPrevValue(true) + }) dispatch(toggleInternalAutoRefresh(false)) handleAutoRefreshPrevValueChange && handleAutoRefreshPrevValueChange(true) } else if (!autoRefreshStopTrigger && internalAutoRefreshPrevValue) { - setInternalAutoRefreshPrevValue(false) + queueMicrotask(() => { + setInternalAutoRefreshPrevValue(false) + }) dispatch(toggleInternalAutoRefresh(true)) - formRef.current?.change(INTERNAL_AUTO_REFRESH_ID, true) + form.change(INTERNAL_AUTO_REFRESH_ID, true) handleAutoRefreshPrevValueChange && handleAutoRefreshPrevValueChange(false) } }, [ @@ -326,7 +329,8 @@ const ActionBar = ({ autoRefreshStopTrigger, handleAutoRefreshPrevValueChange, dispatch, - filtersStore.internalAutoRefresh + filtersStore.internalAutoRefresh, + form ]) useEffect(() => { @@ -336,21 +340,21 @@ const ActionBar = ({ }, []) useLayoutEffect(() => { - const prevValues = formRef.current.getState().values + const prevValues = form.getState().values const valuesToReset = { [INTERNAL_AUTO_REFRESH_ID]: prevValues[INTERNAL_AUTO_REFRESH_ID], [AUTO_REFRESH_ID]: prevValues[AUTO_REFRESH_ID], ...formFiltersInitialValues } - formRef.current.reset(valuesToReset) - }, [formFiltersInitialValues]) + form.reset(valuesToReset) + }, [formFiltersInitialValues, form]) useLayoutEffect(() => { - formRef.current?.batch(() => { - formRef.current?.change(AUTO_REFRESH_ID, autoRefreshIsEnabled) - formRef.current?.change(INTERNAL_AUTO_REFRESH_ID, internalAutoRefreshIsEnabled) + form.batch(() => { + form.change(AUTO_REFRESH_ID, autoRefreshIsEnabled) + form.change(INTERNAL_AUTO_REFRESH_ID, internalAutoRefreshIsEnabled) }) - }, [autoRefreshIsEnabled, internalAutoRefreshIsEnabled]) + }, [autoRefreshIsEnabled, form, internalAutoRefreshIsEnabled]) useEffect(() => { dispatch(toggleAutoRefresh(false)) @@ -358,7 +362,7 @@ const ActionBar = ({ }, [dispatch, params.projectName]) return ( -
{}}> + {}}> {formState => (
@@ -404,7 +408,9 @@ const ActionBar = ({
{!isEmpty(filterMenuModalInitialState) && ( applyFilters(formState.values, filterMenuModal, actionCanBePerformedChecked)} + applyChanges={(filterMenuModal, actionCanBePerformedChecked) => + applyFilters(formState.values, filterMenuModal, actionCanBePerformedChecked) + } initialValues={filterMenuModalInitialState} values={filterMenuModal} detailsChanges={changes} diff --git a/src/components/ArtifactInfoSources/artifactInfoSources.scss b/src/components/ArtifactInfoSources/artifactInfoSources.scss index 60e988a157..9ccc5bf3cd 100644 --- a/src/components/ArtifactInfoSources/artifactInfoSources.scss +++ b/src/components/ArtifactInfoSources/artifactInfoSources.scss @@ -39,8 +39,8 @@ display: inline-block; flex: 1 1; min-width: 110px; - word-break: break-word; color: colors.$topaz; + word-break: break-word; &__copy-to-clipboard { display: flex; diff --git a/src/components/Artifacts/artifacts.scss b/src/components/Artifacts/artifacts.scss index 9a5c65d1db..b26b375d43 100644 --- a/src/components/Artifacts/artifacts.scss +++ b/src/components/Artifacts/artifacts.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $artifactsRowHeight: variables.$rowHeight; $artifactsHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/ArtifactsPreview/artifactsPreview.scss b/src/components/ArtifactsPreview/artifactsPreview.scss index 9c8feb71c2..bbcc290cba 100644 --- a/src/components/ArtifactsPreview/artifactsPreview.scss +++ b/src/components/ArtifactsPreview/artifactsPreview.scss @@ -20,7 +20,7 @@ &-title { display: flex; align-items: center; - margin: 0 0 15px 0; + margin: 0 0 15px; color: colors.$topaz; font-weight: 500; font-size: 20px; diff --git a/src/components/ArtifactsPreview/artifactsPreviewController.scss b/src/components/ArtifactsPreview/artifactsPreviewController.scss index 9a9f12a7f3..94573d135a 100644 --- a/src/components/ArtifactsPreview/artifactsPreviewController.scss +++ b/src/components/ArtifactsPreview/artifactsPreviewController.scss @@ -10,11 +10,11 @@ } .icon-popout { - text-align: end; - cursor: pointer; - min-height: 40px; width: 100%; height: auto; - padding: 0; + min-height: 40px; margin-bottom: 10px; + padding: 0; + text-align: end; + cursor: pointer; } diff --git a/src/components/Details/Details.jsx b/src/components/Details/Details.jsx index 1bed60c636..10617d5f5e 100644 --- a/src/components/Details/Details.jsx +++ b/src/components/Details/Details.jsx @@ -81,7 +81,7 @@ const Details = ({ detailsRef, commonDetailsStore, doNotLeavePage, - formRef, + form, handleShowWarning, leavePage, location, @@ -180,7 +180,7 @@ const Details = ({ detailsStore={detailsStore} commonDetailsStore={commonDetailsStore} doNotLeavePage={doNotLeavePage} - formRef={formRef} + form={form} isDetailsPopUp={isDetailsPopUp} leavePage={leavePage} renderHeader={() => ( @@ -201,7 +201,7 @@ const Details = ({ withActionMenu={withActionMenu} /> )} - renderTabsContent={(formState) => ( + renderTabsContent={formState => ( { - if (!isEmpty(selectedItem.prompt_template)) { - if (!isPromptTemplateValid(selectedItem.prompt_template)) { - setShowError(true) - } else { - setPromptTemplate( - generateJsxContent(selectedItem.prompt_template, selectedItem.prompt_legend) - ) - } - } else if (isEmpty(artifactsStore.LLMPrompts.promptTemplate)) { - if ( - !selectedItem.target_path.endsWith('.txt') && - !selectedItem.target_path.endsWith('.json') - ) { - setShowError(true) - } else { - setLoading(true) - dispatch( - fetchLLMPromptTemplate({ - project: selectedItem.project, - config: { - params: { - path: selectedItem.target_path + queueMicrotask(() => { + if (!isEmpty(selectedItem.prompt_template)) { + if (!isPromptTemplateValid(selectedItem.prompt_template)) { + setShowError(true) + } else { + setPromptTemplate( + generateJsxContent(selectedItem.prompt_template, selectedItem.prompt_legend) + ) + } + } else if (isEmpty(artifactsStore.LLMPrompts.promptTemplate)) { + if ( + !selectedItem.target_path.endsWith('.txt') && + !selectedItem.target_path.endsWith('.json') + ) { + setShowError(true) + } else { + setLoading(true) + dispatch( + fetchLLMPromptTemplate({ + project: selectedItem.project, + config: { + params: { + path: selectedItem.target_path + } } - } - }) - ) - .unwrap() - .then(response => { - if (!isPromptTemplateValid(response.data)) { - setShowError(true) - } else { - setPromptTemplate(generateJsxContent(response.data, selectedItem.prompt_legend)) - } - }) - .catch(() => setShowError(true)) - .finally(() => { - setLoading(false) - }) - } - } else if (!isEmpty(artifactsStore.LLMPrompts.promptTemplate)) { - if (!isPromptTemplateValid(artifactsStore.LLMPrompts.promptTemplate)) { - return setShowError(true) - } else { - setPromptTemplate( - generateJsxContent(artifactsStore.LLMPrompts.promptTemplate, selectedItem.prompt_legend) - ) + }) + ) + .unwrap() + .then(response => { + if (!isPromptTemplateValid(response.data)) { + setShowError(true) + } else { + setPromptTemplate(generateJsxContent(response.data, selectedItem.prompt_legend)) + } + }) + .catch(() => setShowError(true)) + .finally(() => { + setLoading(false) + }) + } + } else if (!isEmpty(artifactsStore.LLMPrompts.promptTemplate)) { + if (!isPromptTemplateValid(artifactsStore.LLMPrompts.promptTemplate)) { + return setShowError(true) + } else { + setPromptTemplate( + generateJsxContent(artifactsStore.LLMPrompts.promptTemplate, selectedItem.prompt_legend) + ) + } } - } + }) }, [ selectedItem.prompt_template, selectedItem.prompt_legend, diff --git a/src/components/DetailsAnalysis/DetailsAnalysis.jsx b/src/components/DetailsAnalysis/DetailsAnalysis.jsx index 83f91a6eba..3db8deaffd 100644 --- a/src/components/DetailsAnalysis/DetailsAnalysis.jsx +++ b/src/components/DetailsAnalysis/DetailsAnalysis.jsx @@ -63,12 +63,16 @@ const DetailsAnalysis = ({ artifact }) => { fetchPreviewFromAnalysis() } else { showErrorNotification(dispatch, '', '', 'The analysis type is malformed. Expected dict') - setNoData(true) + queueMicrotask(() => { + setNoData(true) + }) } previewIsFetchedRef.current = true } else if (!artifact.analysis || isEmpty(artifact.analysis)) { - setNoData(true) + queueMicrotask(() => { + setNoData(true) + }) } }, [artifact.analysis, fetchPreviewFromAnalysis, preview.length, frontendSpec, dispatch]) diff --git a/src/components/DetailsCode/DetailsCode.jsx b/src/components/DetailsCode/DetailsCode.jsx index b97ebbd6fd..7e873556fc 100644 --- a/src/components/DetailsCode/DetailsCode.jsx +++ b/src/components/DetailsCode/DetailsCode.jsx @@ -32,7 +32,9 @@ const DetailsCode = ({ code = '' }) => { }, [code]) useEffect(() => { - decodeCode() + queueMicrotask(() => { + decodeCode() + }) }, [decodeCode]) const html = Prism.highlight(decoded, Prism.languages.py, 'py') @@ -53,7 +55,7 @@ const DetailsCode = ({ code = '' }) => { } DetailsCode.propTypes = { - code: PropTypes.string, + code: PropTypes.string } export default DetailsCode diff --git a/src/components/DetailsFeaturesAnalysis/DetailsFeaturesAnalysis.jsx b/src/components/DetailsFeaturesAnalysis/DetailsFeaturesAnalysis.jsx index bef5e7d90d..95756b78f0 100644 --- a/src/components/DetailsFeaturesAnalysis/DetailsFeaturesAnalysis.jsx +++ b/src/components/DetailsFeaturesAnalysis/DetailsFeaturesAnalysis.jsx @@ -33,7 +33,7 @@ import './detailsFeaturesAnalysis.scss' const DetailsFeaturesAnalysis = ({ selectedItem }) => { const table = generateFeaturesAnalysis(selectedItem) const amethystColor = useMemo(() => getScssVariableValue('--amethystColor'), []) - const chartConfig = useMemo(getHistogramChartConfig, []) + const chartConfig = useMemo(() => getHistogramChartConfig, []) return (
diff --git a/src/components/DetailsInfo/detailsInfo.scss b/src/components/DetailsInfo/detailsInfo.scss index fc4741f8e8..cfc2613c68 100644 --- a/src/components/DetailsInfo/detailsInfo.scss +++ b/src/components/DetailsInfo/detailsInfo.scss @@ -26,13 +26,13 @@ $itemInfoWithoutPadding: item-info-without-padding; &__header { margin: 0; - padding: 10px 0 7.5px 0; + padding: 10px 0 7.5px; font-size: 18px; } &__details { margin: 0 0 21px; - padding: 0 0 0 0; + padding: 0; list-style-type: none; &-wrapper { diff --git a/src/components/DetailsMetrics/DetailsMetrics.jsx b/src/components/DetailsMetrics/DetailsMetrics.jsx index 1b037a51ae..8f76078faa 100644 --- a/src/components/DetailsMetrics/DetailsMetrics.jsx +++ b/src/components/DetailsMetrics/DetailsMetrics.jsx @@ -136,7 +136,9 @@ const DetailsMetrics = ({ const selectedDate = detailsStore.dates.selectedOptionId if (!selectedDate || !(selectedDate in timeRangeMapping)) return - setSelectedDate(timeRangeMapping[selectedDate]) + queueMicrotask(() => { + setSelectedDate(timeRangeMapping[selectedDate]) + }) }, [detailsStore.dates.selectedOptionId]) const fetchData = useCallback( @@ -220,7 +222,9 @@ const DetailsMetrics = ({ selectedItem.metadata.uid ) } else { - setMetrics([]) + queueMicrotask(() => { + setMetrics([]) + }) } return () => { diff --git a/src/components/DetailsMetrics/DetailsMetrics.scss b/src/components/DetailsMetrics/DetailsMetrics.scss index e012f3d79b..f8d9158357 100644 --- a/src/components/DetailsMetrics/DetailsMetrics.scss +++ b/src/components/DetailsMetrics/DetailsMetrics.scss @@ -21,7 +21,7 @@ $stickyHeaderHeight: 55px; background-color: colors.$white; > * { - margin: 0 0 15px 0; + margin: 0 0 15px; } .details-date-picker { diff --git a/src/components/DetailsPods/DetailsPods.jsx b/src/components/DetailsPods/DetailsPods.jsx index ae07539d5a..70c76c036c 100644 --- a/src/components/DetailsPods/DetailsPods.jsx +++ b/src/components/DetailsPods/DetailsPods.jsx @@ -43,7 +43,9 @@ const DetailsPods = ({ isDetailsPopUp = false, noDataMessage = '' }) => { }, [detailsStore.detailsJobPods, detailsStore.pods, isDetailsPopUp]) useEffect(() => { - setTable(generatePods(podsData)) + queueMicrotask(() => { + setTable(generatePods(podsData)) + }) return () => { setSelectedPod(null) @@ -52,7 +54,9 @@ const DetailsPods = ({ isDetailsPopUp = false, noDataMessage = '' }) => { useEffect(() => { if (!selectedPod) { - setSelectedPod(table[0]) + queueMicrotask(() => { + setSelectedPod(table[0]) + }) } }, [selectedPod, table]) diff --git a/src/components/DetailsRequestedFeatures/detailsRequestedFeatures.scss b/src/components/DetailsRequestedFeatures/detailsRequestedFeatures.scss index fc59156a0b..bcd718a217 100644 --- a/src/components/DetailsRequestedFeatures/detailsRequestedFeatures.scss +++ b/src/components/DetailsRequestedFeatures/detailsRequestedFeatures.scss @@ -10,6 +10,7 @@ &-header { position: sticky; top: 0; + z-index: 3; display: flex; flex-direction: row; align-items: center; @@ -20,7 +21,6 @@ line-height: 24px; background-color: colors.$white; border-bottom: borders.$secondaryBorder; - z-index: 3; } &-cell { diff --git a/src/components/DetailsResults/detailsResults.scss b/src/components/DetailsResults/detailsResults.scss index 1b5d47ebf3..3f74e999a7 100644 --- a/src/components/DetailsResults/detailsResults.scss +++ b/src/components/DetailsResults/detailsResults.scss @@ -3,8 +3,8 @@ @use 'igz-controls/scss/mixins'; .table__item-results { - display: flex; position: relative; + display: flex; .table { i { diff --git a/src/components/DetailsStatistics/DetailsStatistics.jsx b/src/components/DetailsStatistics/DetailsStatistics.jsx index e764adc51d..aebfee8431 100644 --- a/src/components/DetailsStatistics/DetailsStatistics.jsx +++ b/src/components/DetailsStatistics/DetailsStatistics.jsx @@ -58,7 +58,7 @@ const DetailsStatistics = ({ selectedItem }) => { }), [detailsStatisticsHeaderRowHeight, detailsStatisticsRowHeight] ) - const chartConfig = useMemo(getHistogramChartConfig, []) + const chartConfig = useMemo(() => getHistogramChartConfig, []) const headers = useMemo( () => Object.entries(statistics[0]).map(([label, value]) => ({ diff --git a/src/components/DetailsStatistics/DetailsStatisticsTableRow.jsx b/src/components/DetailsStatistics/DetailsStatisticsTableRow.jsx index c7edce2eed..1d496a454d 100644 --- a/src/components/DetailsStatistics/DetailsStatisticsTableRow.jsx +++ b/src/components/DetailsStatistics/DetailsStatisticsTableRow.jsx @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { memo, useMemo } from 'react' +import React, { memo, useId, useMemo } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' @@ -30,6 +30,7 @@ import './detailsStatistics.scss' const DetailsStatisticsTableRow = ({ statisticsItem, headers, chartConfig }) => { const amethystColor = useMemo(() => getScssVariableValue('--amethystColor'), []) + const id = useId() return (
@@ -79,7 +80,7 @@ const DetailsStatisticsTableRow = ({ statisticsItem, headers, chartConfig }) => } return ( -
+
{statisticsValue.type.match(/icon/) && !statisticsValue.hidden && statisticsValue.value} {statisticsValue.type === 'chart' && statisticsValue.value[1]?.length > 0 && ( diff --git a/src/components/DetailsStatistics/detailsStatistics.scss b/src/components/DetailsStatistics/detailsStatistics.scss index 75601d6efb..d24b6f49f5 100644 --- a/src/components/DetailsStatistics/detailsStatistics.scss +++ b/src/components/DetailsStatistics/detailsStatistics.scss @@ -21,7 +21,7 @@ $detailsStatisticsHeaderRowHeight: 49px; &-wrapper { width: 100%; - padding: 0 0 20px 0; + padding: 0 0 20px; } &-header { diff --git a/src/components/DetailsTransformations/DetailsTransformations.jsx b/src/components/DetailsTransformations/DetailsTransformations.jsx index ee2edb1892..99feec393d 100644 --- a/src/components/DetailsTransformations/DetailsTransformations.jsx +++ b/src/components/DetailsTransformations/DetailsTransformations.jsx @@ -155,34 +155,40 @@ const DetailsTransformations = ({ selectedItem }) => { }, [states, targets, selectedStep]) useEffect(() => { - setStates(cloneDeep(selectedItem.graph?.steps)) - setTargets(cloneDeep(selectedItem.targets)) + queueMicrotask(() => { + setStates(cloneDeep(selectedItem.graph?.steps)) + setTargets(cloneDeep(selectedItem.targets)) + }) }, [selectedItem.graph, selectedItem.targets]) useEffect(() => { - let stepsList = reject(steps, ['id', selectedStep]) + queueMicrotask(() => { + let stepsList = reject(steps, ['id', selectedStep]) - setErrorSteps(reject(stepsList, ['id', 'Source'])) - stepsList.unshift({ - id: 'Source', - label: 'Source' - }) + setErrorSteps(reject(stepsList, ['id', 'Source'])) + stepsList.unshift({ + id: 'Source', + label: 'Source' + }) - setAfterSteps(stepsList) + setAfterSteps(stepsList) + }) }, [steps, selectedStep]) useEffect(() => { - setNodes(nodes => { - return map(nodes, node => { - return { - ...node, - className: - node.id === selectedStep - ? node.className - ? (node.className += ' selected') - : 'selected' - : node.className?.replace('selected', '') - } + queueMicrotask(() => { + setNodes(nodes => { + return map(nodes, node => { + return { + ...node, + className: + node.id === selectedStep + ? node.className + ? (node.className += ' selected') + : 'selected' + : node.className?.replace('selected', '') + } + }) }) }) }, [selectedStep]) @@ -193,7 +199,9 @@ const DetailsTransformations = ({ selectedItem }) => { useEffect(() => { if (selectedItem.uid !== selectedItemUid) { - setSelectedItemUid(selectedItem.uid) + queueMicrotask(() => { + setSelectedItemUid(selectedItem.uid) + }) } }, [selectedItem, selectedItemUid]) diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanel.jsx b/src/components/FeatureSetsPanel/FeatureSetsPanel.jsx index b864ff6c12..dbbed3bcee 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanel.jsx +++ b/src/components/FeatureSetsPanel/FeatureSetsPanel.jsx @@ -73,13 +73,13 @@ const FeatureSetsPanel = ({ closePanel, createFeatureSetSuccess, project }) => { const [accessKeyRequired, setAccessKeyRequired] = useState(false) const navigate = useNavigate() const dispatch = useDispatch() - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues: { labels: [] }, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) const handleSave = () => { let data = { @@ -87,7 +87,7 @@ const FeatureSetsPanel = ({ closePanel, createFeatureSetSuccess, project }) => { ...featureStore.newFeatureSet, metadata: { ...featureStore.newFeatureSet.metadata, - labels: convertChipsData(formRef.current.getFieldState('labels')?.value), + labels: convertChipsData(form.getFieldState('labels')?.value), tag: featureStore.newFeatureSet.metadata.tag || TAG_FILTER_LATEST } } @@ -171,7 +171,7 @@ const FeatureSetsPanel = ({ closePanel, createFeatureSetSuccess, project }) => { } return createPortal( - {}}> + {}}> {formState => { return ( <> @@ -195,7 +195,7 @@ const FeatureSetsPanel = ({ closePanel, createFeatureSetSuccess, project }) => { { - const areLabelsValid = formRef.current?.getFieldState?.('labels')?.valid ?? true + const areLabelsValid = form?.getFieldState?.('labels')?.valid ?? true setValidation(prevState => { if (prevState.areLabelsValid === areLabelsValid) { return prevState diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.jsx b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.jsx index a8ed0bec34..8afe73cdc0 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.jsx +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.jsx @@ -92,37 +92,39 @@ const FeatureSetsPanelTargetStore = ({ ) useEffect(() => { - if (!targetsPathEditData.online.isModified && !targetsPathEditData.online.isEditMode) { - setData(state => ({ - ...state, - online: { - ...state.online, - path: generatePath( - frontendSpec.feature_store_data_prefixes, - project, - state.online.kind, - featureStore.newFeatureSet.metadata.name, - '' - ) - } - })) - } + queueMicrotask(() => { + if (!targetsPathEditData.online.isModified && !targetsPathEditData.online.isEditMode) { + setData(state => ({ + ...state, + online: { + ...state.online, + path: generatePath( + frontendSpec.feature_store_data_prefixes, + project, + state.online.kind, + featureStore.newFeatureSet.metadata.name, + '' + ) + } + })) + } - if (!targetsPathEditData.parquet.isModified && !targetsPathEditData.parquet.isEditMode) { - setData(state => ({ - ...state, - parquet: { - ...state.parquet, - path: generatePath( - frontendSpec.feature_store_data_prefixes, - project, - PARQUET, - featureStore.newFeatureSet.metadata.name, - state.parquet?.partitioned ? '' : PARQUET - ) - } - })) - } + if (!targetsPathEditData.parquet.isModified && !targetsPathEditData.parquet.isEditMode) { + setData(state => ({ + ...state, + parquet: { + ...state.parquet, + path: generatePath( + frontendSpec.feature_store_data_prefixes, + project, + PARQUET, + featureStore.newFeatureSet.metadata.name, + state.parquet?.partitioned ? '' : PARQUET + ) + } + })) + } + }) }, [ featureStore.newFeatureSet.metadata.name, featureStore.newFeatureSet.spec.source.kind, @@ -207,29 +209,31 @@ const FeatureSetsPanelTargetStore = ({ ]) useEffect(() => { - if (isEmpty(frontendSpec.feature_store_data_prefixes)) { - setTargetsPathEditData(state => ({ - ...state, - [PARQUET]: { - ...state[PARQUET], - isEditMode: true - }, - [ONLINE]: { - ...state[ONLINE], - isEditMode: true - } - })) - setDisableButtons(state => ({ - ...state, - isOfflineTargetPathEditModeClosed: false, - isOnlineTargetPathEditModeClosed: false - })) - setValidation(state => ({ - ...state, - isOfflineTargetPathValid: false, - isOnlineTargetPathValid: false - })) - } + queueMicrotask(() => { + if (isEmpty(frontendSpec.feature_store_data_prefixes)) { + setTargetsPathEditData(state => ({ + ...state, + [PARQUET]: { + ...state[PARQUET], + isEditMode: true + }, + [ONLINE]: { + ...state[ONLINE], + isEditMode: true + } + })) + setDisableButtons(state => ({ + ...state, + isOfflineTargetPathEditModeClosed: false, + isOnlineTargetPathEditModeClosed: false + })) + setValidation(state => ({ + ...state, + isOfflineTargetPathValid: false, + isOnlineTargetPathValid: false + })) + } + }) }, [frontendSpec.feature_store_data_prefixes, setDisableButtons, setValidation]) useEffect(() => { @@ -674,56 +678,58 @@ const FeatureSetsPanelTargetStore = ({ ]) useEffect(() => { - if (featureStore.newFeatureSet.spec.passthrough && !passthroughtEnabled) { - setPreviousTargets({ - data: { - ...data, - [PARQUET]: { - ...data[PARQUET], - path: data[PARQUET].path ?? offlineTarget.path - }, - [ONLINE]: { - ...data[ONLINE], - path: data[ONLINE].path ?? onlineTarget.path - } - }, - featureSetTargets: featureStore.newFeatureSet.spec.targets, - selectedPartitionKind, - selectedTargetKind, - partitionRadioButtonsState - }) - - setPassThrouthEnabled(true) - - if (selectedTargetKind.includes(ONLINE)) { - openPopUp(ConfirmDialog, { - confirmButton: { - label: 'Unset online-target', - variant: PRIMARY_BUTTON, - handler: () => { - clearTargets(false) - } - }, - cancelButton: { - label: 'Keep online-target set', - variant: TERTIARY_BUTTON, - handler: () => { - clearTargets(true) + queueMicrotask(() => { + if (featureStore.newFeatureSet.spec.passthrough && !passthroughtEnabled) { + setPreviousTargets({ + data: { + ...data, + [PARQUET]: { + ...data[PARQUET], + path: data[PARQUET].path ?? offlineTarget.path + }, + [ONLINE]: { + ...data[ONLINE], + path: data[ONLINE].path ?? onlineTarget.path } }, - closePopUp: () => { - dispatch(setNewFeatureSetPassthrough(false)) - }, - message: - 'Passthrough set to "enabled" while online-target is set. Do you want to unset online-target?' + featureSetTargets: featureStore.newFeatureSet.spec.targets, + selectedPartitionKind, + selectedTargetKind, + partitionRadioButtonsState }) - } else { - clearTargets(false) + + setPassThrouthEnabled(true) + + if (selectedTargetKind.includes(ONLINE)) { + openPopUp(ConfirmDialog, { + confirmButton: { + label: 'Unset online-target', + variant: PRIMARY_BUTTON, + handler: () => { + clearTargets(false) + } + }, + cancelButton: { + label: 'Keep online-target set', + variant: TERTIARY_BUTTON, + handler: () => { + clearTargets(true) + } + }, + closePopUp: () => { + dispatch(setNewFeatureSetPassthrough(false)) + }, + message: + 'Passthrough set to "enabled" while online-target is set. Do you want to unset online-target?' + }) + } else { + clearTargets(false) + } + } else if (!featureStore.newFeatureSet.spec.passthrough && passthroughtEnabled) { + restoreTargets() + setPassThrouthEnabled(false) } - } else if (!featureStore.newFeatureSet.spec.passthrough && passthroughtEnabled) { - restoreTargets() - setPassThrouthEnabled(false) - } + }) }, [ clearTargets, data, diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelTitle/FeatureSetsPanelTitle.jsx b/src/components/FeatureSetsPanel/FeatureSetsPanelTitle/FeatureSetsPanelTitle.jsx index 63ce585c5d..f19114c299 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelTitle/FeatureSetsPanelTitle.jsx +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelTitle/FeatureSetsPanelTitle.jsx @@ -62,10 +62,12 @@ const FeatureSetsPanelTitle = ({ useEffect(() => { if (featureStore.newFeatureSet.spec.passthrough !== Boolean(data.passthrough)) { - setData(state => ({ - ...state, - passthrough: featureStore.newFeatureSet.spec.passthrough ? 'passthrough' : '' - })) + queueMicrotask(() => { + setData(state => ({ + ...state, + passthrough: featureStore.newFeatureSet.spec.passthrough ? 'passthrough' : '' + })) + }) } }, [data.passthrough, featureStore.newFeatureSet.spec.passthrough]) diff --git a/src/components/FeatureSetsPanel/featureSetsPanel.scss b/src/components/FeatureSetsPanel/featureSetsPanel.scss index 1be3c5c236..580e8202bb 100644 --- a/src/components/FeatureSetsPanel/featureSetsPanel.scss +++ b/src/components/FeatureSetsPanel/featureSetsPanel.scss @@ -1,7 +1,6 @@ @use 'igz-controls/scss/colors'; @use 'igz-controls/scss/borders'; @use 'igz-controls/scss/mixins'; - @include mixins.newItemSidePanel; .feature-set-panel { diff --git a/src/components/FeatureStore/FeatureSets/featureSets.scss b/src/components/FeatureStore/FeatureSets/featureSets.scss index d09344b5ee..c381eceea0 100644 --- a/src/components/FeatureStore/FeatureSets/featureSets.scss +++ b/src/components/FeatureStore/FeatureSets/featureSets.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $featureSetsRowHeight: variables.$rowHeight; $featureSetsHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/FeatureStore/FeatureVectors/featureVectors.scss b/src/components/FeatureStore/FeatureVectors/featureVectors.scss index 3a160dabb8..eb5c63e963 100644 --- a/src/components/FeatureStore/FeatureVectors/featureVectors.scss +++ b/src/components/FeatureStore/FeatureVectors/featureVectors.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $featureVectorsRowHeight: variables.$rowHeight; $featureVectorsHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/FeatureStore/Features/features.scss b/src/components/FeatureStore/Features/features.scss index 17bef53e51..b0ce7abcdb 100644 --- a/src/components/FeatureStore/Features/features.scss +++ b/src/components/FeatureStore/Features/features.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $featuresRowHeight: variables.$rowHeightSmall; $featuresHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/FilterMenuModal/FilterMenuModal.jsx b/src/components/FilterMenuModal/FilterMenuModal.jsx index fce14451d9..9c8325cc3b 100644 --- a/src/components/FilterMenuModal/FilterMenuModal.jsx +++ b/src/components/FilterMenuModal/FilterMenuModal.jsx @@ -52,14 +52,14 @@ const FilterMenuModal = ({ wizardClassName = '' }) => { const [filtersWizardIsShown, setFiltersWizardIsShown] = useState(false) - const filtersIconButtonRef = useRef() - const dispatch = useDispatch() - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ onSubmit: () => {}, initialValues }) - ) + }) + const filtersIconButtonRef = useRef() + const dispatch = useDispatch() const filtersIconClassnames = classnames( 'filters-button', !isEqual(values, initialValues) && 'filters-button_applied' @@ -68,14 +68,14 @@ const FilterMenuModal = ({ const filtersWizardClassnames = classnames('filters-wizard', wizardClassName) useEffect(() => { - if (!isEqual(formRef.current?.getState().values, values)) { - formRef.current?.batch(() => { + if (!isEqual(form.getState().values, values)) { + form.batch(() => { for (const filterName in values) { - formRef.current?.change(filterName, values[filterName]) + form.change(filterName, values[filterName]) } }) } - }, [values]) + }, [form, values]) const hideFiltersWizard = useCallback(event => { if ( @@ -102,8 +102,8 @@ const FilterMenuModal = ({ }, [hideFiltersWizard]) useLayoutEffect(() => { - formRef.current.reset(initialValues) - }, [initialValues]) + form.reset(initialValues) + }, [form, initialValues]) const getFilterCounter = formState => { const initialValuesLocal = applyChanges ? initialValues : formState.initialValues @@ -144,7 +144,7 @@ const FilterMenuModal = ({ } if (actionCanBePerformed) { - formRef.current.restart(initialValues) + form.restart(initialValues) setFiltersWizardIsShown(false) if (counter > 0) { @@ -164,7 +164,7 @@ const FilterMenuModal = ({ } return ( - {}}> + {}}> {formState => { const counter = getFilterCounter(formState) return ( diff --git a/src/components/FilterMenuModal/filterMenuModal.scss b/src/components/FilterMenuModal/filterMenuModal.scss index 54ce1aab92..a0f5e0dfd8 100644 --- a/src/components/FilterMenuModal/filterMenuModal.scss +++ b/src/components/FilterMenuModal/filterMenuModal.scss @@ -13,16 +13,16 @@ top: 0; margin: 0; padding: 0 0 16px; - background: colors.$white; + color: colors.$mulledWine; font-weight: 500; font-size: 20px; line-height: 23.44px; - color: colors.$mulledWine; + background: colors.$white; } &__list { - padding-right: 15px; margin: 0 -15px 0 0; + padding-right: 15px; overflow-y: auto; } @@ -44,16 +44,16 @@ position: relative; &::after { - content: ''; position: absolute; top: 4px; right: 4px; - height: 10px; + z-index: 2; width: 10px; + height: 10px; + background: colors.$brightTurquoise; border: 2px solid colors.$white; border-radius: 50%; - background: colors.$brightTurquoise; - z-index: 2; + content: ''; } } } diff --git a/src/components/FunctionsPage/functions.scss b/src/components/FunctionsPage/functions.scss index bacdf5ce64..50d3a180c3 100644 --- a/src/components/FunctionsPage/functions.scss +++ b/src/components/FunctionsPage/functions.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $functionsRowHeight: variables.$rowHeight; $functionsHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/FunctionsPanel/FunctionsPanel.jsx b/src/components/FunctionsPanel/FunctionsPanel.jsx index a6de059def..03bc6da589 100644 --- a/src/components/FunctionsPanel/FunctionsPanel.jsx +++ b/src/components/FunctionsPanel/FunctionsPanel.jsx @@ -90,15 +90,15 @@ const FunctionsPanel = ({ ) const params = useParams() const navigate = useNavigate() - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues: { labels: parseChipsData(defaultData?.labels || {}, frontendSpec.internal_labels) }, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) const dispatch = useDispatch() const functionsStore = useSelector(store => store.functionsStore) const appStore = useSelector(store => store.appStore) @@ -306,7 +306,7 @@ const FunctionsPanel = ({ } return createPortal( - {}}> + {}}> {formState => { return ( <> diff --git a/src/components/FunctionsPanel/functionsPanel.scss b/src/components/FunctionsPanel/functionsPanel.scss index be5b458e08..0cd25a6647 100644 --- a/src/components/FunctionsPanel/functionsPanel.scss +++ b/src/components/FunctionsPanel/functionsPanel.scss @@ -1,7 +1,6 @@ @use 'igz-controls/scss/colors'; @use 'igz-controls/scss/borders'; @use 'igz-controls/scss/mixins'; - @include mixins.newItemSidePanel; .functions-panel { diff --git a/src/components/JobWizard/JobWizard.jsx b/src/components/JobWizard/JobWizard.jsx index 779234c451..5ffa315e02 100644 --- a/src/components/JobWizard/JobWizard.jsx +++ b/src/components/JobWizard/JobWizard.jsx @@ -95,13 +95,13 @@ const JobWizard = ({ prePopulatedData = {}, wizardTitle = 'Batch run' }) => { - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ onSubmit: () => {}, mutators: { ...arrayMutators, setFieldState }, initialValues: {} }) - ) + }) const isEditMode = useMemo(() => mode === PANEL_EDIT_MODE || mode === PANEL_RERUN_MODE, [mode]) const isRunMode = useMemo(() => mode === PANEL_FUNCTION_CREATE_MODE, [mode]) const projectIsLoading = useSelector(store => store.projectStore.project.loading) @@ -134,7 +134,7 @@ const JobWizard = ({ onWizardClose && onWizardClose() }, [dispatch, onResolve, onWizardClose, showSchedule]) - const { handleCloseModal, resolveModal } = useModalBlockHistory(closeModal, formRef.current) + const { handleCloseModal, resolveModal } = useModalBlockHistory(closeModal, form) useEffect(() => { if (!isEditMode) { @@ -188,9 +188,11 @@ const JobWizard = ({ useEffect(() => { if (!isEmpty(jobsStore.jobFunc)) { - setSelectedFunctionData({ - name: jobsStore.jobFunc.metadata.name, - functions: [jobsStore.jobFunc] + queueMicrotask(() => { + setSelectedFunctionData({ + name: jobsStore.jobFunc.metadata.name, + functions: [jobsStore.jobFunc] + }) }) } }, [isEditMode, isRunMode, jobsStore.jobFunc]) @@ -467,7 +469,7 @@ const JobWizard = ({ ) return ( - {}}> + {}}> {formState => { formStateRef.current = formState diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/jobWizardAdvanced.scss b/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/jobWizardAdvanced.scss index d4dbe92516..be91e46cc2 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/jobWizardAdvanced.scss +++ b/src/components/JobWizard/JobWizardSteps/JobWizardAdvanced/jobWizardAdvanced.scss @@ -1,5 +1,5 @@ .job-wizard__advanced { .access-key-checkbox { - margin: 30px 10px 0 10px; + margin: 30px 10px 0; } } diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.jsx b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.jsx index 086d8601b6..f454b85256 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.jsx +++ b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.jsx @@ -297,7 +297,7 @@ const JobWizardFunctionSelection = ({ .catch(() => { setFunctions([]) }) - + //TODO: formState.initialValues[FUNCTION_SELECTION_STEP].projectName = currentValue } diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/jobWizardFunctionSelection.scss b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/jobWizardFunctionSelection.scss index ea94067e61..c6b6037317 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/jobWizardFunctionSelection.scss +++ b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/jobWizardFunctionSelection.scss @@ -24,8 +24,8 @@ } .content-menu { - margin-bottom: 15px; min-width: unset; + margin-bottom: 15px; } .form-row__project-name { diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardHyperparameterStrategy/jobWizardHyperparametersStrategy.scss b/src/components/JobWizard/JobWizardSteps/JobWizardHyperparameterStrategy/jobWizardHyperparametersStrategy.scss index 0c25490871..e13f0163f0 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardHyperparameterStrategy/jobWizardHyperparametersStrategy.scss +++ b/src/components/JobWizard/JobWizardSteps/JobWizardHyperparameterStrategy/jobWizardHyperparametersStrategy.scss @@ -1,9 +1,6 @@ .job-wizard__hyperparameter-strategy { .grid-container { display: grid; - gap: 15px; - align-items: center; - grid-template-columns: 20% 20% 20% 20%; grid-template-areas: 'strategy-grid-item strategy-grid-item . .' 'max-iterations-grid-item max-errors-grid-item . .' @@ -13,19 +10,22 @@ 'stop-condition-grid-item stop-condition-grid-item . .' 'parallelism-title-grid-item parallelism-title-grid-item . .' 'parallel-runs-grid-item dask-cluster-uri-grid-item dask-cluster-uri-grid-item teardown-dask-grid-item'; + grid-template-columns: 20% 20% 20% 20%; + gap: 15px; + align-items: center; .strategy-grid-item { grid-area: strategy-grid-item; } .max-iterations-grid-item { - min-width: 100px; grid-area: max-iterations-grid-item; + min-width: 100px; } .max-errors-grid-item { - min-width: 100px; grid-area: max-errors-grid-item; + min-width: 100px; } .ranking-title-grid-item { diff --git a/src/components/Jobs/Jobs.jsx b/src/components/Jobs/Jobs.jsx index 80feb7b0ca..bf4a916c07 100644 --- a/src/components/Jobs/Jobs.jsx +++ b/src/components/Jobs/Jobs.jsx @@ -162,13 +162,15 @@ const Jobs = () => { }, [getWorkflows, handleRefreshJobs, initialTabData, refreshScheduled]) useLayoutEffect(() => { - setSelectedTab( - location.pathname.includes(`${JOBS_PAGE_PATH}/${MONITOR_JOBS_TAB}`) - ? MONITOR_JOBS_TAB - : location.pathname.includes(`${JOBS_PAGE_PATH}/${SCHEDULE_TAB}`) - ? SCHEDULE_TAB - : MONITOR_WORKFLOWS_TAB - ) + queueMicrotask(() => { + setSelectedTab( + location.pathname.includes(`${JOBS_PAGE_PATH}/${MONITOR_JOBS_TAB}`) + ? MONITOR_JOBS_TAB + : location.pathname.includes(`${JOBS_PAGE_PATH}/${SCHEDULE_TAB}`) + ? SCHEDULE_TAB + : MONITOR_WORKFLOWS_TAB + ) + }) }, [location.pathname]) useEffect(() => { @@ -252,7 +254,9 @@ const Jobs = () => { setSearchParams={setSearchParams} tab={selectedTab} withAutoRefresh={selectedTab === MONITOR_JOBS_TAB} - withInternalAutoRefresh={Boolean(selectedTab === MONITOR_JOBS_TAB && params.jobName)} + withInternalAutoRefresh={Boolean( + selectedTab === MONITOR_JOBS_TAB && params.jobName + )} withRefreshButton withoutExpandButton > diff --git a/src/components/ModelsPage/ModelEndpoints/modelEndpoints.scss b/src/components/ModelsPage/ModelEndpoints/modelEndpoints.scss index 6f09983739..2305dbbc80 100644 --- a/src/components/ModelsPage/ModelEndpoints/modelEndpoints.scss +++ b/src/components/ModelsPage/ModelEndpoints/modelEndpoints.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $modelEndpointsRowHeight: variables.$rowHeightSmall; $modelEndpointsHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/ModelsPage/RealTimePipelines/realTimePipelines.scss b/src/components/ModelsPage/RealTimePipelines/realTimePipelines.scss index 29665dc152..0a1c0739cb 100644 --- a/src/components/ModelsPage/RealTimePipelines/realTimePipelines.scss +++ b/src/components/ModelsPage/RealTimePipelines/realTimePipelines.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $pipelinesRowHeight: variables.$rowHeight; $pipelinesHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/ModelsPage/modelsPage.scss b/src/components/ModelsPage/modelsPage.scss index 42aec12289..c61d799516 100644 --- a/src/components/ModelsPage/modelsPage.scss +++ b/src/components/ModelsPage/modelsPage.scss @@ -24,9 +24,9 @@ pre { width: 800px; - overflow-x: scroll; - padding: 20px 0; margin: 0; + padding: 20px 0; + overflow-x: scroll; } } } diff --git a/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/monitoringApplication.scss b/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/monitoringApplication.scss index c5776e65fa..0b78609868 100644 --- a/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/monitoringApplication.scss +++ b/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/monitoringApplication.scss @@ -4,6 +4,6 @@ } .monitoring-app__see-all-link { - text-align: right; margin-top: 8px; + text-align: right; } diff --git a/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss b/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss index db6aeb07b6..6eea95bfa7 100644 --- a/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss +++ b/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss @@ -1,7 +1,7 @@ @use 'igz-controls/scss/colors'; @use 'igz-controls/scss/shadows'; @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $applicationRowHeight: variables.$rowHeight; $applicationHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/components/Pipeline/pipeline.scss b/src/components/Pipeline/pipeline.scss index 04d6bf5615..c39016158f 100644 --- a/src/components/Pipeline/pipeline.scss +++ b/src/components/Pipeline/pipeline.scss @@ -24,9 +24,9 @@ &__title { margin: 0 5px; - font-size: 24px; overflow: hidden; font-weight: bold; + font-size: 24px; } } } diff --git a/src/components/Project/ProjectAction/ProjectAction.scss b/src/components/Project/ProjectAction/ProjectAction.scss index b4f4153484..2f154cb00b 100644 --- a/src/components/Project/ProjectAction/ProjectAction.scss +++ b/src/components/Project/ProjectAction/ProjectAction.scss @@ -5,27 +5,27 @@ flex-flow: row wrap; align-items: flex-start; justify-content: flex-start; + width: 100%; margin: 0; padding: 0; list-style-type: none; - width: 100%; transition: height 0.5s ease-in-out; &__item { display: flex; - justify-content: center; flex: 1 0 auto; + justify-content: center; max-width: 33%; margin: 0 0 1.5em; text-align: center; &-wrapper { + position: relative; display: flex; flex-flow: column nowrap; align-items: center; - position: relative; - margin: 0 1.2em; min-width: 105px; + margin: 0 1.2em; cursor: pointer; &:hover, @@ -53,8 +53,8 @@ justify-content: center; width: 68px; height: 68px; - margin-bottom: 0.5rem; margin-right: 0; + margin-bottom: 0.5rem; background-color: colors.$selago; border-radius: 50%; transition: background-color 0.3s ease-in-out; diff --git a/src/components/Project/ProjectOverview/ProjectOverview.scss b/src/components/Project/ProjectOverview/ProjectOverview.scss index 3acf081c68..d2b0df7539 100644 --- a/src/components/Project/ProjectOverview/ProjectOverview.scss +++ b/src/components/Project/ProjectOverview/ProjectOverview.scss @@ -39,6 +39,7 @@ &-card { flex: 0 0 100%; min-width: 350px; + // min-height: 635px; margin: 0 15px 2em; transform: translateY(-60px); @@ -51,7 +52,6 @@ &__top { position: relative; display: flex; - flex-flow: column; } @@ -65,16 +65,16 @@ &__bottom { display: flex; - flex-flow: column nowrap; flex: 1; + flex-flow: column nowrap; justify-content: space-between; - text-align: center; min-height: 66px; + text-align: center; .label { - font-size: 1.2em; - margin: 0 0 10px 0; + margin: 0 0 10px; padding: 0; + font-size: 1.2em; } } @@ -94,8 +94,8 @@ & > * { display: inline-flex; align-items: flex-end; - font-size: 14px; padding: 5px 10px; + font-size: 14px; text-transform: capitalize; cursor: pointer; @@ -106,8 +106,8 @@ } &__actions { - padding: 0 1.6em; min-height: 270px; + padding: 0 1.6em; } } } @@ -122,7 +122,7 @@ background-color: colors.$white; border: 1px solid colors.$mercury; border-radius: 12px; - box-shadow: 7px 8px 25px rgba(0, 0, 0, 0.04); + box-shadow: 7px 8px 25px rgb(0 0 0 / 4%); &__top { background-color: colors.$zircon; @@ -149,8 +149,8 @@ &-subtitle { margin: 0; - font-size: 1em; font-weight: 300; + font-size: 1em; line-height: 1.3; @media screen and (min-width: 1600px) { diff --git a/src/components/Project/project.scss b/src/components/Project/project.scss index 51ce3a66e1..9298ee7af6 100644 --- a/src/components/Project/project.scss +++ b/src/components/Project/project.scss @@ -146,8 +146,8 @@ justify-content: space-between; margin-bottom: 16px; color: colors.$primary; - font-size: 1em; font-weight: 500; + font-size: 1em; line-height: 23px; } @@ -155,8 +155,8 @@ display: flex; align-items: center; color: colors.$topaz; - font-size: 12px; font-weight: 400; + font-size: 12px; &-icon { flex: 0 1 12px; @@ -202,9 +202,9 @@ display: flex; align-items: center; min-height: 40px; - margin: 0 0 5px 0; - font-size: 14px; + margin: 0 0 5px; font-weight: 500; + font-size: 14px; .text-sm { margin-left: 5px; diff --git a/src/components/ProjectSettings/projectSettings.scss b/src/components/ProjectSettings/projectSettings.scss index 1cac62de27..ba18ce65e5 100644 --- a/src/components/ProjectSettings/projectSettings.scss +++ b/src/components/ProjectSettings/projectSettings.scss @@ -16,10 +16,10 @@ margin: 0 -15px; &-col { - padding: 0 15px; flex: 0 0 auto; width: 50%; min-width: 650px; + padding: 0 15px; &:not(:last-child) { border-right: borders.$dividerBorder; diff --git a/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx b/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx index 640db4dcde..cce04c73e3 100644 --- a/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx +++ b/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React from 'react' +import React, { useState } from 'react' import PropTypes from 'prop-types' import arrayMutators from 'final-form-arrays' import { Form } from 'react-final-form' @@ -46,7 +46,7 @@ import { useModalBlockHistory } from '../../../hooks/useModalBlockHistory.hook' import './createProjectDialog.scss' -const CreateProjectDialog = ({ closeNewProjectPopUp, handleCreateProject, isOpen = false}) => { +const CreateProjectDialog = ({ closeNewProjectPopUp, handleCreateProject, isOpen = false }) => { const projectStore = useSelector(store => store.projectStore) const frontendSpec = useSelector(store => store.appStore.frontendSpec) const initialValues = { @@ -55,15 +55,23 @@ const CreateProjectDialog = ({ closeNewProjectPopUp, handleCreateProject, isOpen labels: [] } - const formRef = React.useRef( - createForm({ + // const formRef = React.useRef( + // createForm({ + // initialValues, + // mutators: { ...arrayMutators, setFieldState }, + // onSubmit: handleCreateProject + // }) + // ) + + const [form] = useState(() => { + return createForm({ initialValues, mutators: { ...arrayMutators, setFieldState }, onSubmit: handleCreateProject }) - ) + }) const dispatch = useDispatch() - const { handleCloseModal } = useModalBlockHistory(closeNewProjectPopUp, formRef.current) + const { handleCloseModal } = useModalBlockHistory(closeNewProjectPopUp, form) return ( {projectStore.loading && } - + {formState => { return ( <> diff --git a/src/components/RegisterArtifactModal/RegisterArtifactModal.jsx b/src/components/RegisterArtifactModal/RegisterArtifactModal.jsx index 0649e2b553..7599157137 100644 --- a/src/components/RegisterArtifactModal/RegisterArtifactModal.jsx +++ b/src/components/RegisterArtifactModal/RegisterArtifactModal.jsx @@ -70,16 +70,17 @@ const RegisterArtifactModal = ({ } } } - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) + const location = useLocation() const dispatch = useDispatch() - const { handleCloseModal, resolveModal } = useModalBlockHistory(onResolve, formRef.current) + const { handleCloseModal, resolveModal } = useModalBlockHistory(onResolve, form) const messagesByKind = useMemo(() => { return getArtifactMessagesByKind(artifactKind) }, [artifactKind]) @@ -160,7 +161,7 @@ const RegisterArtifactModal = ({ } return ( - + {formState => { return ( <> diff --git a/src/components/Workflow/workflow.scss b/src/components/Workflow/workflow.scss index 3985ad0e26..085e7c0b14 100644 --- a/src/components/Workflow/workflow.scss +++ b/src/components/Workflow/workflow.scss @@ -8,17 +8,19 @@ .workflow__actions { &-container { - align-items: center; + display: flex; flex-direction: row; gap: 2px; - display: flex; + align-items: center; } + svg { - padding: 0; - height: 14px; width: 14px; + height: 14px; + padding: 0; } } + .workflow-content { flex-direction: column; diff --git a/src/elements/AlertsTableRow/AlertsTableRow.scss b/src/elements/AlertsTableRow/AlertsTableRow.scss index ae9ebd21c9..d619fbba00 100644 --- a/src/elements/AlertsTableRow/AlertsTableRow.scss +++ b/src/elements/AlertsTableRow/AlertsTableRow.scss @@ -73,7 +73,7 @@ flex-direction: row; gap: 10px; align-items: center; - margin: 12px 0 12px 0; + margin: 12px 0; padding-right: 12px; &:not(:last-child) { diff --git a/src/elements/BreadcrumbsDropdown/breadcrumbsDropdown.scss b/src/elements/BreadcrumbsDropdown/breadcrumbsDropdown.scss index dad7cf67ec..ed988c3819 100644 --- a/src/elements/BreadcrumbsDropdown/breadcrumbsDropdown.scss +++ b/src/elements/BreadcrumbsDropdown/breadcrumbsDropdown.scss @@ -3,8 +3,8 @@ @use 'igz-controls/scss/borders'; .breadcrumbs__dropdown { - max-height: 384px; min-width: 220px; + max-height: 384px; overflow-y: auto; &-wrapper { @@ -37,8 +37,8 @@ font-weight: bold; &:hover { - cursor: default; background: white; + cursor: default; } } diff --git a/src/elements/CreateJobCardTemplate/createJobCardTemplate.scss b/src/elements/CreateJobCardTemplate/createJobCardTemplate.scss index 707b83085e..15dae091e6 100644 --- a/src/elements/CreateJobCardTemplate/createJobCardTemplate.scss +++ b/src/elements/CreateJobCardTemplate/createJobCardTemplate.scss @@ -31,10 +31,10 @@ } &__description { + height: 42px; color: colors.$topaz; font-size: 13px; line-height: 21px; - height: 42px; } &.small { diff --git a/src/elements/DeleteArtifactPopUp/DeleteArtifactPopUp.jsx b/src/elements/DeleteArtifactPopUp/DeleteArtifactPopUp.jsx index f61c13ca67..44f674bfed 100644 --- a/src/elements/DeleteArtifactPopUp/DeleteArtifactPopUp.jsx +++ b/src/elements/DeleteArtifactPopUp/DeleteArtifactPopUp.jsx @@ -52,10 +52,8 @@ const DeleteArtifactPopUp = ({ }) => { const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(true) const [disableConfirmButton, setDisableConfirmButton] = useState(false) - const dispatch = useDispatch() - const params = useParams() - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues: { deletion_strategy: 'metadata-only', extended_deletion_strategy: false, @@ -64,7 +62,9 @@ const DeleteArtifactPopUp = ({ mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) + const dispatch = useDispatch() + const params = useParams() const handleCancel = () => { setIsConfirmDialogOpen(false) @@ -73,7 +73,7 @@ const DeleteArtifactPopUp = ({ const handleDelete = useCallback(() => { const secrets = {} - formRef.current.getState().values.secrets.forEach(secret => { + form.getState().values.secrets.forEach(secret => { secrets[secret.data.key] = secret.data.value }) @@ -88,28 +88,29 @@ const DeleteArtifactPopUp = ({ artifactType, category, false, - formRef.current.getState().values.deletion_strategy, + form.getState().values.deletion_strategy, secrets ).then(() => { setIsConfirmDialogOpen(false) }) }, [ + form, + dispatch, + params.projectName, artifact.db_key, artifact.uid, - artifactType, - category, - dispatch, - filters, refreshArtifacts, refreshAfterDeleteCallback, - params.projectName + filters, + artifactType, + category ]) const toggleExtendedDeletionStrategy = value => { - formRef.current.change('deletion_strategy', value ? '' : 'metadata-only') + form.change('deletion_strategy', value ? '' : 'metadata-only') - if (formRef.current.getState().values.secrets.length > 0) { - formRef.current.change('secrets', []) + if (form.getState().values.secrets.length > 0) { + form.change('secrets', []) } setDisableConfirmButton(value) @@ -138,7 +139,7 @@ const DeleteArtifactPopUp = ({ isOpen={Boolean(isConfirmDialogOpen)} message={`Are you sure you want to delete the ${artifactType} "${artifact.db_key}" metadata? Deleted metadata can not be restored.`} > - {}}> + {}}> {formState => { const extended_deletion_strategy = formState.values.extended_deletion_strategy diff --git a/src/elements/DetailsInfoItemChip/DetailsInfoItemChip.jsx b/src/elements/DetailsInfoItemChip/DetailsInfoItemChip.jsx index 2532afb5ff..29f422fb7a 100644 --- a/src/elements/DetailsInfoItemChip/DetailsInfoItemChip.jsx +++ b/src/elements/DetailsInfoItemChip/DetailsInfoItemChip.jsx @@ -76,15 +76,15 @@ const DetailsInfoItemChip = ({ handleFinishEdit(item.fieldData.name) } }, [ + commonDetailsStore.changes.data, currentField, detailsInfoDispatch, - commonDetailsStore.changes.data, dispatch, formState.form, formState.values, handleFinishEdit, isFieldInEditMode, - item?.editModeType, + item.editModeType, item.fieldData.name ]) diff --git a/src/elements/DetailsPopUp/ArtifactPopUp/ArtifactPopUp.jsx b/src/elements/DetailsPopUp/ArtifactPopUp/ArtifactPopUp.jsx index 4bbb0a7933..e28b7a5a1f 100644 --- a/src/elements/DetailsPopUp/ArtifactPopUp/ArtifactPopUp.jsx +++ b/src/elements/DetailsPopUp/ArtifactPopUp/ArtifactPopUp.jsx @@ -207,7 +207,9 @@ const ArtifactPopUp = ({ artifactData, isOpen, onResolve }) => { useEffect(() => { if (isEmpty(selectedArtifact)) { - fetchArtifact() + queueMicrotask(() => { + fetchArtifact() + }) } }, [fetchArtifact, selectedArtifact]) diff --git a/src/elements/DetailsPopUp/FeatureSetPopUp/FeatureSetPopUp.jsx b/src/elements/DetailsPopUp/FeatureSetPopUp/FeatureSetPopUp.jsx index 7a0826247c..8e8e485826 100644 --- a/src/elements/DetailsPopUp/FeatureSetPopUp/FeatureSetPopUp.jsx +++ b/src/elements/DetailsPopUp/FeatureSetPopUp/FeatureSetPopUp.jsx @@ -82,7 +82,9 @@ const FeatureSetPopUp = ({ featureSetData, isOpen, onResolve }) => { useEffect(() => { if (isEmpty(selectedFeatureSet)) { - fetchFeatureSetData() + queueMicrotask(() => { + fetchFeatureSetData() + }) } }, [fetchFeatureSetData, selectedFeatureSet]) diff --git a/src/elements/DetailsPopUp/FeatureVectorPopUp/FeatureVectorPopUp.jsx b/src/elements/DetailsPopUp/FeatureVectorPopUp/FeatureVectorPopUp.jsx index 0d861c1e24..05f420c9be 100644 --- a/src/elements/DetailsPopUp/FeatureVectorPopUp/FeatureVectorPopUp.jsx +++ b/src/elements/DetailsPopUp/FeatureVectorPopUp/FeatureVectorPopUp.jsx @@ -92,7 +92,9 @@ const FeatureVectorPopUp = ({ featureVectorData, isOpen, onResolve }) => { useEffect(() => { if (isEmpty(selectedFeatureVector)) { - fetchFeatureVector() + queueMicrotask(() => { + fetchFeatureVector() + }) } }, [fetchFeatureVector, selectedFeatureVector]) diff --git a/src/elements/DetailsPopUp/FunctionPopUp/FunctionPopUp.jsx b/src/elements/DetailsPopUp/FunctionPopUp/FunctionPopUp.jsx index 9a2314f217..d0357de0bd 100644 --- a/src/elements/DetailsPopUp/FunctionPopUp/FunctionPopUp.jsx +++ b/src/elements/DetailsPopUp/FunctionPopUp/FunctionPopUp.jsx @@ -43,6 +43,7 @@ const FunctionPopUp = ({ funcTag = '', funcUri = null, isOpen, onResolve }) => { const { isDemoMode, isStagingMode } = useMode() const [isLoading, setIsLoading] = useState(true) const [selectedFunction, setSelectedFunction] = useState({}) + const [pageData, setPageData] = useState({}) const fetchFunctionLogsTimeout = useRef(null) const fetchFunctionNuclioLogsTimeout = useRef(null) const toggleConvertedYaml = useCallback( @@ -100,8 +101,8 @@ const FunctionPopUp = ({ funcTag = '', funcUri = null, isOpen, onResolve }) => { [dispatch, isDemoMode, isStagingMode, toggleConvertedYaml, selectedFunction, fetchFunction] ) - const pageData = useMemo( - () => + useEffect(() => { + setPageData( generateFunctionsPageData( dispatch, selectedFunction, @@ -109,13 +110,15 @@ const FunctionPopUp = ({ funcTag = '', funcUri = null, isOpen, onResolve }) => { fetchFunctionNuclioLogsTimeout, navigate, fetchFunction - ), - [dispatch, fetchFunction, navigate, selectedFunction] - ) + ) + ) + }, [dispatch, fetchFunction, navigate, selectedFunction]) useEffect(() => { if (isEmpty(selectedFunction)) { - fetchFunction() + queueMicrotask(() => { + fetchFunction() + }) } }, [fetchFunction, selectedFunction]) diff --git a/src/elements/DetailsPopUp/JobPopUp/JobPopUp.jsx b/src/elements/DetailsPopUp/JobPopUp/JobPopUp.jsx index f3798b26ec..727764aef4 100644 --- a/src/elements/DetailsPopUp/JobPopUp/JobPopUp.jsx +++ b/src/elements/DetailsPopUp/JobPopUp/JobPopUp.jsx @@ -125,7 +125,9 @@ const JobPopUp = ({ isOpen, jobData, onResolve }) => { useEffect(() => { if (isEmpty(selectedJob)) { - handleFetchJob() + queueMicrotask(() => { + handleFetchJob() + }) } }, [handleFetchJob, selectedJob]) diff --git a/src/elements/EnvironmentVariables/enviromnetVariables.scss b/src/elements/EnvironmentVariables/enviromnetVariables.scss index b8a2133ad7..620b9f128f 100644 --- a/src/elements/EnvironmentVariables/enviromnetVariables.scss +++ b/src/elements/EnvironmentVariables/enviromnetVariables.scss @@ -48,7 +48,7 @@ &__row { .active-input { - padding: 25px 16px 11px 16px; + padding: 25px 16px 11px; } .table-cell { diff --git a/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.jsx b/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.jsx index fc6dba1831..3c5300e645 100644 --- a/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.jsx +++ b/src/elements/FormDataInputsTable/FormDataInputsRow/FormDataInputsRow.jsx @@ -66,7 +66,9 @@ const FormDataInputsRow = ({ const [fieldData, setFieldData] = useState(fields.value[index]) useEffect(() => { - setFieldData(fields.value[index]) + queueMicrotask(() => { + setFieldData(fields.value[index]) + }) }, [fields.value, index]) const isRowDisabled = () => { diff --git a/src/elements/FormEnvironmentVariablesTable/FormEnvironmentVariablesRow/FormEnvironmentVariablesRow.jsx b/src/elements/FormEnvironmentVariablesTable/FormEnvironmentVariablesRow/FormEnvironmentVariablesRow.jsx index 18076b4654..056062a6c8 100644 --- a/src/elements/FormEnvironmentVariablesTable/FormEnvironmentVariablesRow/FormEnvironmentVariablesRow.jsx +++ b/src/elements/FormEnvironmentVariablesTable/FormEnvironmentVariablesRow/FormEnvironmentVariablesRow.jsx @@ -74,7 +74,9 @@ const FormEnvironmentVariablesRow = ({ ) useEffect(() => { - setFieldData(fields.value[index]) + queueMicrotask(() => { + setFieldData(fields.value[index]) + }) }, [fields.value, index]) const handleTypeChange = useCallback( diff --git a/src/elements/FormParametersTable/FormParametersRow/FormParametersRow.jsx b/src/elements/FormParametersTable/FormParametersRow/FormParametersRow.jsx index efde4d0717..ca54492856 100644 --- a/src/elements/FormParametersTable/FormParametersRow/FormParametersRow.jsx +++ b/src/elements/FormParametersTable/FormParametersRow/FormParametersRow.jsx @@ -265,7 +265,9 @@ const FormParametersRow = ({ } useEffect(() => { - setFieldData(fields.value[index]) + queueMicrotask(() => { + setFieldData(fields.value[index]) + }) }, [fields.value, index]) return ( diff --git a/src/elements/FormResourcesUnits/formResourcesUnits.scss b/src/elements/FormResourcesUnits/formResourcesUnits.scss index 74cb92e6af..40355a42cb 100644 --- a/src/elements/FormResourcesUnits/formResourcesUnits.scss +++ b/src/elements/FormResourcesUnits/formResourcesUnits.scss @@ -16,8 +16,8 @@ } .resources-card { - min-width: 280px; width: 100%; + min-width: 280px; padding: 16px; border: borders.$primaryBorder; border-radius: 8px; diff --git a/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.jsx b/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.jsx index 5ac12df267..d408c5999d 100644 --- a/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.jsx +++ b/src/elements/FormVolumesTable/FormVolumesRow/FormVolumesRow.jsx @@ -70,10 +70,12 @@ const FormVolumesRow = ({ ) useLayoutEffect(() => { - setFieldRowData( - generateVolumeInputsData(fields.value[index], fields, editingItem, accessKeyFocusHandler, projectName) - ) - setFieldData(fields.value[index]) + queueMicrotask(() => { + setFieldRowData( + generateVolumeInputsData(fields.value[index], fields, editingItem, accessKeyFocusHandler, projectName) + ) + setFieldData(fields.value[index]) + }) }, [accessKeyFocusHandler, editingItem, fields, index, projectName]) const handleTypeChange = useCallback(() => { diff --git a/src/elements/FormVolumesTable/FormVolumesRow/formVolumeRow.scss b/src/elements/FormVolumesTable/FormVolumesRow/formVolumeRow.scss index ec31b3d8f0..ccabe0b1cf 100644 --- a/src/elements/FormVolumesTable/FormVolumesRow/formVolumeRow.scss +++ b/src/elements/FormVolumesTable/FormVolumesRow/formVolumeRow.scss @@ -1,4 +1,4 @@ -@use 'igz-controls/scss/colors.scss'; +@use 'igz-controls/scss/colors'; .form-table__row { &.form-table__volume-row { diff --git a/src/elements/FunctionCardTemplate/functionCardTemplate.scss b/src/elements/FunctionCardTemplate/functionCardTemplate.scss index 419a7ff21e..f9aeaeac69 100644 --- a/src/elements/FunctionCardTemplate/functionCardTemplate.scss +++ b/src/elements/FunctionCardTemplate/functionCardTemplate.scss @@ -3,11 +3,11 @@ @use 'igz-controls/scss/shadows'; .job-card-template { + position: relative; display: flex; flex-flow: column nowrap; justify-content: space-between; min-height: 165px; - position: relative; padding: 15px; background-color: colors.$white; border: borders.$primaryBorder; @@ -30,15 +30,15 @@ } &__header { - font-size: 18px; - font-weight: bold; margin-bottom: 8px; padding-right: 50px; + font-weight: bold; + font-size: 18px; } &__sub-header { - font-size: 15px; font-weight: 500; + font-size: 15px; } &__side-tag { @@ -46,15 +46,15 @@ top: 15px; right: 0; display: flex; - justify-content: center; align-items: center; + justify-content: center; margin: 0; padding: 6px; - border-radius: 4px 0 0 4px; color: colors.$amethyst; - background-color: colors.$frenchLilac; - font-size: 10px; font-weight: bold; + font-size: 10px; + background-color: colors.$frenchLilac; + border-radius: 4px 0 0 4px; } &__description { diff --git a/src/elements/FunctionsPanelCode/FunctionsPanelCode.jsx b/src/elements/FunctionsPanelCode/FunctionsPanelCode.jsx index 0086b98760..7b01c64bf0 100644 --- a/src/elements/FunctionsPanelCode/FunctionsPanelCode.jsx +++ b/src/elements/FunctionsPanelCode/FunctionsPanelCode.jsx @@ -93,11 +93,15 @@ const FunctionsPanelCode = ({ ) ) setImageType(EXISTING_IMAGE) - setData(state => ({ - ...state, - image: - appStore.frontendSpec?.default_function_image_by_kind?.[functionsStore.newFunction.kind] - })) + queueMicrotask(() => { + setData(state => ({ + ...state, + image: + appStore.frontendSpec?.default_function_image_by_kind?.[ + functionsStore.newFunction.kind + ] + })) + }) } else { const buildImage = (appStore.frontendSpec?.function_deployment_target_image_template || '') .replace('{project}', params.projectName) @@ -118,15 +122,17 @@ const FunctionsPanelCode = ({ ) ) dispatch(setNewFunctionBuildImage(buildImage)) - setData(state => ({ - ...state, - requirements: appStore.frontendSpec?.function_deployment_mlrun_requirement ?? '', - base_image: - appStore.frontendSpec?.default_function_image_by_kind?.[ - functionsStore.newFunction.kind - ] ?? '', - build_image: buildImage - })) + queueMicrotask(() => { + setData(state => ({ + ...state, + requirements: appStore.frontendSpec?.function_deployment_mlrun_requirement ?? '', + base_image: + appStore.frontendSpec?.default_function_image_by_kind?.[ + functionsStore.newFunction.kind + ] ?? '', + build_image: buildImage + })) + }) } } else if ( (defaultData.image?.length > 0 || @@ -138,10 +144,12 @@ const FunctionsPanelCode = ({ ) { dispatch(setNewFunctionImage(defaultData.image || DEFAULT_IMAGE)) setImageType(EXISTING_IMAGE) - setData(state => ({ - ...state, - image: defaultData.image || DEFAULT_IMAGE - })) + queueMicrotask(() => { + setData(state => ({ + ...state, + image: defaultData.image || DEFAULT_IMAGE + })) + }) } else if (imageType.length === 0) { setImageType(NEW_IMAGE) } diff --git a/src/elements/FunctionsPanelCode/functionsPanelCode.scss b/src/elements/FunctionsPanelCode/functionsPanelCode.scss index 65570b0c51..c85b8fd73a 100644 --- a/src/elements/FunctionsPanelCode/functionsPanelCode.scss +++ b/src/elements/FunctionsPanelCode/functionsPanelCode.scss @@ -55,7 +55,7 @@ width: 200px; div:not(:last-child) { - margin: 10px 0 43px 0; + margin: 10px 0 43px; } } } diff --git a/src/elements/FunctionsPanelEnvironmentVariables/FunctionsPanelEnvironmentVariables.jsx b/src/elements/FunctionsPanelEnvironmentVariables/FunctionsPanelEnvironmentVariables.jsx index 8db17fc58d..015f7c413e 100644 --- a/src/elements/FunctionsPanelEnvironmentVariables/FunctionsPanelEnvironmentVariables.jsx +++ b/src/elements/FunctionsPanelEnvironmentVariables/FunctionsPanelEnvironmentVariables.jsx @@ -34,7 +34,9 @@ const FunctionsPanelEnvironmentVariables = () => { const functionsStore = useSelector(store => store.functionsStore) useEffect(() => { - setEnvVariables(parseEnvVariables(functionsStore.newFunction.spec.env)) + queueMicrotask(() => { + setEnvVariables(parseEnvVariables(functionsStore.newFunction.spec.env)) + }) }, [functionsStore.newFunction.spec.env]) const handleAddNewEnv = env => { diff --git a/src/elements/JobsMonitoringStatsCard/jobsMonitoringStatsCard.scss b/src/elements/JobsMonitoringStatsCard/jobsMonitoringStatsCard.scss index 039d4bcb44..80169d7747 100644 --- a/src/elements/JobsMonitoringStatsCard/jobsMonitoringStatsCard.scss +++ b/src/elements/JobsMonitoringStatsCard/jobsMonitoringStatsCard.scss @@ -13,9 +13,9 @@ @include mixins.stats(); &-card { - cursor: pointer; - border: 2px solid transparent; min-height: 144px; + border: 2px solid transparent; + cursor: pointer; } &__counter { diff --git a/src/elements/MembersPopUp/membersPopUp.scss b/src/elements/MembersPopUp/membersPopUp.scss index 4e223b2fb8..7d97d7baac 100644 --- a/src/elements/MembersPopUp/membersPopUp.scss +++ b/src/elements/MembersPopUp/membersPopUp.scss @@ -84,8 +84,8 @@ .new-member-name { width: 70%; - padding-left: 10px; padding-right: 10px; + padding-left: 10px; } .new-member-role { @@ -150,8 +150,8 @@ background-color: colors.$white; .member-info { - margin-right: 5%; width: 65%; + margin-right: 5%; border-bottom: borders.$primaryBorder; } } diff --git a/src/elements/MethodDescription/methodDescription.scss b/src/elements/MethodDescription/methodDescription.scss index db052a6485..2b6cd81fed 100644 --- a/src/elements/MethodDescription/methodDescription.scss +++ b/src/elements/MethodDescription/methodDescription.scss @@ -23,7 +23,7 @@ align-items: center; height: 17px; margin-left: 5px; - padding: 0 5px 12px 5px; + padding: 0 5px 12px; color: colors.$topaz; font-weight: bold; font-size: 20px; diff --git a/src/elements/MetricsSelector/MetricsSelector.jsx b/src/elements/MetricsSelector/MetricsSelector.jsx index 9f65ccef2c..108bc12a7a 100644 --- a/src/elements/MetricsSelector/MetricsSelector.jsx +++ b/src/elements/MetricsSelector/MetricsSelector.jsx @@ -61,9 +61,8 @@ const MetricsSelector = ({ const [nameFilter, setNameFilter] = useState('') const [isOpen, setIsOpen] = useState(false) const [appliedMetrics, setAppliedMetrics] = useState([]) - const selectorFieldRef = useRef() - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues: { metrics: [], metricSearchName: '' @@ -71,7 +70,8 @@ const MetricsSelector = ({ mutators: { ...arrayMutators }, onSubmit: () => {} }) - ) + }) + const selectorFieldRef = useRef() const generatedMetrics = useMemo(() => { return groupMetricByApplication(metrics) @@ -97,26 +97,30 @@ const MetricsSelector = ({ useEffect(() => { if (!isOpen) { - formRef.current?.batch(() => { - formRef.current.change( + form.batch(() => { + form.change( 'metrics', appliedMetrics.map(metricItem => metricItem.full_name) ) - formRef.current.change('metricSearchName', '') + form.change('metricSearchName', '') }) - setNameFilter('') + queueMicrotask(() => { + setNameFilter('') + }) } - }, [appliedMetrics, isOpen]) + }, [appliedMetrics, form, isOpen]) useEffect(() => { if (preselectedMetrics) { - formRef.current.reset({ + form.reset({ metrics: preselectedMetrics.map(metricItem => metricItem.full_name) }) - setAppliedMetrics(preselectedMetrics) + queueMicrotask(() => { + setAppliedMetrics(preselectedMetrics) + }) } - }, [preselectedMetrics]) + }, [form, preselectedMetrics]) const windowClickHandler = useCallback( event => { @@ -155,7 +159,7 @@ const MetricsSelector = ({ const handleApply = () => { const newAppliedMetrics = - formRef.current?.getFieldState('metrics')?.value?.map(metricFullName => { + form.getFieldState('metrics')?.value?.map(metricFullName => { return metrics.find(metric => metric.full_name === metricFullName) }) || [] @@ -165,7 +169,7 @@ const MetricsSelector = ({ } const handleClear = () => { - formRef.current?.change('metrics', []) + form.change('metrics', []) } const getSelectValue = () => { @@ -202,7 +206,7 @@ const MetricsSelector = ({ } return ( - {}}> + {}}> {formState => ( ) : ( [ -
+
{counterObject.loading ? ( ) : ( @@ -95,7 +99,7 @@ const ProjectStatisticsCounter = ({ counterObject }) => {
,
{counterObject.label ?? 'N/A'} {counterObject.status && } diff --git a/src/elements/ProjectsMonitoringCounters/AlertsCounters.jsx b/src/elements/ProjectsMonitoringCounters/AlertsCounters.jsx index 719176febf..60c343b127 100644 --- a/src/elements/ProjectsMonitoringCounters/AlertsCounters.jsx +++ b/src/elements/ProjectsMonitoringCounters/AlertsCounters.jsx @@ -53,11 +53,13 @@ const AlertsCounters = () => { const alertsData = useMemo(() => { const projectName = paramProjectName ? paramProjectName : '*' + const summaryData = projectStore?.projectSummary?.data + const alerts = projectStore?.jobsMonitoringData?.alerts if (projectName !== '*') { - const endpoint = projectStore?.projectSummary?.data?.endpoint_alerts_count - const jobs = projectStore?.projectSummary?.data?.job_alerts_count - const application = projectStore?.projectSummary?.data?.other_alerts_count + const endpoint = summaryData?.endpoint_alerts_count + const jobs = summaryData?.job_alerts_count + const application = summaryData?.other_alerts_count return { projectName, @@ -72,15 +74,9 @@ const AlertsCounters = () => { return { projectName, - data: defaults({}, projectStore?.jobsMonitoringData?.alerts) + data: defaults({}, alerts) } - }, [ - paramProjectName, - projectStore?.jobsMonitoringData?.alerts, - projectStore?.projectSummary?.data?.endpoint_alerts_count, - projectStore?.projectSummary?.data?.job_alerts_count, - projectStore?.projectSummary?.data?.other_alerts_count - ]) + }, [paramProjectName, projectStore.projectSummary?.data, projectStore.jobsMonitoringData?.alerts]) const alertsStats = useMemo( () => generateAlertsStats(alertsData.data, navigate, alertsData.projectName), diff --git a/src/elements/ProjectsMonitoringCounters/ScheduledJobsCounters.jsx b/src/elements/ProjectsMonitoringCounters/ScheduledJobsCounters.jsx index d014223213..68567b6d16 100644 --- a/src/elements/ProjectsMonitoringCounters/ScheduledJobsCounters.jsx +++ b/src/elements/ProjectsMonitoringCounters/ScheduledJobsCounters.jsx @@ -49,10 +49,12 @@ const ScheduledJobsCounters = () => { } const scheduledData = useMemo(() => { + const summaryData = projectStore.projectSummary?.data + const scheduled = projectStore.jobsMonitoringData?.scheduled + if (projectName) { - const jobs = projectStore.projectSummary?.data?.distinct_scheduled_jobs_pending_count - const workflows = - projectStore.projectSummary?.data?.distinct_scheduled_pipelines_pending_count + const jobs = summaryData?.distinct_scheduled_jobs_pending_count + const workflows = summaryData?.distinct_scheduled_pipelines_pending_count return { jobs, @@ -60,13 +62,11 @@ const ScheduledJobsCounters = () => { total: countTotalValue([jobs, workflows]) } } - return projectStore?.jobsMonitoringData.scheduled || {} - }, [ - projectName, - projectStore.projectSummary?.data?.distinct_scheduled_jobs_pending_count, - projectStore.projectSummary?.data?.distinct_scheduled_pipelines_pending_count, - projectStore.jobsMonitoringData?.scheduled - ]) + + return ( + scheduled || {} + ) + }, [projectName, projectStore.projectSummary?.data, projectStore.jobsMonitoringData?.scheduled]) const scheduledStats = useMemo( () => diff --git a/src/elements/ReadOnlyChips/ReadOnlyChips.jsx b/src/elements/ReadOnlyChips/ReadOnlyChips.jsx index ee13d06ad5..fd058733f2 100644 --- a/src/elements/ReadOnlyChips/ReadOnlyChips.jsx +++ b/src/elements/ReadOnlyChips/ReadOnlyChips.jsx @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React from 'react' +import React, { useState } from 'react' import { Form } from 'react-final-form' import { createForm } from 'final-form' import PropTypes from 'prop-types' @@ -30,16 +30,16 @@ import { setFieldState } from 'igz-controls/utils/form.util' import { CHIP_OPTIONS } from 'igz-controls/types' const ReadOnlyChips = ({ labels = [], chipOptions = getChipOptions('metrics'), ...args }) => { - const formRef = React.useRef( - createForm({ + const [form] = useState(() => { + return createForm({ initialValues: { labels: labels }, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) return ( - {}}> + {}}> {formState => { return ( { + return createForm({ initialValues, mutators: { ...arrayMutators, setFieldState }, onSubmit: () => {} }) - ) + }) + const location = useLocation() - const { handleCloseModal, resolveModal } = useModalBlockHistory(onResolve, formRef.current) + const { handleCloseModal, resolveModal } = useModalBlockHistory(onResolve, form) const dispatch = useDispatch() const registerModel = values => { @@ -154,7 +162,7 @@ function RegisterModelModal({ actions = null, isOpen, onResolve, params, refresh } return ( - + {formState => { return ( <> diff --git a/src/elements/ScheduledJobsTable/scheduledJobsTable.scss b/src/elements/ScheduledJobsTable/scheduledJobsTable.scss index 96c95ebb21..09b049dd4e 100644 --- a/src/elements/ScheduledJobsTable/scheduledJobsTable.scss +++ b/src/elements/ScheduledJobsTable/scheduledJobsTable.scss @@ -1,5 +1,5 @@ @use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; +@use '@/scss/mixins'; $scheduledJobsRowHeight: variables.$rowHeightSmall; $scheduledJobsHeaderRowHeight: variables.$headerRowHeight; diff --git a/src/elements/TableTop/tableTop.scss b/src/elements/TableTop/tableTop.scss index 14b76c803c..1c4eeb503c 100644 --- a/src/elements/TableTop/tableTop.scss +++ b/src/elements/TableTop/tableTop.scss @@ -17,9 +17,9 @@ &__title { margin: 0 5px; - font-size: 24px; overflow: hidden; font-weight: bold; + font-size: 24px; } } } diff --git a/src/elements/WorkflowsTable/WorkflowsTable.jsx b/src/elements/WorkflowsTable/WorkflowsTable.jsx index 9920630629..60e03eb965 100644 --- a/src/elements/WorkflowsTable/WorkflowsTable.jsx +++ b/src/elements/WorkflowsTable/WorkflowsTable.jsx @@ -78,548 +78,520 @@ import { useSortTable } from '../../hooks/useSortTable.hook' import './workflowsTable.scss' -const WorkflowsTable = React.forwardRef( - ( - { - backLink, - context, - filters, - filtersConfig, - getWorkflows, - itemIsSelected, - requestErrorMessage, - selectedFunction, - selectedJob, - setItemIsSelected, - setSelectedFunction, - setSelectedJob, - setWorkflowIsLoaded, - tableContent = [], - workflowIsLoaded +const WorkflowsTable = ( + { + ref: abortJobRef, + backLink, + context, + filters, + filtersConfig, + getWorkflows, + itemIsSelected, + requestErrorMessage, + selectedFunction, + selectedJob, + setItemIsSelected, + setSelectedFunction, + setSelectedJob, + setWorkflowIsLoaded, + tableContent = [], + workflowIsLoaded + } +) => { + const [dataIsLoading, setDataIsLoading] = useState(false) + const [rerunIsDisabled, setRerunIsDisabled] = useState(false) + const [workflowsViewMode, setWorkflowsViewMode] = useState(WORKFLOW_GRAPH_VIEW) + const workflowsStore = useSelector(state => state.workflowsStore) + const filtersStore = useSelector(state => state.filtersStore) + const appStore = useSelector(store => store.appStore) + const params = useParams() + const dispatch = useDispatch() + const navigate = useNavigate() + const location = useLocation() + const fetchJobFunctionsPromiseRef = useRef() + let fetchFunctionLogsTimeout = useRef(null) + const accessibleProjectsMap = useSelector(state => state.projectStore.accessibleProjectsMap) + const [permissionsLoading, setPermissionsLoading] = useState(false) + + useEffect(() => { + const projectNames = workflowsStore.workflows.data.map(workflow => workflow.project) + setPermissionsLoading(true) + projectNames && + fetchMissingProjectsPermissions(projectNames, accessibleProjectsMap, dispatch).finally( + () => { + setPermissionsLoading(false) + } + ) + }, [dispatch, workflowsStore.workflows.data, accessibleProjectsMap]) + + const monitorWorkflowsRowHeight = useMemo( + () => getScssVariableValue('--monitorWorkflowsRowHeight'), + [] + ) + const monitorWorkflowsRowHeightExtended = useMemo( + () => getScssVariableValue('--monitorWorkflowsRowHeightExtended'), + [] + ) + const monitorWorkflowsHeaderRowHeight = useMemo( + () => getScssVariableValue('--monitorWorkflowsHeaderRowHeight'), + [] + ) + + const { + editableItem, + handleMonitoring, + handleRerunJob, + jobWizardIsOpened, + jobWizardMode, + setConfirmData, + setEditableItem, + setJobWizardIsOpened, + setJobWizardMode + } = React.useContext(context) + + const { sortedTableContent } = useSortTable({ + headers: tableContent[0]?.content, + content: tableContent, + sortConfig: { defaultSortBy: 'createdat', defaultDirection: 'desc' } + }) + + const handleRetry = useCallback(() => { + getWorkflows(filters) + }, [filters, getWorkflows]) + + const handleFetchFunctionLogs = useCallback( + (item, projectName, setDetailsLogs) => { + return getFunctionLogs( + dispatch, + fetchFunctionLogsTimeout, + projectName, + item.name, + item.tag, + setDetailsLogs + ) }, - abortJobRef - ) => { - const [dataIsLoading, setDataIsLoading] = useState(false) - const [rerunIsDisabled, setRerunIsDisabled] = useState(false) - const [workflowsViewMode, setWorkflowsViewMode] = useState(WORKFLOW_GRAPH_VIEW) - const workflowsStore = useSelector(state => state.workflowsStore) - const filtersStore = useSelector(state => state.filtersStore) - const appStore = useSelector(store => store.appStore) - const params = useParams() - const dispatch = useDispatch() - const navigate = useNavigate() - const location = useLocation() - const fetchJobFunctionsPromiseRef = useRef() - let fetchFunctionLogsTimeout = useRef(null) - const accessibleProjectsMap = useSelector(state => state.projectStore.accessibleProjectsMap) - const [permissionsLoading, setPermissionsLoading] = useState(false) - - useEffect(() => { - const projectNames = workflowsStore.workflows.data.map(workflow => workflow.project) - setPermissionsLoading(true) - projectNames && - fetchMissingProjectsPermissions(projectNames, accessibleProjectsMap, dispatch).finally( - () => { - setPermissionsLoading(false) - } - ) - }, [dispatch, workflowsStore.workflows.data, accessibleProjectsMap]) - - const monitorWorkflowsRowHeight = useMemo( - () => getScssVariableValue('--monitorWorkflowsRowHeight'), - [] - ) - const monitorWorkflowsRowHeightExtended = useMemo( - () => getScssVariableValue('--monitorWorkflowsRowHeightExtended'), - [] - ) - const monitorWorkflowsHeaderRowHeight = useMemo( - () => getScssVariableValue('--monitorWorkflowsHeaderRowHeight'), - [] - ) + [fetchFunctionLogsTimeout, dispatch] + ) - const { - editableItem, - handleMonitoring, - handleRerunJob, - jobWizardIsOpened, - jobWizardMode, - setConfirmData, - setEditableItem, - setJobWizardIsOpened, - setJobWizardMode - } = React.useContext(context) - - const { sortedTableContent } = useSortTable({ - headers: tableContent[0]?.content, - content: tableContent, - sortConfig: { defaultSortBy: 'createdat', defaultDirection: 'desc' } - }) - - const handleRetry = useCallback(() => { - getWorkflows(filters) - }, [filters, getWorkflows]) - - const handleFetchFunctionLogs = useCallback( - (item, projectName, setDetailsLogs) => { - return getFunctionLogs( - dispatch, - fetchFunctionLogsTimeout, - projectName, - item.name, - item.tag, - setDetailsLogs - ) - }, - [fetchFunctionLogsTimeout, dispatch] - ) + const handleFetchJobLogs = useCallback( + (item, projectName, setDetailsLogs, streamLogsRef) => { + return getJobLogs(item.uid, projectName, streamLogsRef, setDetailsLogs, dispatch) + }, + [dispatch] + ) - const handleFetchJobLogs = useCallback( - (item, projectName, setDetailsLogs, streamLogsRef) => { - return getJobLogs(item.uid, projectName, streamLogsRef, setDetailsLogs, dispatch) - }, - [dispatch] - ) + const toggleConvertedYaml = useCallback( + data => { + return dispatch(toggleYaml(data)) + }, + [dispatch] + ) - const toggleConvertedYaml = useCallback( - data => { - return dispatch(toggleYaml(data)) - }, - [dispatch] - ) + const handleRemoveFunctionLogs = useCallback(() => { + clearTimeout(fetchFunctionLogsTimeout.current) + }, [fetchFunctionLogsTimeout]) - const handleRemoveFunctionLogs = useCallback(() => { - clearTimeout(fetchFunctionLogsTimeout.current) - }, [fetchFunctionLogsTimeout]) - - const pageData = useMemo( - () => - generatePageData( - selectedFunction, - handleFetchFunctionLogs, - handleFetchJobLogs, - handleRemoveFunctionLogs, - selectedJob - ), - [ - handleFetchJobLogs, + const pageData = useMemo( + () => + generatePageData( + selectedFunction, handleFetchFunctionLogs, + handleFetchJobLogs, handleRemoveFunctionLogs, - selectedFunction, selectedJob - ] + ), + [ + handleFetchJobLogs, + handleFetchFunctionLogs, + handleRemoveFunctionLogs, + selectedFunction, + selectedJob + ] + ) + + const refreshWorkflow = useCallback(() => { + return dispatch( + fetchWorkflow({ + project: params.workflowProjectName || params.projectName, + workflowId: params.workflowId + }) ) - - const refreshWorkflow = useCallback(() => { - return dispatch( - fetchWorkflow({ - project: params.workflowProjectName || params.projectName, - workflowId: params.workflowId - }) - ) - .unwrap() - .catch(error => { - showErrorNotification(dispatch, error, 'Failed to fetch workflow') - navigate(backLink, { - replace: true - }) + .unwrap() + .catch(error => { + showErrorNotification(dispatch, error, 'Failed to fetch workflow') + navigate(backLink, { + replace: true }) - }, [ - backLink, - dispatch, - navigate, - params.projectName, - params.workflowId, - params.workflowProjectName - ]) - - const handlePollAbortingJob = useCallback( - (jobRun, refresh) => { - if (jobRun.abortTaskId && jobRun.state.value === ABORTING_STATE) { - const abortingJob = { - [jobRun.abortTaskId]: { - uid: jobRun.uid, - name: jobRun.name - } + }) + }, [ + backLink, + dispatch, + navigate, + params.projectName, + params.workflowId, + params.workflowProjectName + ]) + + const handlePollAbortingJob = useCallback( + (jobRun, refresh) => { + if (jobRun.abortTaskId && jobRun.state.value === ABORTING_STATE) { + const abortingJob = { + [jobRun.abortTaskId]: { + uid: jobRun.uid, + name: jobRun.name } + } - pollAbortingJobs( - params.workflowProjectName || params.projectName, - abortJobRef, - abortingJob, - refresh, - dispatch - ) + pollAbortingJobs( + params.workflowProjectName || params.projectName, + abortJobRef, + abortingJob, + refresh, + dispatch + ) + } + }, + [abortJobRef, dispatch, params.projectName, params.workflowProjectName] + ) + + const modifyAndSelectRun = useCallback( + (jobRun, refresh) => { + return enrichRunWithFunctionFields(dispatch, jobRun, fetchJobFunctionsPromiseRef).then( + jobRun => { + setSelectedJob(jobRun) + setSelectedFunction({}) + setItemIsSelected(true) + + if (refresh) { + handlePollAbortingJob(jobRun, refresh) + } } - }, - [abortJobRef, dispatch, params.projectName, params.workflowProjectName] - ) + ) + }, + [dispatch, handlePollAbortingJob, setItemIsSelected, setSelectedFunction, setSelectedJob] + ) - const modifyAndSelectRun = useCallback( - (jobRun, refresh) => { - return enrichRunWithFunctionFields(dispatch, jobRun, fetchJobFunctionsPromiseRef).then( - jobRun => { - setSelectedJob(jobRun) - setSelectedFunction({}) - setItemIsSelected(true) + const findSelectedWorkflowJob = useCallback(() => { + if (workflowsStore.activeWorkflow?.data) { + const workflow = { ...workflowsStore.activeWorkflow.data } - if (refresh) { - handlePollAbortingJob(jobRun, refresh) - } + return find( + workflow.graph, + workflowItem => workflowItem.run_type === 'run' && workflowItem.run_uid === params.jobId + ) + } + }, [params.jobId, workflowsStore.activeWorkflow.data]) + + const getPipelineError = useCallback( + isErrorState => { + return isErrorState && + workflowsStore.activeWorkflow?.data?.run?.error && + workflowsStore.activeWorkflow.data.run.error !== 'None' + ? { + title: 'Pipeline error - ', + message: workflowsStore.activeWorkflow.data.run.error } - ) - }, - [dispatch, handlePollAbortingJob, setItemIsSelected, setSelectedFunction, setSelectedJob] - ) - - const findSelectedWorkflowJob = useCallback(() => { - if (workflowsStore.activeWorkflow?.data) { - const workflow = { ...workflowsStore.activeWorkflow.data } + : {} + }, + [workflowsStore.activeWorkflow.data] + ) - return find( - workflow.graph, - workflowItem => workflowItem.run_type === 'run' && workflowItem.run_uid === params.jobId - ) - } - }, [params.jobId, workflowsStore.activeWorkflow.data]) - - const getPipelineError = useCallback( - isErrorState => { - return isErrorState && - workflowsStore.activeWorkflow?.data?.run?.error && - workflowsStore.activeWorkflow.data.run.error !== 'None' - ? { - title: 'Pipeline error - ', - message: workflowsStore.activeWorkflow.data.run.error - } - : {} - }, - [workflowsStore.activeWorkflow.data] + const fetchRun = useCallback(() => { + return dispatch( + fetchJob({ project: params.workflowProjectName || params.projectName, jobId: params.jobId }) ) - - const fetchRun = useCallback(() => { - return dispatch( - fetchJob({ project: params.workflowProjectName || params.projectName, jobId: params.jobId }) - ) - .unwrap() - .then(job => { - const selectedJob = findSelectedWorkflowJob() - const graphJobState = selectedJob?.phase?.toLowerCase() - const isErrorState = [FAILED_STATE, ERROR_STATE].includes(graphJobState) - const customJobState = isErrorState ? graphJobState : '' - - return modifyAndSelectRun( - parseJob(job, MONITOR_WORKFLOWS_TAB, customJobState, getPipelineError(isErrorState)), - fetchRun - ) - }) - .catch(() => - navigate(backLink, { - replace: true - }) + .unwrap() + .then(job => { + const selectedJob = findSelectedWorkflowJob() + const graphJobState = selectedJob?.phase?.toLowerCase() + const isErrorState = [FAILED_STATE, ERROR_STATE].includes(graphJobState) + const customJobState = isErrorState ? graphJobState : '' + + return modifyAndSelectRun( + parseJob(job, MONITOR_WORKFLOWS_TAB, customJobState, getPipelineError(isErrorState)), + fetchRun ) - .finally(() => { - fetchJobFunctionsPromiseRef.current = null + }) + .catch(() => + navigate(backLink, { + replace: true }) - }, [ - backLink, - dispatch, - findSelectedWorkflowJob, - modifyAndSelectRun, - navigate, - params.jobId, - params.projectName, - params.workflowProjectName, - getPipelineError - ]) - - const setJobStatusAborting = useCallback( - task => { - setSelectedJob(state => ({ - ...state, - abortTaskId: task, - state: getState(ABORTING_STATE, JOBS_PAGE, JOB_KIND_JOB) - })) - }, - [setSelectedJob] - ) + ) + .finally(() => { + fetchJobFunctionsPromiseRef.current = null + }) + }, [ + backLink, + dispatch, + findSelectedWorkflowJob, + modifyAndSelectRun, + navigate, + params.jobId, + params.projectName, + params.workflowProjectName, + getPipelineError + ]) + + const setJobStatusAborting = useCallback( + task => { + setSelectedJob(state => ({ + ...state, + abortTaskId: task, + state: getState(ABORTING_STATE, JOBS_PAGE, JOB_KIND_JOB) + })) + }, + [setSelectedJob] + ) + + const onAbortJob = useCallback( + job => { + const refresh = () => { + refreshWorkflow() + fetchRun() + } - const onAbortJob = useCallback( - job => { - const refresh = () => { - refreshWorkflow() - fetchRun() + handleAbortJob( + job, + setNotification, + refresh, + setConfirmData, + dispatch, + abortJobRef, + setJobStatusAborting + ) + }, + [abortJobRef, dispatch, fetchRun, refreshWorkflow, setConfirmData, setJobStatusAborting] + ) + + const handleConfirmAbortJob = useCallback( + job => { + setConfirmData({ + item: job, + header: 'Abort job?', + message: ( +
+ Are you sure you want to abort the job "{job.name}"?
+ {isJobKindLocal(job) && + 'This is a local run. You can abort the run, though the actual process will continue.'} +
+ ), + btnConfirmLabel: 'Abort', + btnConfirmType: DANGER_BUTTON, + rejectHandler: () => { + setConfirmData(null) + }, + confirmHandler: () => { + onAbortJob(job) + setConfirmData(null) } + }) + }, + [onAbortJob, setConfirmData] + ) - handleAbortJob( - job, - setNotification, - refresh, - setConfirmData, - dispatch, - abortJobRef, - setJobStatusAborting + const onTerminateWorkflow = useCallback( + job => { + handleTerminateWorkflow(job, dispatch) + }, + [dispatch] + ) + + const onDeleteJob = useCallback( + job => { + handleDeleteJob(false, job, refreshWorkflow, null, filters, dispatch).then(() => { + navigate( + location.pathname + .split('/') + .splice(0, location.pathname.split('/').indexOf(params.workflowId) + 1) + .join('/') + window.location.search ) - }, - [abortJobRef, dispatch, fetchRun, refreshWorkflow, setConfirmData, setJobStatusAborting] - ) + }) + }, + [dispatch, filters, location.pathname, navigate, params.workflowId, refreshWorkflow] + ) + + const handleConfirmDeleteJob = useCallback( + job => { + setConfirmData({ + item: job, + header: 'Delete job?', + message: `Are you sure you want to delete the job "${job.name}"? Deleted jobs can not be restored.`, + btnConfirmLabel: 'Delete', + btnConfirmType: DANGER_BUTTON, + rejectHandler: () => { + setConfirmData(null) + }, + confirmHandler: () => { + onDeleteJob(job) + setConfirmData(null) + } + }) + }, + [onDeleteJob, setConfirmData] + ) + + const handleConfirmTerminateWorkflow = useCallback( + job => { + setConfirmData({ + item: job, + header: 'Terminate workflow', + message: `Are you sure you want to terminate the workflow "${job.name}" (stop its execution)? Workflows termination cannot be undone.`, + btnConfirmLabel: 'Terminate', + btnConfirmType: DANGER_BUTTON, + rejectHandler: () => { + setConfirmData(null) + }, + confirmHandler: () => { + onTerminateWorkflow(job) + setConfirmData(null) + } + }) + }, + [onTerminateWorkflow, setConfirmData] + ) - const handleConfirmAbortJob = useCallback( - job => { - setConfirmData({ - item: job, - header: 'Abort job?', - message: ( -
- Are you sure you want to abort the job "{job.name}"?
- {isJobKindLocal(job) && - 'This is a local run. You can abort the run, though the actual process will continue.'} -
- ), - btnConfirmLabel: 'Abort', - btnConfirmType: DANGER_BUTTON, - rejectHandler: () => { - setConfirmData(null) - }, - confirmHandler: () => { - onAbortJob(job) - setConfirmData(null) - } + const handleRerun = useCallback( + workflow => { + dispatch(rerunWorkflow({ project: workflow.project, workflowId: workflow.id })) + .unwrap() + .then(() => { + handleRetry() + dispatch( + setNotification({ + status: 200, + id: Math.random(), + message: 'Workflow run successfully.' + }) + ) }) - }, - [onAbortJob, setConfirmData] - ) - - const onTerminateWorkflow = useCallback( - job => { - handleTerminateWorkflow(job, dispatch) - }, - [dispatch] - ) - - const onDeleteJob = useCallback( - job => { - handleDeleteJob(false, job, refreshWorkflow, null, filters, dispatch).then(() => { - navigate( - location.pathname - .split('/') - .splice(0, location.pathname.split('/').indexOf(params.workflowId) + 1) - .join('/') + window.location.search + .catch(error => { + showErrorNotification(dispatch, error, 'Workflow did not run successfully', '', () => + handleRerun(workflow) ) }) - }, - [dispatch, filters, location.pathname, navigate, params.workflowId, refreshWorkflow] - ) + }, + [dispatch, handleRetry] + ) + + const actionsMenu = useMemo(() => { + return job => + generateActionsMenu( + job, + handleRerunJob, + appStore.frontendSpec.jobs_dashboard_url, + handleMonitoring, + appStore.frontendSpec.abortable_function_kinds, + appStore?.frontendSpec?.ce?.version, + handleConfirmAbortJob, + handleConfirmDeleteJob, + handleConfirmTerminateWorkflow, + accessibleProjectsMap, + toggleConvertedYaml, + handleRerun, + rerunIsDisabled + ) + }, [ + handleRerunJob, + appStore.frontendSpec.jobs_dashboard_url, + appStore.frontendSpec.abortable_function_kinds, + appStore.frontendSpec?.ce?.version, + handleMonitoring, + handleConfirmAbortJob, + handleConfirmDeleteJob, + handleConfirmTerminateWorkflow, + accessibleProjectsMap, + toggleConvertedYaml, + handleRerun, + rerunIsDisabled + ]) + + const handleCancel = useCallback(() => { + setSelectedJob({}) + setSelectedFunction({}) + setItemIsSelected(false) + }, [setItemIsSelected, setSelectedFunction, setSelectedJob]) + + const findSelectedWorkflowFunction = useCallback( + withoutRunType => { + if (workflowsStore.activeWorkflow?.data) { + const workflow = { ...workflowsStore.activeWorkflow.data } - const handleConfirmDeleteJob = useCallback( - job => { - setConfirmData({ - item: job, - header: 'Delete job?', - message: `Are you sure you want to delete the job "${job.name}"? Deleted jobs can not be restored.`, - btnConfirmLabel: 'Delete', - btnConfirmType: DANGER_BUTTON, - rejectHandler: () => { - setConfirmData(null) - }, - confirmHandler: () => { - onDeleteJob(job) - setConfirmData(null) - } - }) - }, - [onDeleteJob, setConfirmData] - ) + return find(workflow.graph, workflowItem => { + let workflowItemIsFound = + workflowItem.function?.includes(`${params.functionName}@${params.functionHash}`) || + workflowItem.function?.includes(params.functionName) || + workflowItem.function?.includes(params.jobId) - const handleConfirmTerminateWorkflow = useCallback( - job => { - setConfirmData({ - item: job, - header: 'Terminate workflow', - message: `Are you sure you want to terminate the workflow "${job.name}" (stop its execution)? Workflows termination cannot be undone.`, - btnConfirmLabel: 'Terminate', - btnConfirmType: DANGER_BUTTON, - rejectHandler: () => { - setConfirmData(null) - }, - confirmHandler: () => { - onTerminateWorkflow(job) - setConfirmData(null) + if (withoutRunType) { + workflowItemIsFound = workflowItemIsFound && workflowItem.run_type !== 'run' } - }) - }, - [onTerminateWorkflow, setConfirmData] - ) - - const handleRerun = useCallback( - workflow => { - dispatch(rerunWorkflow({ project: workflow.project, workflowId: workflow.id })) - .unwrap() - .then(() => { - handleRetry() - dispatch( - setNotification({ - status: 200, - id: Math.random(), - message: 'Workflow run successfully.' - }) - ) - }) - .catch(error => { - showErrorNotification(dispatch, error, 'Workflow did not run successfully', '', () => - handleRerun(workflow) - ) - }) - }, - [dispatch, handleRetry] - ) - - const actionsMenu = useMemo(() => { - return job => - generateActionsMenu( - job, - handleRerunJob, - appStore.frontendSpec.jobs_dashboard_url, - handleMonitoring, - appStore.frontendSpec.abortable_function_kinds, - appStore?.frontendSpec?.ce?.version, - handleConfirmAbortJob, - handleConfirmDeleteJob, - handleConfirmTerminateWorkflow, - accessibleProjectsMap, - toggleConvertedYaml, - handleRerun, - rerunIsDisabled - ) - }, [ - handleRerunJob, - appStore.frontendSpec.jobs_dashboard_url, - appStore.frontendSpec.abortable_function_kinds, - appStore.frontendSpec?.ce?.version, - handleMonitoring, - handleConfirmAbortJob, - handleConfirmDeleteJob, - handleConfirmTerminateWorkflow, - accessibleProjectsMap, - toggleConvertedYaml, - handleRerun, - rerunIsDisabled - ]) - - const handleCancel = useCallback(() => { - setSelectedJob({}) - setSelectedFunction({}) - setItemIsSelected(false) - }, [setItemIsSelected, setSelectedFunction, setSelectedJob]) - - const findSelectedWorkflowFunction = useCallback( - withoutRunType => { - if (workflowsStore.activeWorkflow?.data) { - const workflow = { ...workflowsStore.activeWorkflow.data } - return find(workflow.graph, workflowItem => { - let workflowItemIsFound = - workflowItem.function?.includes(`${params.functionName}@${params.functionHash}`) || - workflowItem.function?.includes(params.functionName) || - workflowItem.function?.includes(params.jobId) - - if (withoutRunType) { - workflowItemIsFound = workflowItemIsFound && workflowItem.run_type !== 'run' - } - - return workflowItemIsFound - }) - } - }, - [params.functionName, params.functionHash, params.jobId, workflowsStore.activeWorkflow.data] - ) - - const checkIfWorkflowItemIsJob = useCallback(() => { - if (workflowsStore.activeWorkflow?.data?.graph) { - let selectedWorkflowItem = findSelectedWorkflowFunction(true) + return workflowItemIsFound + }) + } + }, + [params.functionName, params.functionHash, params.jobId, workflowsStore.activeWorkflow.data] + ) - if (isEmpty(selectedWorkflowItem)) { - selectedWorkflowItem = findSelectedWorkflowFunction(false) - } + const checkIfWorkflowItemIsJob = useCallback(() => { + if (workflowsStore.activeWorkflow?.data?.graph) { + let selectedWorkflowItem = findSelectedWorkflowFunction(true) - return !['deploy', 'build'].includes(selectedWorkflowItem?.run_type) + if (isEmpty(selectedWorkflowItem)) { + selectedWorkflowItem = findSelectedWorkflowFunction(false) } - }, [workflowsStore.activeWorkflow.data.graph, findSelectedWorkflowFunction]) - const handleCatchRequest = useCallback( - (error, message) => { - showErrorNotification(dispatch, error, message, '') - navigate( - location.pathname - .split('/') - .splice(0, location.pathname.split('/').indexOf(params.workflowId) + 1) - .join('/') + window.location.search - ) - }, - [dispatch, location.pathname, navigate, params.workflowId] - ) + return !['deploy', 'build'].includes(selectedWorkflowItem?.run_type) + } + }, [workflowsStore.activeWorkflow.data.graph, findSelectedWorkflowFunction]) + + const handleCatchRequest = useCallback( + (error, message) => { + showErrorNotification(dispatch, error, message, '') + navigate( + location.pathname + .split('/') + .splice(0, location.pathname.split('/').indexOf(params.workflowId) + 1) + .join('/') + window.location.search + ) + }, + [dispatch, location.pathname, navigate, params.workflowId] + ) + + useEffect(() => { + if ( + !fetchJobFunctionsPromiseRef.current && + params.jobId && + (isEmpty(selectedJob) || params.jobId !== selectedJob.uid) && + checkIfWorkflowItemIsJob() && + !dataIsLoading + ) { + setDataIsLoading(true) + fetchRun().finally(() => setDataIsLoading(false)) + } + }, [fetchRun, params.jobId, selectedJob, checkIfWorkflowItemIsJob, dataIsLoading]) + + useEffect(() => { + const functionToBeSelected = findSelectedWorkflowFunction(true) + + if (isWorkflowStepExecutable(functionToBeSelected)) { + const workflow = { ...workflowsStore.activeWorkflow?.data } + const graphFunctionState = functionToBeSelected?.phase?.toLowerCase() + const isErrorState = [FAILED_STATE, ERROR_STATE].includes(graphFunctionState) + const customFunctionState = isErrorState ? graphFunctionState : '' + const pipelineError = getPipelineError(isErrorState) - useEffect(() => { if ( - !fetchJobFunctionsPromiseRef.current && - params.jobId && - (isEmpty(selectedJob) || params.jobId !== selectedJob.uid) && - checkIfWorkflowItemIsJob() && - !dataIsLoading + workflow.graph && + params.functionHash && + (isEmpty(selectedFunction) || params.functionHash !== selectedFunction.hash) ) { - setDataIsLoading(true) - fetchRun().finally(() => setDataIsLoading(false)) - } - }, [fetchRun, params.jobId, selectedJob, checkIfWorkflowItemIsJob, dataIsLoading]) - - useEffect(() => { - const functionToBeSelected = findSelectedWorkflowFunction(true) - - if (isWorkflowStepExecutable(functionToBeSelected)) { - const workflow = { ...workflowsStore.activeWorkflow?.data } - const graphFunctionState = functionToBeSelected?.phase?.toLowerCase() - const isErrorState = [FAILED_STATE, ERROR_STATE].includes(graphFunctionState) - const customFunctionState = isErrorState ? graphFunctionState : '' - const pipelineError = getPipelineError(isErrorState) - - if ( - workflow.graph && - params.functionHash && - (isEmpty(selectedFunction) || params.functionHash !== selectedFunction.hash) - ) { - if (params.functionName !== selectedFunction.name) { - dispatch( - fetchFunction({ - project: params.workflowProjectName || params.projectName, - name: params.functionName, - hash: params.functionHash === 'latest' ? '' : params.functionHash - }) - ) - .unwrap() - .then(func => { - setSelectedFunction( - parseFunction( - func, - params.workflowProjectName || params.projectName, - customFunctionState, - pipelineError - ) - ) - setItemIsSelected(true) - setSelectedJob({}) - }) - .catch(error => handleCatchRequest(error, 'Failed to fetch function')) - } - } else if ( - workflow.graph && - params.jobId && - (isEmpty(selectedFunction) || params.jobId !== selectedFunction.name) && - !checkIfWorkflowItemIsJob() - ) { + if (params.functionName !== selectedFunction.name) { dispatch( fetchFunction({ project: params.workflowProjectName || params.projectName, - name: params.jobId + name: params.functionName, + hash: params.functionHash === 'latest' ? '' : params.functionHash }) ) .unwrap() @@ -637,191 +609,217 @@ const WorkflowsTable = React.forwardRef( }) .catch(error => handleCatchRequest(error, 'Failed to fetch function')) } - } - }, [ - findSelectedWorkflowFunction, - handleCatchRequest, - params.functionHash, - params.functionName, - params.projectName, - selectedFunction, - workflowsStore.activeWorkflow, - checkIfWorkflowItemIsJob, - params.jobId, - dispatch, - setSelectedFunction, - setItemIsSelected, - setSelectedJob, - params.workflowProjectName, - getPipelineError - ]) - - useEffect(() => { - if ((params.jobId || params.functionHash) && pageData.details.menu.length > 0) { - isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) - } - }, [navigate, pageData.details.menu, location, params.jobId, params.functionHash, params.tab]) - - useEffect(() => { - const workflow = { ...workflowsStore.activeWorkflow?.data } - - if (!params.workflowId && workflow.graph) { - dispatch(resetWorkflow()) - } - - if (!workflow.graph && params.workflowId && !workflowIsLoaded) { - refreshWorkflow() - setWorkflowIsLoaded(true) - } - - if ( - ['Running', 'None'].includes(workflow?.run?.status) && - params.workflowId && - workflow.graph - ) { - const timeout = setTimeout(refreshWorkflow, 10000) - - return () => clearTimeout(timeout) - } - }, [ - refreshWorkflow, - dispatch, - workflowIsLoaded, - params.workflowId, - workflowsStore.activeWorkflow, - setWorkflowIsLoaded - ]) - - useEffect(() => { - if ( - jobWizardMode && - !jobWizardIsOpened && - ((jobWizardMode === PANEL_RERUN_MODE && editableItem?.rerun_object) || - jobWizardMode !== PANEL_RERUN_MODE) + } else if ( + workflow.graph && + params.jobId && + (isEmpty(selectedFunction) || params.jobId !== selectedFunction.name) && + !checkIfWorkflowItemIsJob() ) { - openPopUp(JobWizard, { - params: { - ...params, - projectName: editableItem?.rerun_object?.task?.metadata?.project || params.projectName - }, - onWizardClose: () => { - setEditableItem(null) - setJobWizardMode(null) - setJobWizardIsOpened(false) - }, - defaultData: jobWizardMode === PANEL_RERUN_MODE ? editableItem?.rerun_object : {}, - mode: jobWizardMode, - wizardTitle: jobWizardMode === PANEL_RERUN_MODE ? 'Batch re-run' : undefined - }) - - setJobWizardIsOpened(true) - } - }, [ - editableItem?.rerun_object, - filters, - jobWizardIsOpened, - jobWizardMode, - params, - setEditableItem, - setJobWizardIsOpened, - setJobWizardMode - ]) - - useEffect(() => { - abortJobRef.current?.() - }, [abortJobRef, params.jobId]) - - useEffect(() => { - if (!params.functionHash && !params.jobId) { - setItemIsSelected(false) - setSelectedJob({}) - setSelectedFunction({}) - } - }, [params.functionHash, params.jobId, setItemIsSelected, setSelectedFunction, setSelectedJob]) - - useEffect(() => { - if (workflowsStore.workflows.rerunInProgress) { - setRerunIsDisabled(true) - setTimeout(() => { - setRerunIsDisabled(false) - }, 5000) - } - }, [workflowsStore.workflows.rerunInProgress]) - - const virtualizationConfig = useVirtualization({ - rowsData: { - content: tableContent - }, - heightData: { - headerRowHeight: monitorWorkflowsHeaderRowHeight, - rowHeight: monitorWorkflowsRowHeight, - rowHeightExtended: monitorWorkflowsRowHeightExtended + dispatch( + fetchFunction({ + project: params.workflowProjectName || params.projectName, + name: params.jobId + }) + ) + .unwrap() + .then(func => { + setSelectedFunction( + parseFunction( + func, + params.workflowProjectName || params.projectName, + customFunctionState, + pipelineError + ) + ) + setItemIsSelected(true) + setSelectedJob({}) + }) + .catch(error => handleCatchRequest(error, 'Failed to fetch function')) } - }) - - return ( - <> - {(workflowsStore.workflows.loading || permissionsLoading) && } - {workflowsStore.workflows.loading ? null : (!workflowsStore.workflows.loading && - !params.workflowId && - workflowsStore.workflows.data.length === 0) || - requestErrorMessage ? ( - - ) : ( - <> - {params.workflowId ? ( - - ) : ( - - {sortedTableContent.map( - (tableItem, index) => - isRowRendered(virtualizationConfig, index) && ( - - ) - )} -
- )} - - )} - - ) - } -) + } + }, [ + findSelectedWorkflowFunction, + handleCatchRequest, + params.functionHash, + params.functionName, + params.projectName, + selectedFunction, + workflowsStore.activeWorkflow, + checkIfWorkflowItemIsJob, + params.jobId, + dispatch, + setSelectedFunction, + setItemIsSelected, + setSelectedJob, + params.workflowProjectName, + getPipelineError + ]) + + useEffect(() => { + if ((params.jobId || params.functionHash) && pageData.details.menu.length > 0) { + isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) + } + }, [navigate, pageData.details.menu, location, params.jobId, params.functionHash, params.tab]) + + useEffect(() => { + const workflow = { ...workflowsStore.activeWorkflow?.data } + + if (!params.workflowId && workflow.graph) { + dispatch(resetWorkflow()) + } + + if (!workflow.graph && params.workflowId && !workflowIsLoaded) { + refreshWorkflow() + setWorkflowIsLoaded(true) + } + + if ( + ['Running', 'None'].includes(workflow?.run?.status) && + params.workflowId && + workflow.graph + ) { + const timeout = setTimeout(refreshWorkflow, 10000) + + return () => clearTimeout(timeout) + } + }, [ + refreshWorkflow, + dispatch, + workflowIsLoaded, + params.workflowId, + workflowsStore.activeWorkflow, + setWorkflowIsLoaded + ]) + + useEffect(() => { + if ( + jobWizardMode && + !jobWizardIsOpened && + ((jobWizardMode === PANEL_RERUN_MODE && editableItem?.rerun_object) || + jobWizardMode !== PANEL_RERUN_MODE) + ) { + openPopUp(JobWizard, { + params: { + ...params, + projectName: editableItem?.rerun_object?.task?.metadata?.project || params.projectName + }, + onWizardClose: () => { + setEditableItem(null) + setJobWizardMode(null) + setJobWizardIsOpened(false) + }, + defaultData: jobWizardMode === PANEL_RERUN_MODE ? editableItem?.rerun_object : {}, + mode: jobWizardMode, + wizardTitle: jobWizardMode === PANEL_RERUN_MODE ? 'Batch re-run' : undefined + }) + + setJobWizardIsOpened(true) + } + }, [ + editableItem?.rerun_object, + filters, + jobWizardIsOpened, + jobWizardMode, + params, + setEditableItem, + setJobWizardIsOpened, + setJobWizardMode + ]) + + useEffect(() => { + abortJobRef.current?.() + }, [abortJobRef, params.jobId]) + + useEffect(() => { + if (!params.functionHash && !params.jobId) { + setItemIsSelected(false) + setSelectedJob({}) + setSelectedFunction({}) + } + }, [params.functionHash, params.jobId, setItemIsSelected, setSelectedFunction, setSelectedJob]) + + useEffect(() => { + if (workflowsStore.workflows.rerunInProgress) { + setRerunIsDisabled(true) + setTimeout(() => { + setRerunIsDisabled(false) + }, 5000) + } + }, [workflowsStore.workflows.rerunInProgress]) + + const virtualizationConfig = useVirtualization({ + rowsData: { + content: tableContent + }, + heightData: { + headerRowHeight: monitorWorkflowsHeaderRowHeight, + rowHeight: monitorWorkflowsRowHeight, + rowHeightExtended: monitorWorkflowsRowHeightExtended + } + }) + + return ( + <> + {(workflowsStore.workflows.loading || permissionsLoading) && } + {workflowsStore.workflows.loading ? null : (!workflowsStore.workflows.loading && + !params.workflowId && + workflowsStore.workflows.data.length === 0) || + requestErrorMessage ? ( + + ) : ( + <> + {params.workflowId ? ( + + ) : ( + + {sortedTableContent.map( + (tableItem, index) => + isRowRendered(virtualizationConfig, index) && ( + + ) + )} +
+ )} + + )} + + ) +} WorkflowsTable.displayName = 'WorkflowsTable' diff --git a/src/hooks/mode.hook.js b/src/hooks/mode.hook.js index a2ac86def2..604c71d134 100644 --- a/src/hooks/mode.hook.js +++ b/src/hooks/mode.hook.js @@ -41,11 +41,11 @@ export const useMode = () => { const urlMode = getUrlMode(window.location.search) useLayoutEffect(() => { - if (urlMode) { + if (urlMode && urlMode !== mode) { localStorageService.setStorageValue('mode', urlMode) - setMode(urlMode) + queueMicrotask(() => setMode(urlMode)) } - }, [urlMode]) + }, [mode, urlMode]) return { isDemoMode: mode === 'demo', diff --git a/src/hooks/nuclioMode.hook.js b/src/hooks/nuclioMode.hook.js index 88c5fea164..d7813b6546 100644 --- a/src/hooks/nuclioMode.hook.js +++ b/src/hooks/nuclioMode.hook.js @@ -37,7 +37,7 @@ export const useNuclioMode = () => { useLayoutEffect(() => { if (mode !== window.mlrunConfig.nuclioMode) { - setMode(window.mlrunConfig.nuclioMode) + queueMicrotask(() => setMode(window.mlrunConfig.nuclioMode)) } }, [mode]) diff --git a/src/hooks/openPanel.hook.js b/src/hooks/openPanel.hook.js index fc981e275f..943d0bb578 100644 --- a/src/hooks/openPanel.hook.js +++ b/src/hooks/openPanel.hook.js @@ -27,7 +27,9 @@ export const useOpenPanel = () => { const location = useLocation() useLayoutEffect(() => { - setPanelOpened(isPanelOpened(location.search)) + queueMicrotask(() => { + setPanelOpened(isPanelOpened(location.search)) + }) }, [location.search]) return panelOpened diff --git a/src/hooks/useJobsPageData.js b/src/hooks/useJobsPageData.js index d4a6c7a730..8967b1aa16 100644 --- a/src/hooks/useJobsPageData.js +++ b/src/hooks/useJobsPageData.js @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { useCallback, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useLocation, useParams } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' import { isEmpty } from 'lodash' @@ -62,6 +62,7 @@ export const useJobsPageData = (initialTabData, selectedTab) => { const location = useLocation() const appStore = useSelector(store => store.appStore) const lastCheckedJobIdRef = useRef(null) + const refreshJobsRef = useRef() const historyBackLink = useMemo(() => { const queryParams = getSavedSearchParams(location.search) @@ -159,7 +160,7 @@ export const useJobsPageData = (initialTabData, selectedTab) => { filters.project?.toLowerCase?.() || params.projectName || '*', abortJobRef, responseAbortingJobs, - () => refreshJobs(filters), + () => refreshJobsRef.current?.(filters), dispatch ) } @@ -192,6 +193,10 @@ export const useJobsPageData = (initialTabData, selectedTab) => { [dispatch, params.jobName, params.projectName, terminateAbortTasksPolling] ) + useEffect(() => { + refreshJobsRef.current = refreshJobs + }, [refreshJobs]) + const refreshScheduled = useCallback( filters => { setScheduledJobs([]) diff --git a/src/hooks/useModalBlockHistory.hook.js b/src/hooks/useModalBlockHistory.hook.js index 7d903db25d..765fa8b112 100644 --- a/src/hooks/useModalBlockHistory.hook.js +++ b/src/hooks/useModalBlockHistory.hook.js @@ -24,17 +24,20 @@ import { defaultCloseModalHandler } from '../utils/defaultCloseModalHandler' import { areFormValuesChanged } from 'igz-controls/utils/form.util' export const useModalBlockHistory = (closeModal, form) => { - const shouldBlock = useCallback(({ currentLocation, nextLocation }) => { - const { initialValues, values } = form.getState() + const shouldBlock = useCallback( + ({ currentLocation, nextLocation }) => { + const { initialValues, values } = form.getState() - const isFormDirty = areFormValuesChanged(initialValues, values) + const isFormDirty = areFormValuesChanged(initialValues, values) - if (!isFormDirty && currentLocation.pathname !== nextLocation.pathname) { - closeModal() - } + if (!isFormDirty && currentLocation.pathname !== nextLocation.pathname) { + closeModal() + } - return isFormDirty && currentLocation.pathname !== nextLocation.pathname - }, [closeModal, form]) + return isFormDirty && currentLocation.pathname !== nextLocation.pathname + }, + [closeModal, form] + ) let blocker = useBlocker(shouldBlock) diff --git a/src/hooks/useRefreshAlerts.hook.js b/src/hooks/useRefreshAlerts.hook.js index 4836fb319c..46e145b7fd 100644 --- a/src/hooks/useRefreshAlerts.hook.js +++ b/src/hooks/useRefreshAlerts.hook.js @@ -90,7 +90,9 @@ export const useRefreshAlerts = (filters, isAlertsPage) => { ) useEffect(() => { - !isAlertsPage && refreshAlerts(filters) + queueMicrotask(() => { + !isAlertsPage && refreshAlerts(filters) + }) }, [isAlertsPage, refreshAlerts, filters]) return { diff --git a/src/hooks/useSortTable.hook.jsx b/src/hooks/useSortTable.hook.jsx index ea87085c13..34523e8be0 100644 --- a/src/hooks/useSortTable.hook.jsx +++ b/src/hooks/useSortTable.hook.jsx @@ -17,8 +17,8 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { useCallback, useEffect, useState, useMemo } from 'react' -import { isEmpty, isNumber, orderBy, isEqual } from 'lodash' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { isEmpty, isEqual, isNumber, orderBy } from 'lodash' import ArrowIcon from 'igz-controls/images/back-arrow.svg?react' @@ -38,7 +38,9 @@ export const useSortTable = ({ headers, content, sortConfig = {} }) => { useEffect(() => { if (!isEqual(config, sortConfig)) { - setConfig(sortConfig) + queueMicrotask(() => { + setConfig(sortConfig) + }) } }, [sortConfig, config]) @@ -87,16 +89,13 @@ export const useSortTable = ({ headers, content, sortConfig = {} }) => { ) const isSortableByIndex = useCallback(() => { - let isSortByIndex = - isNumber(allowSortBy) || isNumber(excludeSortBy) - ? true - : Array.isArray(allowSortBy) - ? allowSortBy.every(allowedIndex => isNumber(allowedIndex)) - : Array.isArray(excludeSortBy) - ? excludeSortBy.every(allowedIndex => isNumber(allowedIndex)) - : false - - return isSortByIndex + return isNumber(allowSortBy) || isNumber(excludeSortBy) + ? true + : Array.isArray(allowSortBy) + ? allowSortBy.every(allowedIndex => isNumber(allowedIndex)) + : Array.isArray(excludeSortBy) + ? excludeSortBy.every(allowedIndex => isNumber(allowedIndex)) + : false }, [allowSortBy, excludeSortBy]) const isSortable = useCallback( @@ -202,28 +201,32 @@ export const useSortTable = ({ headers, content, sortConfig = {} }) => { } useEffect(() => { - if (direction && selectedColumnName) { - sortTable(selectedColumnName, direction) - } else if (defaultSortBy !== null && (!direction || defaultDirection) && content.length > 0) { - sortTable( - selectedColumnName - ? selectedColumnName - : isNumber(defaultSortBy) - ? headers[defaultSortBy].headerId - : defaultSortBy, - defaultDirection - ) - } else { - setSortedTableContent(content) - } + queueMicrotask(() => { + if (direction && selectedColumnName) { + sortTable(selectedColumnName, direction) + } else if (defaultSortBy !== null && (!direction || defaultDirection) && content.length > 0) { + sortTable( + selectedColumnName + ? selectedColumnName + : isNumber(defaultSortBy) + ? headers[defaultSortBy].headerId + : defaultSortBy, + defaultDirection + ) + } else { + setSortedTableContent(content) + } + }) }, [content, defaultDirection, defaultSortBy, direction, headers, selectedColumnName, sortTable]) useEffect(() => { - if (headers && headers.length > 0 && (excludeSortBy || allowSortBy)) { - const header = getSortableHeaders() + queueMicrotask(() => { + if (headers && headers.length > 0 && (excludeSortBy || allowSortBy)) { + const header = getSortableHeaders() - setSortedTableHeaders(header) - } + setSortedTableHeaders(header) + } + }) }, [allowSortBy, excludeSortBy, getSortableHeaders, headers]) return { sortTable, selectedColumnName, getSortingIcon, sortedTableContent, sortedTableHeaders } diff --git a/src/hooks/useVirtualization.hook.js b/src/hooks/useVirtualization.hook.js index 41184a7233..6882c00ef4 100644 --- a/src/hooks/useVirtualization.hook.js +++ b/src/hooks/useVirtualization.hook.js @@ -245,7 +245,9 @@ export const useVirtualization = ({ useLayoutEffect(() => { if (isEmpty(rowsData.content) && !isEqual(rowsSizes, rowsSizesLocal)) { - setRowsSizesLocal(rowsSizes) + queueMicrotask(() => { + setRowsSizesLocal(rowsSizes) + }) } }, [rowsSizesLocal, rowsData, rowsSizes]) @@ -260,7 +262,9 @@ export const useVirtualization = ({ ) if (!isEqual(rowsSizesLocal, newRowsSizes)) { - setRowsSizesLocal(newRowsSizes) + queueMicrotask(() => { + setRowsSizesLocal(newRowsSizes) + }) } } }, [ @@ -355,7 +359,9 @@ export const useVirtualization = ({ tableElement.addEventListener('scroll', calculateVirtualizationConfig) window.addEventListener('resize', calculateVirtualizationConfig) } else { - setVirtualizationConfig(virtualizationConfigInitialState) + queueMicrotask(() => { + setVirtualizationConfig(virtualizationConfigInitialState) + }) } return () => { diff --git a/src/layout/Header/header.scss b/src/layout/Header/header.scss index f89ceade06..b95e95f579 100644 --- a/src/layout/Header/header.scss +++ b/src/layout/Header/header.scss @@ -5,10 +5,6 @@ @use 'igz-controls/scss/borders'; .header { - line-height: 1.5; - - @include mixins.fixed; - z-index: 12; display: flex; align-items: center; @@ -16,9 +12,12 @@ min-height: 64px; padding: 0 24px; color: colors.$white; + line-height: 1.5; background-color: colors.$ebonyClay; box-shadow: shadows.$mainHeaderShadow; + @include mixins.fixed; + &__brand { display: flex; align-items: flex-end; @@ -60,10 +59,10 @@ display: flex; align-items: center; justify-content: center; + padding: 8px; + color: colors.$white; background-color: colors.$cerulean; border-radius: 50%; - color: colors.$white; - padding: 8px; transition: background-color 0.2s ease-in-out; & > * { diff --git a/src/layout/Navbar/Navbar.scss b/src/layout/Navbar/Navbar.scss index 8d63e91673..1c6da5e17d 100644 --- a/src/layout/Navbar/Navbar.scss +++ b/src/layout/Navbar/Navbar.scss @@ -3,14 +3,14 @@ @use 'igz-controls/scss/borders'; .navbar { - display: flex; - flex-grow: 1; - flex-shrink: 0; - flex-flow: column nowrap; position: absolute; top: 0; left: 0; z-index: 11; + display: flex; + flex-flow: column nowrap; + flex-grow: 1; + flex-shrink: 0; height: 100%; background-color: colors.$wildSand; border-right: borders.$tertiaryBorder; @@ -27,8 +27,8 @@ } .navbar__pin-icon { - opacity: 1; visibility: visible; + opacity: 1; } } @@ -53,12 +53,12 @@ margin-bottom: 20px; &::before { - content: ''; display: block; width: 100%; height: 1px; - border-top: borders.$primaryBorder; margin-bottom: 10px; + border-top: borders.$primaryBorder; + content: ''; } } } @@ -76,8 +76,8 @@ display: flex; justify-content: flex-end; padding: 0.5rem; - opacity: 0; visibility: hidden; + opacity: 0; transition: all 0.3s ease-in-out; button { diff --git a/src/layout/Page/Page.jsx b/src/layout/Page/Page.jsx index 20ba4df3f2..1882191b79 100644 --- a/src/layout/Page/Page.jsx +++ b/src/layout/Page/Page.jsx @@ -77,7 +77,9 @@ const Page = () => { navigate('/projects') }) } else { - setProjectFetched(true) + queueMicrotask(() => { + setProjectFetched(true) + }) } }, [dispatch, location.pathname, navigate, projectName, projectsList.length]) diff --git a/src/layout/Page/Page.scss b/src/layout/Page/Page.scss index 2222511699..390dc777d3 100644 --- a/src/layout/Page/Page.scss +++ b/src/layout/Page/Page.scss @@ -7,6 +7,7 @@ display: flex; justify-content: flex-end; width: 100%; + // transform: translateX(variables.$navbarTogglerWidth); transition: all 0.3s ease-in-out; } @@ -17,6 +18,6 @@ flex: 1; flex-flow: column nowrap; align-items: flex-start; - overflow: auto; width: 100%; + overflow: auto; } diff --git a/src/scss/main.scss b/src/scss/main.scss index b95ed7e119..fb64c367a8 100644 --- a/src/scss/main.scss +++ b/src/scss/main.scss @@ -87,7 +87,7 @@ main { .page-actions { display: flex; justify-content: flex-end; - margin: 0 0 20px 0; + margin: 0 0 20px; .search-container { flex: unset; diff --git a/src/stories/Link.stories.js b/src/stories/Link.stories.jsx similarity index 100% rename from src/stories/Link.stories.js rename to src/stories/Link.stories.jsx diff --git a/src/utils/wrapComponentForNavbarNavigationTracking.jsx b/src/utils/wrapComponentForNavbarNavigationTracking.jsx index 26cd2e304f..c76ef67c48 100644 --- a/src/utils/wrapComponentForNavbarNavigationTracking.jsx +++ b/src/utils/wrapComponentForNavbarNavigationTracking.jsx @@ -17,19 +17,22 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { useMemo, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { useLocation } from 'react-router-dom' const wrapComponentForNavbarNavigationTracking = WrappedComponent => { const Wrap = props => { const location = useLocation() const savedKeyRef = useRef(0) - const key = useMemo(() => { + const [key, setKey] = useState(0) + + useEffect(() => { if (location.state?.navbarNavigate) { - return ++savedKeyRef.current + savedKeyRef.current += 1 + setKey(savedKeyRef.current) + } else { + setKey(savedKeyRef.current) } - - return savedKeyRef.current }, [location.state?.navbarNavigate]) return @@ -38,4 +41,4 @@ const wrapComponentForNavbarNavigationTracking = WrappedComponent => { return Wrap } -export default wrapComponentForNavbarNavigationTracking \ No newline at end of file +export default wrapComponentForNavbarNavigationTracking diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index 13f5109b70..854c98917b 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -3031,7 +3031,6 @@ app.get( ) app.get(`${mlrunAPIIngress}/projects/:project/runs`, getRuns) -app.get(`${mlrunAPIIngress}/projects/*/runs`, getRuns) app.get(`${mlrunAPIIngress}/projects/:project/alert-activations`, getAlerts) app.get(`${mlrunAPIIngress}/projects/:project/alert-activations/:id`, getAlert) app.get(`${mlrunAPIIngress}/projects/:project/runs/:uid`, getRun) @@ -3047,14 +3046,12 @@ app.get(`${mlrunAPIIngress}/hub/sources/:project/item-object`, getFunctionObject app.get(`${mlrunIngress}/:function/function.yaml`, getFunctionTemplate) app.get(`${mlrunAPIIngress}/projects/:project/schedules`, getProjectsSchedules) -app.get(`${mlrunAPIIngress}/projects/*/schedules`, getProjectsSchedules) app.get(`${mlrunAPIIngress}/projects/:project/schedules/:schedule`, getProjectsSchedule) app.delete(`${mlrunAPIIngress}/projects/:project/schedules/:schedule`, deleteSchedule) app.post(`${mlrunAPIIngress}/projects/:project/schedules/:schedule/invoke`, invokeSchedule) app.put(`${mlrunAPIIngress}/projects/:project/schedules/:schedule/`, updateSchedule) app.get(`${mlrunAPIIngress}/projects/:project/pipelines`, getPipelines) -app.get(`${mlrunAPIIngress}/projects/*/pipelines`, getPipelines) app.get(`${mlrunAPIIngress}/projects/:project/pipelines/:pipelineID`, getPipeline) app.post(`${mlrunAPIIngress}/projects/:project/pipelines/:pipelineID/retry`, pipelineRetry) app.post(`${mlrunAPIIngress}/projects/:project/pipelines/:pipelineID/terminate`, pipelineTerminate) @@ -3102,7 +3099,7 @@ app.get(`${mlrunAPIIngress}/projects/:project/functions/:func`, getFunc) app.post(`${mlrunAPIIngress}/projects/:project/functions/:func`, postFunc) app.get( - `${mlrunAPIIngress}/projects/:project/:featureArtifact/*/tags`, + `${mlrunAPIIngress}/projects/:project/:featureArtifact/:name/tags`, getProjectsFeatureArtifactTags ) diff --git a/vite.config.mjs b/vite.config.mjs index 20b71de28d..0a9b9907c3 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -59,7 +59,8 @@ export default defineConfig(({ mode }) => { 'igz-controls': path.resolve( __dirname, 'node_modules/iguazio.dashboard-react-controls/dist' - ) + ), + '@': path.resolve(__dirname, 'src') }, dedupe: [ 'react',