From 48a5913d5ad3761cac67c1ec67e5fa6802be5b66 Mon Sep 17 00:00:00 2001 From: mariana-furyk <58301139+mariana-furyk@users.noreply.github.com> Date: Sun, 26 Mar 2023 14:07:13 +0300 Subject: [PATCH 01/11] Fix [Target Path] UI should get "store://datasets/" format as an input to the function (#1681) --- src/common/TargetPath/TargetPath.js | 22 +++++++-- src/common/TargetPath/targetPath.util.js | 46 ++++++++++--------- .../FeatureSetsPanelDataSource.js | 20 +++++++- .../featureSetsPanelDataSource.util.js | 17 +++---- .../FeatureSetsPanelDataSourceTable.js | 19 -------- src/reducers/artifactsReducer.js | 8 ++-- src/utils/panelPathScheme.js | 23 ++++++---- 7 files changed, 86 insertions(+), 69 deletions(-) delete mode 100644 src/components/FeatureSetsPanel/FeatureSetsPanelDataSourceTable/FeatureSetsPanelDataSourceTable.js diff --git a/src/common/TargetPath/TargetPath.js b/src/common/TargetPath/TargetPath.js index dc59a80ba..cce71de7d 100644 --- a/src/common/TargetPath/TargetPath.js +++ b/src/common/TargetPath/TargetPath.js @@ -40,7 +40,7 @@ import { } from './targetPath.util' import featureStoreActions from '../../actions/featureStore' import projectAction from '../../actions/projects' -import { MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../constants' +import { ARTIFACT_OTHER_TYPE, DATASET_TYPE, MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../constants' import { fetchArtifact, fetchArtifacts } from '../../reducers/artifactsReducer' import { getFeatureReference } from '../../utils/resources' @@ -141,8 +141,22 @@ const TargetPath = ({ dataInputState.storePathType && dataInputState.project ) { - if (dataInputState.storePathType === 'artifacts' && dataInputState.artifacts.length === 0) { - dispatch(fetchArtifacts({ project: dataInputState.project })) + if ( + dataInputState.storePathType !== 'feature-vectors' && + dataInputState.artifacts.length === 0 + ) { + dispatch( + fetchArtifacts({ + project: dataInputState.project, + filters: null, + config: { + params: { + category: + dataInputState.storePathType === 'artifacts' ? ARTIFACT_OTHER_TYPE : DATASET_TYPE + } + } + }) + ) .unwrap() .then(artifacts => { setDataInputState(prev => ({ @@ -189,7 +203,7 @@ const TargetPath = ({ const projectItem = dataInputState.projectItem if (dataInputState.inputProjectItemPathEntered && storePathType && projectName && projectItem) { - if (storePathType === 'artifacts' && dataInputState.artifactsReferences.length === 0) { + if (storePathType !== 'feature-vectors' && dataInputState.artifactsReferences.length === 0) { dispatch(fetchArtifact({ project: projectName, artifact: projectItem })) .unwrap() .then(artifacts => { diff --git a/src/common/TargetPath/targetPath.util.js b/src/common/TargetPath/targetPath.util.js index 2a9bfaf8b..121f7f633 100644 --- a/src/common/TargetPath/targetPath.util.js +++ b/src/common/TargetPath/targetPath.util.js @@ -26,7 +26,7 @@ import { S3_INPUT_PATH_SCHEME, V3IO_INPUT_PATH_SCHEME } from '../../constants' -import { isNil } from 'lodash' +import { isNil, uniqBy } from 'lodash' import { getArtifactReference, getParsedResource } from '../../utils/resources' export const pathPlaceholders = { @@ -54,7 +54,12 @@ export const targetPathInitialState = { } export const pathTips = storePathType => { - const pathType = storePathType === 'feature-vectors' ? 'feature-vector' : 'artifact' + const pathType = + storePathType === 'feature-vectors' + ? 'feature-vector' + : storePathType === 'artifacts' + ? 'artifact' + : 'dataset' return { [MLRUN_STORAGE_INPUT_PATH_SCHEME]: `${pathType}s/my-project/my-${pathType}:my-tag" or "${pathType}s/my-project/my-${pathType}@my-uid`, @@ -70,6 +75,10 @@ export const storePathTypes = [ label: 'Artifacts', id: 'artifacts' }, + { + label: 'Datasets', + id: 'datasets' + }, { label: 'Feature vectors', id: 'feature-vectors' @@ -80,11 +89,11 @@ export const handleStoreInputPathChange = (targetPathState, setTargetPathState, const pathItems = value.split('/') const [projectItem, projectItemReference] = getParsedResource(pathItems[2]) const projectItems = - targetPathState[pathItems[0] === 'artifacts' ? 'artifacts' : 'featureVectors'] + targetPathState[pathItems[0] !== 'feature-vectors' ? 'artifacts' : 'featureVectors'] const projectItemIsEntered = projectItems.find(project => project.id === projectItem) const projectItemsReferences = targetPathState[ - pathItems[0] === 'artifacts' ? 'artifactsReferences' : 'featureVectorsReferences' + pathItems[0] !== 'feature-vectors' ? 'artifactsReferences' : 'featureVectorsReferences' ] const projectItemReferenceIsEntered = projectItemsReferences.find( projectItemRef => projectItemRef.id === projectItemReference @@ -206,23 +215,12 @@ export const generateComboboxMatchesList = ( } else if (!inputProjectPathEntered && storePathTypes.some(type => type.id === storePathType)) { return projects.filter(proj => proj.id.startsWith(project)) } else if (!inputProjectItemPathEntered) { - const selectedStorePathType = storePathType - const projectItems = - selectedStorePathType === 'artifacts' - ? artifacts - : selectedStorePathType === 'feature-vectors' - ? featureVectors - : null + const projectItems = storePathType === 'feature-vectors' ? featureVectors : artifacts return projectItems ? projectItems.filter(projItem => projItem.id.startsWith(projectItem)) : [] } else if (!inputProjectItemReferencePathEntered) { - const selectedStorePathType = storePathType const projectItemsReferences = - selectedStorePathType === 'artifacts' - ? artifactsReferences - : selectedStorePathType === 'feature-vectors' - ? featureVectorsReferences - : null + storePathType === 'feature-vectors' ? featureVectorsReferences : artifactsReferences return projectItemsReferences ? projectItemsReferences.filter(projectItem => @@ -234,8 +232,8 @@ export const generateComboboxMatchesList = ( } } -export const generateArtifactsList = artifacts => - artifacts +export const generateArtifactsList = artifacts => { + const generatedArtifacts = artifacts .map(artifact => { const key = artifact.link_iteration ? artifact.link_iteration.db_key : artifact.key ?? '' return { @@ -246,8 +244,11 @@ export const generateArtifactsList = artifacts => .filter(artifact => artifact.label !== '') .sort((prevArtifact, nextArtifact) => prevArtifact.id.localeCompare(nextArtifact.id)) -export const generateArtifactsReferencesList = artifacts => - artifacts + return uniqBy(generatedArtifacts, 'id') +} + +export const generateArtifactsReferencesList = artifacts => { + const generatedArtifacts = artifacts .map(artifact => { const artifactReference = getArtifactReference(artifact) @@ -268,3 +269,6 @@ export const generateArtifactsReferencesList = artifacts => return prevRefTree.localeCompare(nextRefTree) } }) + + return uniqBy(generatedArtifacts, 'id') +} diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSource.js b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSource.js index c473253e1..1a2aac80b 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSource.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSource.js @@ -25,7 +25,11 @@ import PropTypes from 'prop-types' import FeatureSetsPanelDataSourceView from './FeatureSetsPanelDataSourceView' import featureStoreActions from '../../../actions/featureStore' -import { MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../../constants' +import { + ARTIFACT_OTHER_TYPE, + DATASET_TYPE, + MLRUN_STORAGE_INPUT_PATH_SCHEME +} from '../../../constants' import { getParsedResource } from '../../../utils/resources' import { CSV, @@ -95,7 +99,18 @@ const FeatureSetsPanelDataSource = ({ useEffect(() => { if (urlProjectItemTypeEntered && urlProjectPathEntered && artifacts.length === 0) { - dispatch(fetchArtifacts({ project: data.url.project })) + dispatch( + fetchArtifacts({ + project: data.url.project, + filters: null, + config: { + params: { + category: + data.url.projectItemType === 'artifacts' ? ARTIFACT_OTHER_TYPE : DATASET_TYPE + } + } + }) + ) .unwrap() .then(artifacts => { if (artifacts?.length > 0) { @@ -106,6 +121,7 @@ const FeatureSetsPanelDataSource = ({ }, [ artifacts.length, data.url.project, + data.url.projectItemType, dispatch, urlProjectItemTypeEntered, urlProjectPathEntered diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js index c55cff1e6..abfe478fb 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js @@ -76,9 +76,7 @@ export const generateComboboxMatchesList = ( urlProjectItemTypeEntered ) => { if (!urlProjectItemTypeEntered) { - return projectItemsPathTypes.some(type => - type.id.startsWith(url.projectItemType) - ) + return projectItemsPathTypes.some(type => type.id.startsWith(url.projectItemType)) ? projectItemsPathTypes : [] } else if ( @@ -105,20 +103,19 @@ export const projectItemsPathTypes = [ { label: 'Artifacts', id: 'artifacts' + }, + { + label: 'Datasets', + id: 'datasets' } ] -export const isUrlInputValid = ( - pathInputType, - pathInputValue, - dataSourceKind -) => { +export const isUrlInputValid = (pathInputType, pathInputValue, dataSourceKind) => { const regExp = dataSourceKind === CSV ? /^artifacts\/(.+?)\/(.+?)(#(.+?))?(:(.+?))?(@(.+))?(? 0 && /.*?\/(.*?)/.test(pathInputValue) + const defaultValidation = pathInputValue.length > 0 && /.*?\/(.*?)/.test(pathInputValue) switch (pathInputType) { case MLRUN_STORAGE_INPUT_PATH_SCHEME: diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSourceTable/FeatureSetsPanelDataSourceTable.js b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSourceTable/FeatureSetsPanelDataSourceTable.js deleted file mode 100644 index 6cd08cc01..000000000 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSourceTable/FeatureSetsPanelDataSourceTable.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2019 Iguazio Systems Ltd. - -Licensed under the Apache License, Version 2.0 (the "License") with -an addition restriction as set forth herein. You may not use this -file except in compliance with the License. You may obtain a copy of -the License at http://www.apache.org/licenses/LICENSE-2.0. - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied. See the License for the specific language governing -permissions and limitations under the License. - -In addition, you may not use the software for any purposes that are -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. -*/ diff --git a/src/reducers/artifactsReducer.js b/src/reducers/artifactsReducer.js index e53d44583..3c0033b0a 100644 --- a/src/reducers/artifactsReducer.js +++ b/src/reducers/artifactsReducer.js @@ -86,9 +86,11 @@ export const fetchArtifact = createAsyncThunk('fetchArtifact', ({ project, artif return filterArtifacts(data.artifacts) }) }) -export const fetchArtifacts = createAsyncThunk('fetchArtifacts', ({ project, filters }) => { - return artifactsApi.getArtifacts(project, filters).then(({ data }) => { - return filterArtifacts(data.artifacts) +export const fetchArtifacts = createAsyncThunk('fetchArtifacts', ({ project, filters, config }) => { + return artifactsApi.getArtifacts(project, filters, config).then(({ data }) => { + const result = parseArtifacts(data.artifacts) + + return generateArtifacts(filterArtifacts(result)) }) }) export const fetchArtifactTags = createAsyncThunk('fetchArtifactTags', ({ project, category }) => { diff --git a/src/utils/panelPathScheme.js b/src/utils/panelPathScheme.js index 4c0a43b8c..140a16b29 100644 --- a/src/utils/panelPathScheme.js +++ b/src/utils/panelPathScheme.js @@ -25,6 +25,7 @@ import { S3_INPUT_PATH_SCHEME, V3IO_INPUT_PATH_SCHEME } from '../constants' +import { uniqBy } from 'lodash' export const generateProjectsList = (projectsList, currentProject) => projectsList @@ -40,24 +41,23 @@ export const generateProjectsList = (projectsList, currentProject) => : prevProject.id.localeCompare(nextProject.id) }) -export const generateArtifactsList = artifacts => - artifacts +export const generateArtifactsList = artifacts => { + const generatedArtifacts = artifacts .map(artifact => { - const key = artifact.link_iteration - ? artifact.link_iteration.db_key - : artifact.key ?? '' + const key = artifact.link_iteration ? artifact.link_iteration.db_key : artifact.key ?? '' return { label: key, id: key } }) .filter(artifact => artifact.label !== '') - .sort((prevArtifact, nextArtifact) => - prevArtifact.id.localeCompare(nextArtifact.id) - ) + .sort((prevArtifact, nextArtifact) => prevArtifact.id.localeCompare(nextArtifact.id)) -export const generateArtifactsReferencesList = artifacts => - artifacts + return uniqBy(generatedArtifacts, 'id') +} + +export const generateArtifactsReferencesList = artifacts => { + const generatedArtifacts = artifacts .map(artifact => { const artifactReference = getArtifactReference(artifact) @@ -79,6 +79,9 @@ export const generateArtifactsReferencesList = artifacts => } }) + return uniqBy(generatedArtifacts, 'id') +} + export const pathPlaceholders = { [MLRUN_STORAGE_INPUT_PATH_SCHEME]: 'artifacts/my-project/my-artifact:my-tag', [S3_INPUT_PATH_SCHEME]: 'bucket/path', From 53da373ad26e9f0e65925ee9427b50261299b7b9 Mon Sep 17 00:00:00 2001 From: mariana-furyk <58301139+mariana-furyk@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:30:28 +0300 Subject: [PATCH 02/11] Fix [Feature Set] Target path for partition parquet should be taken as Folder `1.3.x` (#1682) --- .../FeatureSetsPanelTargetStore.js | 100 +++++++++--------- .../FeatureSetsPanelTargetStoreView.js | 24 ++++- .../featureSetsPanelTargetStore.scss | 2 +- .../featureSetsPanelTargetStore.util.js | 16 +++ 4 files changed, 87 insertions(+), 55 deletions(-) diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.js b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.js index 4b6cb7ab9..6f5011eaa 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStore.js @@ -274,9 +274,11 @@ const FeatureSetsPanelTargetStore = ({ const handleDiscardPathChange = kind => { const currentStoreType = kind === ONLINE ? NOSQL : kind - const currentKind = featureStore.newFeatureSet.spec.targets.find(el => el.kind === currentStoreType) + const currentKind = featureStore.newFeatureSet.spec.targets.find( + el => el.kind === currentStoreType + ) - if (currentKind .path.length > 0) { + if (currentKind.path.length > 0) { setData(state => ({ ...state, [kind]: { @@ -564,57 +566,57 @@ const FeatureSetsPanelTargetStore = ({ } const triggerPartitionCheckbox = (id, kind) => { - if (kind === EXTERNAL_OFFLINE || kind === PARQUET) { - setData(state => { - let path = state[kind].path - - if ( - kind === PARQUET && - !targetsPathEditData.parquet.isEditMode && - !targetsPathEditData.parquet.isModified - ) { - path = generatePath( - frontendSpec.feature_store_data_prefixes, - project, - data[kind].kind, - featureStore.newFeatureSet.metadata.name, - data[kind].partitioned ? PARQUET : '' - ) - } + setData(state => { + let path = state[kind].path + + if ( + kind === PARQUET && + !targetsPathEditData.parquet.isEditMode && + !targetsPathEditData.parquet.isModified + ) { + path = generatePath( + frontendSpec.feature_store_data_prefixes, + project, + data[kind].kind, + featureStore.newFeatureSet.metadata.name, + data[kind].partitioned ? PARQUET : '' + ) + } else if (kind === PARQUET && targetsPathEditData.parquet.isModified) { + path = state[kind].partitioned ? `${path}.parquet` : path.replace(/\.[^.]+$/, '') + } - return data[kind]?.partitioned - ? { - ...state, - [kind]: { - ...dataInitialState[kind], - path, - kind: PARQUET - } + return data[kind]?.partitioned + ? { + ...state, + [kind]: { + ...dataInitialState[kind], + path, + kind: PARQUET } - : { - ...state, - [kind]: { - ...state[kind], - path, - partitioned: state[kind].partitioned === id ? '' : id, - key_bucketing_number: '', - partition_cols: '', - time_partitioning_granularity: 'hour' - } + } + : { + ...state, + [kind]: { + ...state[kind], + path, + partitioned: state[kind].partitioned === id ? '' : id, + key_bucketing_number: '', + partition_cols: '', + time_partitioning_granularity: 'hour' } - }) + } + }) - if (data[kind].partitioned) { - setShowAdvanced(state => ({ ...state, [kind]: false })) - setPartitionRadioButtonsState(state => ({ - ...state, - [kind]: 'districtKeys' - })) - setSelectedPartitionKind(state => ({ - ...state, - [kind]: [...selectedPartitionKindInitialState[kind]] - })) - } + if (data[kind].partitioned) { + setShowAdvanced(state => ({ ...state, [kind]: false })) + setPartitionRadioButtonsState(state => ({ + ...state, + [kind]: 'districtKeys' + })) + setSelectedPartitionKind(state => ({ + ...state, + [kind]: [...selectedPartitionKindInitialState[kind]] + })) } const targets = cloneDeep(featureStore.newFeatureSet.spec.targets).map(targetKind => { diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStoreView.js b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStoreView.js index 9c9ab59ab..541151efc 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStoreView.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/FeatureSetsPanelTargetStoreView.js @@ -34,7 +34,9 @@ import { externalOfflineKindOptions, ONLINE, PARQUET, - checkboxModels + checkboxModels, + isParquetPathValid, + getInvalidParquetPathMessage } from './featureSetsPanelTargetStore.util' import { ReactComponent as Online } from 'igz-controls/images/nosql.svg' @@ -178,14 +180,18 @@ const FeatureSetsPanelTargetStoreView = ({ density="normal" floatingLabel focused={frontendSpecIsNotEmpty} - invalid={!validation.isOfflineTargetPathValid} + invalid={isParquetPathValid( + validation.isOfflineTargetPathValid, + data.parquet + )} + invalidText={getInvalidParquetPathMessage(data.parquet)} label="Path" - onChange={path => + onChange={path => { setData(state => ({ ...state, parquet: { ...state.parquet, path } })) - } + }} placeholder={ 'v3io:///projects/{project}/FeatureStore/{name}/{run_id}/parquet/sets/{name}.parquet' } @@ -201,7 +207,14 @@ const FeatureSetsPanelTargetStoreView = ({ wrapperClassName="offline-path" />
- + )} triggerPartitionCheckbox(id, PARQUET)} selectedId={data.parquet.partitioned} diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.scss b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.scss index f75800957..7a23bfa27 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.scss +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.scss @@ -82,7 +82,7 @@ .partition-fields { width: 100%; - margin-bottom: 10px; + margin: 10px 0; &__checkbox-container { display: flex; diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.util.js b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.util.js index 83e2c9fa3..2464a53b9 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.util.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.util.js @@ -218,3 +218,19 @@ export const handlePathChange = ( })) } } + +export const isParquetPathValid = (validation, parquet) => { + return ( + !validation || + Boolean(parquet.partitioned && /\.\w*\s*$/.test(parquet.path)) || + Boolean(!parquet.partitioned && !/\.parquet\s*$|\.pq\s*$/.test(parquet.path)) + ) +} + +export const getInvalidParquetPathMessage = parquet => { + return parquet.partitioned && /\.\w*\s*$/.test(parquet.path) + ? 'The partitioned Parquet target for storey engine must be a directory. (The directory name must not end in .parquet/.pq.)' + : !parquet.partitioned && !/\.parquet\s*$|\.pq\s*$/.test(parquet.path) + ? 'The Parquet target for storey engine file path must have a .parquet/.pq suffix.' + : 'This field is invalid.' +} From 4886625552cb31e0731902452ec700f72d7ce961 Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:34:29 +0300 Subject: [PATCH 03/11] Fix [Project settings] "Something went wrong" during project creation `1.3.x` (#1685) --- src/reducers/projectReducer.js | 54 +--------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/src/reducers/projectReducer.js b/src/reducers/projectReducer.js index 29f05bc88..62e73a127 100644 --- a/src/reducers/projectReducer.js +++ b/src/reducers/projectReducer.js @@ -251,59 +251,7 @@ const projectReducer = (state = initialState, { type, payload }) => { error: null, loading: false, project: { - data: null, - error: null, - loading: false, - dataSets: { - data: null, - error: null, - loading: false - }, - failedJobs: { - data: [], - error: null, - loading: false - }, - featureSets: { - data: null, - error: null, - loading: false - }, - files: { - data: null, - error: null, - loading: false - }, - jobs: { - data: null, - error: null, - loading: false - }, - functions: { - data: null, - error: null, - loading: false - }, - models: { - data: [], - error: null, - loading: false - }, - runningJobs: { - data: [], - error: null, - loading: false - }, - scheduledJobs: { - data: [], - error: null, - loading: false - }, - workflows: { - data: [], - error: null, - loading: false - } + ...initialState.project } } case FETCH_PROJECT_BEGIN: From f7df6dc46a2c25aa485031d444e180e1d7e53352 Mon Sep 17 00:00:00 2001 From: Ilank <63646693+ilan7empest@users.noreply.github.com> Date: Thu, 30 Mar 2023 10:03:22 +0300 Subject: [PATCH 04/11] Impl [Models] Adjust the Average Latency calculation in model endpoints tab `1.3.x` (#1690) --- src/utils/createArtifactsContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/createArtifactsContent.js b/src/utils/createArtifactsContent.js index 03d1ebfa1..e3741d597 100644 --- a/src/utils/createArtifactsContent.js +++ b/src/utils/createArtifactsContent.js @@ -374,7 +374,7 @@ export const createModelEndpointsRowData = (artifact, project) => { ? `store://functions/${artifact.spec.function_uri}` : '' const { key: functionName } = parseUri(functionUri) - const averageLatency = artifact.status?.metrics?.latency_avg_1h?.values?.[0]?.[1] + const averageLatency = artifact.status?.metrics?.real_time?.latency_avg_1h?.[0]?.[1] return { data: { From 85f5a0ddfdd197ef34dc6168d5144e4744597689 Mon Sep 17 00:00:00 2001 From: Tom Tankilevitch <59158507+Tankilevitch@users.noreply.github.com> Date: Mon, 10 Apr 2023 23:34:47 +0300 Subject: [PATCH 05/11] uncomment side branches from build flow (#1694) (#1695) --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1a44d4868..b8b7ded00 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,7 +18,7 @@ on: push: branches: - development -# - '[0-9]+.[0-9]+.x' + - '[0-9]+.[0-9]+.x' workflow_dispatch: inputs: From ec9c8536318d4d0b66ad11c51ef4ca1805e2a9fe Mon Sep 17 00:00:00 2001 From: mariana-furyk <58301139+mariana-furyk@users.noreply.github.com> Date: Mon, 24 Apr 2023 10:08:35 +0300 Subject: [PATCH 06/11] Fix [Jobs] [Feature sets] UI should get "store://datasets/" format as an input to the function `1.3.x` (#1698) --- src/common/TargetPath/TargetPath.js | 10 +- src/common/TargetPath/targetPath.util.js | 6 +- .../FeatureSetsPanelDataSourceView.js | 41 +++------ .../featureSetsPanelDataSource.util.js | 4 +- .../JobsPanelDataInputs.js | 19 +++- .../jobsPanelDataInputs.util.js | 92 +++++-------------- .../EditableDataInputsRow.js | 44 +++------ .../JobsPanelDataInputsTable.js | 33 +++---- src/utils/panelPathScheme.js | 17 ++++ 9 files changed, 106 insertions(+), 160 deletions(-) diff --git a/src/common/TargetPath/TargetPath.js b/src/common/TargetPath/TargetPath.js index cce71de7d..797e9b4d6 100644 --- a/src/common/TargetPath/TargetPath.js +++ b/src/common/TargetPath/TargetPath.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 React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useMemo } from 'react' import { useParams } from 'react-router-dom' import { useDispatch } from 'react-redux' import { get, isNil, uniqBy } from 'lodash' @@ -252,6 +252,10 @@ const TargetPath = ({ setDataInputState ]) + const generatedPathTips = useMemo(() => { + return pathTips(dataInputState.storePathType) + }, [dataInputState.storePathType]) + return ( <> { +export const pathTips = projectItem => { const pathType = - storePathType === 'feature-vectors' + projectItem === 'feature-vectors' ? 'feature-vector' - : storePathType === 'artifacts' + : projectItem === 'artifacts' ? 'artifact' : 'dataset' diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSourceView.js b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSourceView.js index ad7349319..e37286098 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSourceView.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/FeatureSetsPanelDataSourceView.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 React from 'react' +import React, { useMemo } from 'react' import PropTypes from 'prop-types' import cronstrue from 'cronstrue' @@ -29,15 +29,10 @@ import ScheduleFeatureSet from '../ScheduleFeatureSet/ScheduleFeatureSet' import Select from '../../../common/Select/Select' import { Button } from 'igz-controls/components' -import { - comboboxSelectList, - CSV, - kindOptions, - PARQUET -} from './featureSetsPanelDataSource.util' +import { comboboxSelectList, CSV, kindOptions, PARQUET } from './featureSetsPanelDataSource.util' import { MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../../constants' import { SECONDARY_BUTTON } from 'igz-controls/constants' -import { pathPlaceholders } from '../../../utils/panelPathScheme' +import { pathTips } from '../../../utils/panelPathScheme' import { ReactComponent as Pencil } from 'igz-controls/images/edit.svg' @@ -61,6 +56,10 @@ const FeatureSetsPanelDataSourceView = ({ urlProjectItemTypeEntered, validation }) => { + const generatedPathTips = useMemo(() => { + return pathTips(data.url.projectItemType) + }, [data.url.projectItemType]) + return (
@@ -78,18 +77,14 @@ const FeatureSetsPanelDataSourceView = ({ comboboxClassName="url" hideSearchInput={!urlProjectItemTypeEntered} inputDefaultValue={ - data.url.pathType === MLRUN_STORAGE_INPUT_PATH_SCHEME - ? data.url.projectItemType - : '' + data.url.pathType === MLRUN_STORAGE_INPUT_PATH_SCHEME ? data.url.projectItemType : '' } inputOnChange={path => { handleUrlPathChange(path) }} inputPlaceholder={data.url.placeholder} invalid={!validation.isUrlValid} - invalidText={`Field must be in "${ - pathPlaceholders[data.url.pathType] - }" format`} + invalidText={`Field must be in "${generatedPathTips[data.url.pathType]}" format`} matches={comboboxMatches} maxSuggestedMatches={3} onBlur={handleUrlOnBlur} @@ -109,9 +104,7 @@ const FeatureSetsPanelDataSourceView = ({ className="schedule-tumbler" label={ <> - {data.schedule - ? cronstrue.toString(data.schedule) - : 'Schedule'} + {data.schedule ? cronstrue.toString(data.schedule) : 'Schedule'} } @@ -136,10 +129,7 @@ const FeatureSetsPanelDataSourceView = ({ invalid={!validation.isParseDatesValid} label="Parse Dates" onBlur={event => { - if ( - featureStore.newFeatureSet.spec.source.parse_dates !== - event.target.value - ) { + if (featureStore.newFeatureSet.spec.source.parse_dates !== event.target.value) { setNewFeatureSetDataSourceParseDates(event.target.value) } }} @@ -150,17 +140,12 @@ const FeatureSetsPanelDataSourceView = ({ })) } placeholder="col_name1,col_name2,..." - setInvalid={value => - setValidation(state => ({ ...state, isParseDatesValid: value })) - } + setInvalid={value => setValidation(state => ({ ...state, isParseDatesValid: value }))} type="text" /> )} {data.kind === PARQUET && ( - + )}
diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js index abfe478fb..1b77cde8f 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelDataSource/featureSetsPanelDataSource.util.js @@ -113,8 +113,8 @@ export const projectItemsPathTypes = [ export const isUrlInputValid = (pathInputType, pathInputValue, dataSourceKind) => { const regExp = dataSourceKind === CSV - ? /^artifacts\/(.+?)\/(.+?)(#(.+?))?(:(.+?))?(@(.+))?(? 0 && /.*?\/(.*?)/.test(pathInputValue) switch (pathInputType) { diff --git a/src/components/JobsPanelDataInputs/JobsPanelDataInputs.js b/src/components/JobsPanelDataInputs/JobsPanelDataInputs.js index 00dbe32c2..71c7dee0b 100644 --- a/src/components/JobsPanelDataInputs/JobsPanelDataInputs.js +++ b/src/components/JobsPanelDataInputs/JobsPanelDataInputs.js @@ -42,7 +42,7 @@ import { } from './jobsPanelDataInputs.util' import featureStoreActions from '../../actions/featureStore' import { isEveryObjectValueEmpty } from '../../utils/isEveryObjectValueEmpty' -import { MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../constants' +import { ARTIFACT_OTHER_TYPE, DATASET_TYPE, MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../constants' import { getFeatureReference, getParsedResource } from '../../utils/resources' import { generateArtifactsList, @@ -112,10 +112,21 @@ const JobsPanelDataInputs = ({ useEffect(() => { const storePathType = getInputValue('storePathType') const projectName = getInputValue('project') + const projectItem = getInputValue('projectItem') if (inputsState.inputProjectPathEntered && storePathType && projectName) { - if (storePathType === 'artifacts' && inputsState.artifacts.length === 0) { - dispatch(fetchArtifacts({ project: projectName })) + if (storePathType !== 'feature-vectors' && inputsState.artifacts.length === 0) { + dispatch( + fetchArtifacts({ + project: projectName, + filters: null, + config: { + params: { + category: projectItem === 'artifacts' ? ARTIFACT_OTHER_TYPE : DATASET_TYPE + } + } + }) + ) .unwrap() .then(artifacts => { inputsDispatch({ @@ -156,7 +167,7 @@ const JobsPanelDataInputs = ({ const projectItem = getInputValue('projectItem') if (inputsState.inputProjectItemPathEntered && storePathType && projectName && projectItem) { - if (storePathType === 'artifacts' && inputsState.artifactsReferences.length === 0) { + if (storePathType !== 'feature-vectors' && inputsState.artifactsReferences.length === 0) { dispatch(fetchArtifact({ project: projectName, artifact: projectItem })) .unwrap() .then(artifacts => { diff --git a/src/components/JobsPanelDataInputs/jobsPanelDataInputs.util.js b/src/components/JobsPanelDataInputs/jobsPanelDataInputs.util.js index 3203a6692..33f44be0c 100644 --- a/src/components/JobsPanelDataInputs/jobsPanelDataInputs.util.js +++ b/src/components/JobsPanelDataInputs/jobsPanelDataInputs.util.js @@ -50,9 +50,7 @@ export const generateComboboxMatchesList = ( selectedDataInputPath ) => { if (!inputStorePathTypeEntered) { - return storePathTypes.some(type => - type.id.startsWith(newInput.path.storePathType) - ) + return storePathTypes.some(type => type.id.startsWith(newInput.path.storePathType)) ? storePathTypes : [] } else if ( @@ -71,32 +69,24 @@ export const generateComboboxMatchesList = ( } else if (!inputProjectItemPathEntered) { const selectedStorePathType = newInput.path.storePathType || selectedDataInputPath.value.split('/')[0] - const projectItems = - selectedStorePathType === 'artifacts' - ? artifacts - : selectedStorePathType === 'feature-vectors' - ? featureVectors - : null + const projectItems = selectedStorePathType === 'feature-vectors' ? featureVectors : artifacts return projectItems ? projectItems.filter(projectItem => { return isEveryObjectValueEmpty(selectedDataInputPath) ? projectItem.id.startsWith(newInput.path.projectItem) - : projectItem.id.startsWith( - selectedDataInputPath.value.split('/')[2] - ) + : projectItem.id.startsWith(selectedDataInputPath.value.split('/')[2]) }) : [] } else if (!inputProjectItemReferencePathEntered) { const selectedStorePathType = newInput.path.storePathType || selectedDataInputPath.value.split('/')[0] const projectItemsReferences = - selectedStorePathType === 'artifacts' + selectedStorePathType !== 'feature-vectors' ? artifactsReferences : selectedStorePathType === 'feature-vectors' ? featureVectorsReferences : null - return projectItemsReferences ? projectItemsReferences.filter(projectItem => { return isEveryObjectValueEmpty(selectedDataInputPath) @@ -125,8 +115,7 @@ export const handleAddItem = ( newInputUrlPath, setDataInputsValidations ) => { - const isMlRunStorePath = - newItemObj.path.pathType === MLRUN_STORAGE_INPUT_PATH_SCHEME + const isMlRunStorePath = newItemObj.path.pathType === MLRUN_STORAGE_INPUT_PATH_SCHEME let mlRunStorePath = '' if (isMlRunStorePath) { @@ -145,9 +134,7 @@ export const handleAddItem = ( if (newItemObj.name.length === 0 || !pathInputIsValid) { setDataInputsValidations({ - isNameValid: - !isNameNotUnique(newItemObj.name, dataInputs) && - newItemObj.name.length > 0, + isNameValid: !isNameNotUnique(newItemObj.name, dataInputs) && newItemObj.name.length > 0, isPathValid: pathInputIsValid }) } else { @@ -208,15 +195,9 @@ export const handleEdit = ( if (selectedItem.newDataInputName) { delete currentDataObj[selectedItem.name] - currentDataObj[selectedItem.newDataInputName] = joinDataOfArrayOrObject( - selectedItem.path, - '' - ) + currentDataObj[selectedItem.newDataInputName] = joinDataOfArrayOrObject(selectedItem.path, '') } else { - currentDataObj[selectedItem.name] = joinDataOfArrayOrObject( - selectedItem.path, - '' - ) + currentDataObj[selectedItem.name] = joinDataOfArrayOrObject(selectedItem.path, '') } setCurrentPanelData({ ...currentDataObj }) @@ -242,10 +223,7 @@ export const handleEdit = ( }) } -export const resetDataInputsData = ( - inputsDispatch, - setDataInputsValidations -) => { +export const resetDataInputsData = (inputsDispatch, setDataInputsValidations) => { inputsDispatch({ type: inputsActions.REMOVE_NEW_INPUT_DATA }) @@ -287,15 +265,11 @@ export const handleDelete = ( setCurrentPanelData({ ...newInputs }) panelDispatch({ type: setPreviousPanelData, - payload: previousPanelData.filter( - dataItem => dataItem.data.name !== selectedItem.data.name - ) + payload: previousPanelData.filter(dataItem => dataItem.data.name !== selectedItem.data.name) }) panelDispatch({ type: setCurrentTableData, - payload: currentTableData.filter( - dataItem => dataItem.data.name !== selectedItem.data.name - ) + payload: currentTableData.filter(dataItem => dataItem.data.name !== selectedItem.data.name) }) } @@ -342,6 +316,10 @@ export const storePathTypes = [ label: 'Artifacts', id: 'artifacts' }, + { + label: 'Datasets', + id: 'datasets' + }, { label: 'Feature vectors', id: 'feature-vectors' @@ -389,10 +367,7 @@ export const handleInputPathTypeChange = ( export const handleInputPathChange = (inputsDispatch, inputsState, path) => { if (inputsState.newInput.path.pathType === MLRUN_STORAGE_INPUT_PATH_SCHEME) { - if ( - path.length === 0 && - inputsState.newInputDefaultPathProject.length > 0 - ) { + if (path.length === 0 && inputsState.newInputDefaultPathProject.length > 0) { inputsDispatch({ type: inputsActions.SET_NEW_INPUT_DEFAULT_PATH_PROJECT, payload: '' @@ -408,12 +383,7 @@ export const handleInputPathChange = (inputsDispatch, inputsState, path) => { } } -export const handleStoreInputPathChange = ( - isNewInput, - inputsDispatch, - inputsState, - path -) => { +export const handleStoreInputPathChange = (isNewInput, inputsDispatch, inputsState, path) => { const pathItems = path.split('/') const [projectItem, projectItemReference] = getParsedResource(pathItems[2]) @@ -457,29 +427,22 @@ export const handleStoreInputPathChange = ( storePathType: pathItems[0] ?? inputsState.newInput.path.storePathType, project: pathItems[1] ?? inputsState.newInput.path.project, projectItem: projectItem ?? inputsState.newInput.path.projectItem, - projectItemReference: - projectItemReference ?? inputsState.newInput.path.projectItemReference + projectItemReference: projectItemReference ?? inputsState.newInput.path.projectItemReference } }) } const projectItems = - inputsState[pathItems[0] === 'artifacts' ? 'artifacts' : 'featureVectors'] - const projectItemIsEntered = projectItems.find( - project => project.id === projectItem - ) + inputsState[pathItems[0] !== 'feature-vectors' ? 'artifacts' : 'featureVectors'] + const projectItemIsEntered = projectItems.find(project => project.id === projectItem) const projectItemsReferences = inputsState[ - pathItems[0] === 'artifacts' - ? 'artifactsReferences' - : 'featureVectorsReferences' + pathItems[0] !== 'feature-vectors' ? 'artifactsReferences' : 'featureVectorsReferences' ] const projectItemReferenceIsEntered = projectItemsReferences.find( projectItemRef => projectItemRef.id === projectItemReference ) - const isInputStorePathTypeValid = storePathTypes.some(type => - type.id.startsWith(pathItems[0]) - ) + const isInputStorePathTypeValid = storePathTypes.some(type => type.id.startsWith(pathItems[0])) inputsDispatch({ type: inputsActions.SET_INPUT_STORE_PATH_TYPE_ENTERED, @@ -505,7 +468,7 @@ export const isPathInputValid = (pathInputType, pathInputValue) => { case MLRUN_STORAGE_INPUT_PATH_SCHEME: return ( valueIsNotEmpty && - /^(artifacts|feature-vectors)\/(.+?)\/(.+?)(#(.+?))?(:(.+?))?(@(.+))?$/.test( + /^(artifacts|datasets|feature-vectors)\/(.+?)\/(.+?)(#(.+?))?(:(.+?))?(@(.+))?$/.test( pathInputValue ) ) @@ -516,12 +479,3 @@ export const isPathInputValid = (pathInputType, pathInputValue) => { return valueIsNotEmpty } } - -export const pathTips = { - [MLRUN_STORAGE_INPUT_PATH_SCHEME]: - 'artifacts/my-project/my-artifact:my-tag" or "artifacts/my-project/my-artifact@my-uid', - [S3_INPUT_PATH_SCHEME]: 'bucket/path', - [GOOGLE_STORAGE_INPUT_PATH_SCHEME]: 'bucket/path', - [AZURE_STORAGE_INPUT_PATH_SCHEME]: 'container/path', - [V3IO_INPUT_PATH_SCHEME]: 'container-name/file' -} diff --git a/src/elements/EditableDataInputsRow/EditableDataInputsRow.js b/src/elements/EditableDataInputsRow/EditableDataInputsRow.js index e861f4d7a..55cddbc0b 100644 --- a/src/elements/EditableDataInputsRow/EditableDataInputsRow.js +++ b/src/elements/EditableDataInputsRow/EditableDataInputsRow.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 React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' @@ -25,18 +25,12 @@ import Combobox from '../../common/Combobox/Combobox' import Input from '../../common/Input/Input' import { Tooltip, TextTooltipTemplate } from 'igz-controls/components' -import { - comboboxSelectList, - pathTips -} from '../../components/JobsPanelDataInputs/jobsPanelDataInputs.util' +import { comboboxSelectList } from '../../components/JobsPanelDataInputs/jobsPanelDataInputs.util' import { inputsActions } from '../../components/JobsPanelDataInputs/jobsPanelDataInputsReducer' import { MLRUN_STORAGE_INPUT_PATH_SCHEME } from '../../constants' -import { - applyEditButtonHandler, - handleEditInputPath -} from './EditableDataInputsRow.utils' +import { applyEditButtonHandler, handleEditInputPath } from './EditableDataInputsRow.utils' import { isNameNotUnique } from '../../components/JobsPanel/jobsPanel.util' -import { pathPlaceholders } from '../../utils/panelPathScheme' +import { pathPlaceholders, pathTips } from '../../utils/panelPathScheme' import { ReactComponent as Checkmark } from 'igz-controls/images/checkmark.svg' @@ -64,24 +58,15 @@ const EditableDataInputsRow = ({ }) const tableRowRef = useRef(null) - const comboboxClassNames = classNames( - 'input-row__item', - requiredField.path && 'required' - ) - const inputNameClassNames = classNames( - 'input', - requiredField.name && 'input_required' - ) + const comboboxClassNames = classNames('input-row__item', requiredField.path && 'required') + const inputNameClassNames = classNames('input', requiredField.name && 'input_required') useEffect(() => { if (selectDefaultValue.label?.length === 0) { setSelectDefaultValue({ id: selectedDataInput.data.path.pathType, label: selectedDataInput.data.path.pathType, - className: `path-type-${selectedDataInput.data.path.pathType?.replace( - /:\/\/.*$/g, - '' - )}` + className: `path-type-${selectedDataInput.data.path.pathType?.replace(/:\/\/.*$/g, '')}` }) } }, [selectDefaultValue.label, selectedDataInput.data.path.pathType]) @@ -143,6 +128,10 @@ const EditableDataInputsRow = ({ }) } + const generatedPathTips = useMemo(() => { + return pathTips(selectedDataInput.data.path.projectItem) + }, [selectedDataInput.data.path.projectItem]) + return (
{selectedDataInput.isDefault ? ( @@ -155,8 +144,7 @@ const EditableDataInputsRow = ({ className={inputNameClassNames} density="dense" invalid={ - inputName !== selectedDataInput.data.name && - isNameNotUnique(inputName, content) + inputName !== selectedDataInput.data.name && isNameNotUnique(inputName, content) } invalidText="Name already exists" onChange={name => { @@ -182,12 +170,11 @@ const EditableDataInputsRow = ({ inputPlaceholder={inputsState.pathPlaceholder} invalid={!isPathValid} invalidText={`Field must be in "${ - pathTips[selectedDataInput.data.path.pathType] + generatedPathTips[selectedDataInput.data.path.pathType] }" format`} matches={comboboxMatchesList} maxSuggestedMatches={ - inputsState.selectedDataInput.data.path.pathType === - MLRUN_STORAGE_INPUT_PATH_SCHEME + inputsState.selectedDataInput.data.path.pathType === MLRUN_STORAGE_INPUT_PATH_SCHEME ? 3 : 2 } @@ -214,8 +201,7 @@ const EditableDataInputsRow = ({ - + +
) } else if (data.type === 'buttonDownload') { return (
- }> +
@@ -148,7 +149,7 @@ const TableCell = ({ } else if (data.type === BUTTON_COPY_URI_CELL_TYPE) { return (
- data.actionHandler(item)}> + data.actionHandler(item)}>
diff --git a/src/utils/createArtifactsContent.js b/src/utils/createArtifactsContent.js index e3741d597..17f6de1b2 100644 --- a/src/utils/createArtifactsContent.js +++ b/src/utils/createArtifactsContent.js @@ -43,21 +43,21 @@ import { ReactComponent as SeverityOk } from 'igz-controls/images/severity-ok.sv import { ReactComponent as SeverityWarning } from 'igz-controls/images/severity-warning.svg' import { ReactComponent as SeverityError } from 'igz-controls/images/severity-error.svg' -export const createArtifactsContent = (artifacts, page, pageTab, project) => { +export const createArtifactsContent = (artifacts, page, pageTab, project, frontendSpec) => { return (artifacts.filter(artifact => !artifact.link_iteration) ?? []).map(artifact => { if (page === ARTIFACTS_PAGE) { return createArtifactsRowData(artifact) } else if (page === MODELS_PAGE) { if (pageTab === MODELS_TAB) { - return createModelsRowData(artifact, project) + return createModelsRowData(artifact, project, frontendSpec) } else if (pageTab === MODEL_ENDPOINTS_TAB) { return createModelEndpointsRowData(artifact, project) } } else if (page === FILES_PAGE) { - return createFilesRowData(artifact, project) + return createFilesRowData(artifact, project, frontendSpec) } - return createDatasetsRowData(artifact, project) + return createDatasetsRowData(artifact, project, frontendSpec) }) } @@ -104,8 +104,11 @@ const createArtifactsRowData = artifact => { } } -export const createModelsRowData = (artifact, project, showExpandButton) => { +export const createModelsRowData = (artifact, project, frontendSpec, showExpandButton) => { const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}` + const isTargetPathValid = frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { + return artifact.target_path?.startsWith?.(prefix) + }) return { data: { @@ -215,14 +218,16 @@ export const createModelsRowData = (artifact, project, showExpandButton) => { headerId: 'popupt', value: '', class: 'table-cell-icon', - type: 'buttonPopout' + type: 'buttonPopout', + disabled: !isTargetPathValid }, { id: `buttonDownload.${artifact.ui.identifierUnique}`, headerId: 'download', value: '', class: 'table-cell-icon', - type: 'buttonDownload' + type: 'buttonDownload', + disabled: !isTargetPathValid }, { id: `buttonCopy.${artifact.ui.identifierUnique}`, @@ -236,8 +241,11 @@ export const createModelsRowData = (artifact, project, showExpandButton) => { } } -export const createFilesRowData = (artifact, project, showExpandButton) => { +export const createFilesRowData = (artifact, project, frontendSpec, showExpandButton) => { const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}` + const isTargetPathValid = frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { + return artifact.target_path?.startsWith?.(prefix) + }) return { data: { @@ -331,14 +339,16 @@ export const createFilesRowData = (artifact, project, showExpandButton) => { headerId: 'popout', value: '', class: 'table-cell-icon', - type: 'buttonPopout' + type: 'buttonPopout', + disabled: !isTargetPathValid }, { id: `buttonDownload.${artifact.ui.identifierUnique}`, headerId: 'download', value: '', class: 'table-cell-icon', - type: 'buttonDownload' + type: 'buttonDownload', + disabled: !isTargetPathValid }, { id: `buttonCopy.${artifact.ui.identifierUnique}`, @@ -477,8 +487,11 @@ export const createModelEndpointsRowData = (artifact, project) => { } } -export const createDatasetsRowData = (artifact, project, showExpandButton) => { +export const createDatasetsRowData = (artifact, project, frontendSpec, showExpandButton) => { const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}` + const isTargetPathValid = frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { + return artifact.target_path?.startsWith?.(prefix) + }) return { data: { @@ -565,14 +578,16 @@ export const createDatasetsRowData = (artifact, project, showExpandButton) => { headerId: 'popout', value: '', class: 'table-cell-icon', - type: 'buttonPopout' + type: 'buttonPopout', + disabled: !isTargetPathValid }, { id: `buttonDownload.${artifact.ui.identifierUnique}`, headerId: 'download', value: '', class: 'table-cell-icon', - type: 'buttonDownload' + type: 'buttonDownload', + disabled: !isTargetPathValid }, { id: `buttonCopy.${artifact.ui.identifierUnique}`, diff --git a/src/utils/generateTableContent.js b/src/utils/generateTableContent.js index 81f04c99c..c21913c9f 100644 --- a/src/utils/generateTableContent.js +++ b/src/utils/generateTableContent.js @@ -46,6 +46,7 @@ export const generateTableContent = ( page, isTablePanelOpen, params, + frontendSpec, isStagingMode, isSelectedItem ) => { @@ -59,7 +60,7 @@ export const generateTableContent = ( ? createFunctionsContent(group, isSelectedItem, params.pageTab, params.projectName, true) : page === FEATURE_STORE_PAGE ? createFeatureStoreContent(group, params.pageTab, params.projectName, isTablePanelOpen) - : createArtifactsContent(group, page, params.pageTab, params.projectName) + : createArtifactsContent(group, page, params.pageTab, params.projectName, frontendSpec) ) } else if (!isEmpty(content) && (groupFilter === GROUP_BY_NONE || !groupFilter)) { return page === CONSUMER_GROUP_PAGE @@ -70,7 +71,7 @@ export const generateTableContent = ( page === FILES_PAGE || page === DATASETS_PAGE || (page === MODELS_PAGE && params.pageTab !== REAL_TIME_PIPELINES_TAB) - ? createArtifactsContent(content, page, params.pageTab, params.projectName) + ? createArtifactsContent(content, page, params.pageTab, params.projectName, frontendSpec) : page === FEATURE_STORE_PAGE ? createFeatureStoreContent(content, params.pageTab, params.projectName, isTablePanelOpen) : createFunctionsContent(content, isSelectedItem, params) From 27b698feff2ad02f1c1297e6440b67d95e02463f Mon Sep 17 00:00:00 2001 From: Andrew Mavdryk Date: Sun, 7 May 2023 19:31:36 +0300 Subject: [PATCH 08/11] Fix [Projects] Projects screen may explode the system `1.3.x` (#1712) --- src/actions/projects.js | 8 ++- src/api/projects-api.js | 7 +-- src/common/ActionsMenu/ActionsMenu.js | 30 +++++---- src/components/ProjectsPage/Projects.js | 62 ++++++++++++++----- src/components/ProjectsPage/ProjectsView.js | 2 +- src/components/ProjectsPage/projectsData.js | 4 +- .../ActionMenuItem/ActionsMenuItem.js | 7 ++- 7 files changed, 76 insertions(+), 44 deletions(-) diff --git a/src/actions/projects.js b/src/actions/projects.js index 06142cc12..40c5fca17 100644 --- a/src/actions/projects.js +++ b/src/actions/projects.js @@ -179,6 +179,8 @@ const projectsAction = { .getProject(project) .then(response => { dispatch(projectsAction.fetchProjectSuccess(response?.data)) + + return response?.data }) .catch(error => { dispatch(projectsAction.fetchProjectFailure(error)) @@ -485,11 +487,11 @@ const projectsAction = { type: FETCH_PROJECT_SUMMARY_SUCCESS, payload: summary }), - fetchProjects: () => dispatch => { + fetchProjects: params => dispatch => { dispatch(projectsAction.fetchProjectsBegin()) return projectsApi - .getProjects() + .getProjects(params) .then(response => { dispatch(projectsAction.fetchProjectsSuccess(response.data.projects)) @@ -508,7 +510,7 @@ const projectsAction = { dispatch(projectsAction.fetchProjectsNamesBegin()) return projectsApi - .getProjectsNames() + .getProjects({ format: 'name_only' }) .then(({ data: { projects } }) => { dispatch(projectsAction.fetchProjectsNamesSuccess(projects)) diff --git a/src/api/projects-api.js b/src/api/projects-api.js index b690e574e..557836c2f 100644 --- a/src/api/projects-api.js +++ b/src/api/projects-api.js @@ -70,12 +70,9 @@ const projectsApi = { getProjectScheduledJobs: project => mainHttpClient.get(`/projects/${project}/schedules`), getProjectSecrets: project => mainHttpClient.get(`/projects/${project}/secret-keys?provider=kubernetes`), - getProjects: () => mainHttpClient.get('/projects'), - getProjectsNames: () => + getProjects: params => mainHttpClient.get('/projects', { - params: { - format: 'name_only' - } + params }), getProjectSummaries: cancelToken => mainHttpClient.get('/project-summaries', { diff --git a/src/common/ActionsMenu/ActionsMenu.js b/src/common/ActionsMenu/ActionsMenu.js index c60b2d12e..acbd3d124 100644 --- a/src/common/ActionsMenu/ActionsMenu.js +++ b/src/common/ActionsMenu/ActionsMenu.js @@ -44,10 +44,7 @@ const ActionsMenu = ({ dataItem, menu, time }) => { 'actions-menu__container', isShowMenu && 'actions-menu__container-active' ) - const dropDownMenuClassNames = classnames( - 'actions-menu__body', - isShowMenu && 'show' - ) + const dropDownMenuClassNames = classnames('actions-menu__body', isShowMenu && 'show') let idTimeout = null const offset = 15 @@ -67,21 +64,18 @@ const ActionsMenu = ({ dataItem, menu, time }) => { const dropDownMenuRect = dropDownMenuRef.current.getBoundingClientRect() if ( - actionMenuRect.top + - actionMenuRect.height + - offset + - dropDownMenuRect.height >= + actionMenuRect.top + actionMenuRect.height + offset + dropDownMenuRect.height >= window.innerHeight ) { - dropDownMenuRef.current.style.top = `${actionMenuRect.top - - dropDownMenuRect.height}px` - dropDownMenuRef.current.style.left = `${actionMenuRect.left - - dropDownMenuRect.width + - offset}px` + dropDownMenuRef.current.style.top = `${actionMenuRect.top - dropDownMenuRect.height}px` + dropDownMenuRef.current.style.left = `${ + actionMenuRect.left - dropDownMenuRect.width + offset + }px` } else { dropDownMenuRef.current.style.top = `${actionMenuRect.bottom}px` - dropDownMenuRef.current.style.left = `${actionMenuRect.left - - (dropDownMenuRect.width - offset)}px` + dropDownMenuRef.current.style.left = `${ + actionMenuRect.left - (dropDownMenuRect.width - offset) + }px` } } @@ -128,7 +122,11 @@ const ActionsMenu = ({ dataItem, menu, time }) => {
setIsShowMenu(false)} + onClick={event => { + setIsShowMenu(false) + setRenderMenu(false) + event.stopPropagation() + }} ref={dropDownMenuRef} > {actionMenu.map( diff --git a/src/components/ProjectsPage/Projects.js b/src/components/ProjectsPage/Projects.js index 7e9b9b000..e116401ec 100644 --- a/src/components/ProjectsPage/Projects.js +++ b/src/components/ProjectsPage/Projects.js @@ -28,14 +28,14 @@ import ProjectsView from './ProjectsView' import { generateProjectActionsMenu, - successProjectDeletingMessage, + handleDeleteProjectError, projectsSortOptions, - handleDeleteProjectError + successProjectDeletingMessage } from './projectsData' import nuclioActions from '../../actions/nuclio' -import { setNotification } from '../../reducers/notificationReducer' import projectsAction from '../../actions/projects' import { DANGER_BUTTON, FORBIDDEN_ERROR_STATUS_CODE, PRIMARY_BUTTON } from 'igz-controls/constants' +import { setNotification } from '../../reducers/notificationReducer' import { useNuclioMode } from '../../hooks/nuclioMode.hook' @@ -44,6 +44,7 @@ const Projects = ({ createNewProject, deleteProject, fetchNuclioFunctions, + fetchProject, fetchProjects, fetchProjectsNames, fetchProjectsSummary, @@ -72,6 +73,10 @@ const Projects = ({ const { isNuclioModeDisabled } = useNuclioMode() + const fetchMinimalProjects = useCallback(() => { + fetchProjects({ format: 'minimal' }) + }, [fetchProjects]) + const isValidProjectState = useCallback( project => { return ( @@ -106,12 +111,12 @@ const Projects = ({ } removeProjects() - fetchProjects() + fetchMinimalProjects() fetchProjectsNames() fetchProjectsSummary(source.token) }, [ fetchNuclioFunctions, - fetchProjects, + fetchMinimalProjects, fetchProjectsNames, fetchProjectsSummary, isNuclioModeDisabled, @@ -135,7 +140,7 @@ const Projects = ({ project => { changeProjectState(project.metadata.name, 'archived') .then(() => { - fetchProjects() + fetchMinimalProjects() }) .catch(error => { dispatch( @@ -152,7 +157,7 @@ const Projects = ({ }) setConfirmData(null) }, - [changeProjectState, dispatch, fetchProjects] + [changeProjectState, dispatch, fetchMinimalProjects] ) const handleDeleteProject = useCallback( @@ -160,7 +165,7 @@ const Projects = ({ setConfirmData(null) deleteProject(project.metadata.name, deleteNonEmpty) .then(() => { - fetchProjects() + fetchMinimalProjects() dispatch( setNotification({ status: 200, @@ -180,16 +185,16 @@ const Projects = ({ ) }) }, - [deleteProject, dispatch, fetchProjects] + [deleteProject, dispatch, fetchMinimalProjects] ) const handleUnarchiveProject = useCallback( project => { changeProjectState(project.metadata.name, 'online').then(() => { - fetchProjects() + fetchMinimalProjects() }) }, - [changeProjectState, fetchProjects] + [changeProjectState, fetchMinimalProjects] ) const convertToYaml = useCallback( @@ -239,11 +244,37 @@ const Projects = ({ [handleDeleteProject] ) + const viewYaml = useCallback( + projectMinimal => { + if (projectMinimal?.metadata?.name) { + fetchProject(projectMinimal.metadata.name) + .then(project => { + convertToYaml(project) + }) + .catch(() => { + setConvertedYaml('') + + dispatch( + setNotification({ + status: 400, + id: Math.random(), + retry: () => viewYaml(projectMinimal), + message: "Failed to fetch project's YAML" + }) + ) + }) + } else { + setConvertedYaml('') + } + }, + [convertToYaml, dispatch, fetchProject] + ) + useEffect(() => { setActionsMenu( generateProjectActionsMenu( projectStore.projects, - convertToYaml, + viewYaml, onArchiveProject, handleUnarchiveProject, onDeleteProject @@ -254,7 +285,8 @@ const Projects = ({ onArchiveProject, onDeleteProject, handleUnarchiveProject, - projectStore.projects + projectStore.projects, + viewYaml ]) useEffect(() => { @@ -262,12 +294,12 @@ const Projects = ({ fetchNuclioFunctions() } - fetchProjects() + fetchMinimalProjects() fetchProjectsNames() fetchProjectsSummary(source.token) }, [ + fetchMinimalProjects, fetchNuclioFunctions, - fetchProjects, fetchProjectsNames, fetchProjectsSummary, isNuclioModeDisabled, diff --git a/src/components/ProjectsPage/ProjectsView.js b/src/components/ProjectsPage/ProjectsView.js index b157b5434..7d1afe337 100644 --- a/src/components/ProjectsPage/ProjectsView.js +++ b/src/components/ProjectsPage/ProjectsView.js @@ -78,7 +78,7 @@ const ProjectsView = ({ return (
- {projectStore.loading && } + {(projectStore.loading || projectStore.project.loading) && } {createProject && ( , - onClick: toggleConvertToYaml + onClick: viewYaml }, { label: 'Archive', diff --git a/src/elements/ActionMenuItem/ActionsMenuItem.js b/src/elements/ActionMenuItem/ActionsMenuItem.js index f51649957..a0b06bc74 100644 --- a/src/elements/ActionMenuItem/ActionsMenuItem.js +++ b/src/elements/ActionMenuItem/ActionsMenuItem.js @@ -44,8 +44,11 @@ const ActionsMenuItem = ({ dataItem, isIconDisplayed, menuItem }) => {
{ - event.stopPropagation() - !menuItem.disabled && menuItem.onClick(dataItem) + if (!menuItem.disabled) { + menuItem.onClick(dataItem) + } else { + event.stopPropagation() + } }} > {menuItem.label} From 93ff0046dcd7497f1e53711e4d4aaa5ae8062365 Mon Sep 17 00:00:00 2001 From: Ilank <63646693+ilan7empest@users.noreply.github.com> Date: Tue, 9 May 2023 12:35:59 +0300 Subject: [PATCH 09/11] Fix [Models] UI crash on attempt to open Models screen `1.3.x` (#1707) --- src/components/Table/table.scss | 2 +- src/utils/createArtifactsContent.js | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/Table/table.scss b/src/components/Table/table.scss index 80eaa8005..1e7eba9d1 100644 --- a/src/components/Table/table.scss +++ b/src/components/Table/table.scss @@ -325,7 +325,7 @@ &:first-child { position: sticky; top: 50px; - z-index: 1; + z-index: 3; background-color: $white; .table-body__cell { diff --git a/src/utils/createArtifactsContent.js b/src/utils/createArtifactsContent.js index 17f6de1b2..a6a33cb1a 100644 --- a/src/utils/createArtifactsContent.js +++ b/src/utils/createArtifactsContent.js @@ -104,11 +104,17 @@ const createArtifactsRowData = artifact => { } } +const getIter = artifact => (isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}`) +const getIsTargetPathValid = (artifact, frontendSpec) => + frontendSpec?.allowed_artifact_path_prefixes_list + ? frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { + return artifact.target_path?.startsWith?.(prefix) + }) + : false + export const createModelsRowData = (artifact, project, frontendSpec, showExpandButton) => { - const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}` - const isTargetPathValid = frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { - return artifact.target_path?.startsWith?.(prefix) - }) + const iter = getIter(artifact) + const isTargetPathValid = getIsTargetPathValid(artifact, frontendSpec) return { data: { @@ -242,10 +248,8 @@ export const createModelsRowData = (artifact, project, frontendSpec, showExpandB } export const createFilesRowData = (artifact, project, frontendSpec, showExpandButton) => { - const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}` - const isTargetPathValid = frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { - return artifact.target_path?.startsWith?.(prefix) - }) + const iter = getIter(artifact) + const isTargetPathValid = getIsTargetPathValid(artifact, frontendSpec) return { data: { @@ -488,10 +492,8 @@ export const createModelEndpointsRowData = (artifact, project) => { } export const createDatasetsRowData = (artifact, project, frontendSpec, showExpandButton) => { - const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}` - const isTargetPathValid = frontendSpec.allowed_artifact_path_prefixes_list.some(prefix => { - return artifact.target_path?.startsWith?.(prefix) - }) + const iter = getIter(artifact) + const isTargetPathValid = getIsTargetPathValid(artifact, frontendSpec) return { data: { From a03a756ce158f6ae643111f6862ccabb4fb3f186 Mon Sep 17 00:00:00 2001 From: mariana-furyk Date: Wed, 17 May 2023 11:33:38 +0300 Subject: [PATCH 10/11] Fix [Functions] nuclio function does shown the current function image --- src/constants.js | 1 + src/utils/createFunctionsContent.js | 13 +++++++++++-- src/utils/parseFunction.js | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/constants.js b/src/constants.js index 418f04e17..9e707bbec 100644 --- a/src/constants.js +++ b/src/constants.js @@ -182,6 +182,7 @@ export const FETCH_FUNCTION_TEMPLATE_SUCCESS = 'FETCH_FUNCTION_TEMPLATE_SUCCESS' export const FUNCTION_TYPE_JOB = 'job' export const FUNCTION_TYPE_LOCAL = 'local' export const FUNCTION_TYPE_SERVING = 'serving' +export const FUNCTION_TYPE_NUCLIO = 'nuclio' export const GET_FUNCTION_BEGIN = 'GET_FUNCTION_BEGIN' export const GET_FUNCTION_FAILURE = 'GET_FUNCTION_FAILURE' export const GET_FUNCTION_SUCCESS = 'GET_FUNCTION_SUCCESS' diff --git a/src/utils/createFunctionsContent.js b/src/utils/createFunctionsContent.js index e7eb8327e..5d876fa63 100644 --- a/src/utils/createFunctionsContent.js +++ b/src/utils/createFunctionsContent.js @@ -18,7 +18,13 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ import { formatDatetime } from './datetime' -import { FUNCTIONS_PAGE, MODELS_PAGE, REAL_TIME_PIPELINES_TAB } from '../constants' +import { + FUNCTION_TYPE_NUCLIO, + FUNCTION_TYPE_SERVING, + FUNCTIONS_PAGE, + MODELS_PAGE, + REAL_TIME_PIPELINES_TAB +} from '../constants' import { generateLinkToDetailsPanel } from './generateLinkToDetailsPanel' const createFunctionsContent = (functions, pageTab, projectName, showExpandButton) => @@ -140,7 +146,10 @@ const createFunctionsContent = (functions, pageTab, projectName, showExpandButto id: `image.${func.ui.identifierUnique}`, headerId: 'image', headerLabel: 'Image', - value: func.image, + value: + func.type === FUNCTION_TYPE_NUCLIO || func.type === FUNCTION_TYPE_SERVING + ? func.container_image + : func.image, class: 'table-cell-1' }, { diff --git a/src/utils/parseFunction.js b/src/utils/parseFunction.js index 53658daae..bb6774504 100644 --- a/src/utils/parseFunction.js +++ b/src/utils/parseFunction.js @@ -28,6 +28,7 @@ export const parseFunction = (func, projectName, customState) => { base_spec: func.spec?.base_spec ?? {}, build: func.spec?.build ?? {}, command: func.spec?.command, + container_image: func?.status?.container_image ?? '', default_class: func.spec?.default_class ?? '', default_handler: func.spec?.default_handler ?? '', description: func.spec?.description ?? '', @@ -51,7 +52,7 @@ export const parseFunction = (func, projectName, customState) => { type: func.kind, volume_mounts: func.spec?.volume_mounts ?? [], volumes: func.spec?.volumes ?? [], - updated: new Date(func.metadata?.updated ?? ''), + updated: new Date(func.metadata?.updated ?? '') } item.ui = { From 21869c19681d46cbeed73e26545691122f554010 Mon Sep 17 00:00:00 2001 From: mariana-furyk <58301139+mariana-furyk@users.noreply.github.com> Date: Thu, 25 May 2023 13:02:33 +0300 Subject: [PATCH 11/11] Fix [Functions] nuclio remote function doesn't show the current function image `1.3.x` (#1738) --- src/constants.js | 1 + src/utils/createFunctionsContent.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 9e707bbec..46eb6bc34 100644 --- a/src/constants.js +++ b/src/constants.js @@ -183,6 +183,7 @@ export const FUNCTION_TYPE_JOB = 'job' export const FUNCTION_TYPE_LOCAL = 'local' export const FUNCTION_TYPE_SERVING = 'serving' export const FUNCTION_TYPE_NUCLIO = 'nuclio' +export const FUNCTION_TYPE_REMOTE = 'remote' export const GET_FUNCTION_BEGIN = 'GET_FUNCTION_BEGIN' export const GET_FUNCTION_FAILURE = 'GET_FUNCTION_FAILURE' export const GET_FUNCTION_SUCCESS = 'GET_FUNCTION_SUCCESS' diff --git a/src/utils/createFunctionsContent.js b/src/utils/createFunctionsContent.js index 5d876fa63..0b4a0e622 100644 --- a/src/utils/createFunctionsContent.js +++ b/src/utils/createFunctionsContent.js @@ -20,6 +20,7 @@ such restriction. import { formatDatetime } from './datetime' import { FUNCTION_TYPE_NUCLIO, + FUNCTION_TYPE_REMOTE, FUNCTION_TYPE_SERVING, FUNCTIONS_PAGE, MODELS_PAGE, @@ -147,7 +148,9 @@ const createFunctionsContent = (functions, pageTab, projectName, showExpandButto headerId: 'image', headerLabel: 'Image', value: - func.type === FUNCTION_TYPE_NUCLIO || func.type === FUNCTION_TYPE_SERVING + func.type === FUNCTION_TYPE_NUCLIO || + func.type === FUNCTION_TYPE_SERVING || + func.type === FUNCTION_TYPE_REMOTE ? func.container_image : func.image, class: 'table-cell-1'