diff --git a/src/common/Download/Download.js b/src/common/Download/Download.js
index 9c7e70f2c..4cefa9da6 100644
--- a/src/common/Download/Download.js
+++ b/src/common/Download/Download.js
@@ -20,6 +20,7 @@ such restriction.
import React, { useState, useEffect, useCallback, useRef } from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
+import classnames from 'classnames'
import { useDispatch } from 'react-redux'
import { useParams } from 'react-router-dom'
@@ -34,10 +35,14 @@ import colors from 'igz-controls/scss/colors.scss'
const DEFAULT_FILE_NAME = 'mlrun-file'
-const Download = ({ fileName, path, user }) => {
+const Download = ({ disabled, fileName, path, user }) => {
const [progress, setProgress] = useState(0)
const [isDownload, setDownload] = useState(false)
const params = useParams()
+ const downloadContainerClassnames = classnames(
+ 'download-container',
+ disabled && 'download-container_disabled'
+ )
const downloadRef = useRef(null)
const dispatch = useDispatch()
@@ -149,14 +154,17 @@ const Download = ({ fileName, path, user }) => {
}, [downloadCallback, downloadRef])
const handleClick = () => {
- if (downloadRef.current?.cancel) {
- return downloadRef.current.cancel('cancel')
+ if (!disabled) {
+ if (downloadRef.current?.cancel) {
+ return downloadRef.current.cancel('cancel')
+ }
+
+ setDownload(!isDownload)
}
- setDownload(!isDownload)
}
return (
-
+
{
{!isDownload ? (
-
+
{
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 => {
@@ -238,6 +252,10 @@ const TargetPath = ({
setDataInputState
])
+ const generatedPathTips = useMemo(() => {
+ return pathTips(dataInputState.storePathType)
+ }, [dataInputState.storePathType])
+
return (
<>
{
- const pathType = storePathType === 'feature-vectors' ? 'feature-vector' : 'artifact'
+export const pathTips = projectItem => {
+ const pathType =
+ projectItem === 'feature-vectors'
+ ? 'feature-vector'
+ : projectItem === '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/Datasets/Datasets.js b/src/components/Datasets/Datasets.js
index 6557d3251..ae094c88b 100644
--- a/src/components/Datasets/Datasets.js
+++ b/src/components/Datasets/Datasets.js
@@ -74,6 +74,7 @@ const Datasets = () => {
const navigate = useNavigate()
const location = useLocation()
const dispatch = useDispatch()
+ const frontendSpec = useSelector(store => store.appStore.frontendSpec)
const detailsFormInitialValues = useMemo(
() => ({
@@ -185,10 +186,11 @@ const Datasets = () => {
setSelectedRowData,
filtersStore.iter,
filtersStore.tag,
- params.projectName
+ params.projectName,
+ frontendSpec
)
},
- [dispatch, filtersStore.iter, filtersStore.tag, params.projectName]
+ [dispatch, filtersStore.iter, filtersStore.tag, frontendSpec, params.projectName]
)
const handleRemoveRowData = useCallback(
@@ -219,10 +221,12 @@ const Datasets = () => {
const tableContent = useMemo(() => {
return filtersStore.groupBy === GROUP_BY_NAME
? latestItems.map(contentItem => {
- return createDatasetsRowData(contentItem, params.projectName, true)
+ return createDatasetsRowData(contentItem, params.projectName, frontendSpec, true)
})
- : datasets.map(contentItem => createDatasetsRowData(contentItem, params.projectName))
- }, [datasets, filtersStore.groupBy, latestItems, params.projectName])
+ : datasets.map(contentItem =>
+ createDatasetsRowData(contentItem, params.projectName, frontendSpec)
+ )
+ }, [datasets, filtersStore.groupBy, frontendSpec, latestItems, params.projectName])
useEffect(() => {
dispatch(removeDataSet({}))
diff --git a/src/components/Datasets/datasets.util.js b/src/components/Datasets/datasets.util.js
index 7888d532e..94ee25945 100644
--- a/src/components/Datasets/datasets.util.js
+++ b/src/components/Datasets/datasets.util.js
@@ -105,7 +105,8 @@ export const fetchDataSetRowData = async (
setSelectedRowData,
iter,
tag,
- projectName
+ projectName,
+ frontendSpec
) => {
const dataSetIdentifier = getArtifactIdentifier(dataSet)
@@ -124,7 +125,9 @@ export const fetchDataSetRowData = async (
return {
...state,
[dataSetIdentifier]: {
- content: result.map(artifact => createDatasetsRowData(artifact, projectName)),
+ content: result.map(artifact =>
+ createDatasetsRowData(artifact, projectName, frontendSpec)
+ ),
error: null,
loading: false
}
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/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 c55cff1e6..1b77cde8f 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)
+ ? /^artifacts|datasets\/(.+?)\/(.+?)(#(.+?))?(:(.+?))?(@(.+))?(? 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/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.'
+}
diff --git a/src/components/Files/Files.js b/src/components/Files/Files.js
index 5bbc2d132..1ccf04a10 100644
--- a/src/components/Files/Files.js
+++ b/src/components/Files/Files.js
@@ -74,6 +74,7 @@ const Files = () => {
const dispatch = useDispatch()
const filesRef = useRef(null)
const pageData = useMemo(() => generatePageData(selectedFile), [selectedFile])
+ const frontendSpec = useSelector(store => store.appStore.frontendSpec)
const detailsFormInitialValues = useMemo(
() => ({
@@ -172,10 +173,11 @@ const Files = () => {
dispatch,
params.projectName,
filtersStore.iter,
- filtersStore.tag
+ filtersStore.tag,
+ frontendSpec
)
},
- [dispatch, filtersStore.iter, filtersStore.tag, params.projectName]
+ [dispatch, filtersStore.iter, filtersStore.tag, frontendSpec, params.projectName]
)
const { latestItems, handleExpandRow } = useGroupContent(
@@ -190,10 +192,10 @@ const Files = () => {
const tableContent = useMemo(() => {
return filtersStore.groupBy === GROUP_BY_NAME
? latestItems.map(contentItem => {
- return createFilesRowData(contentItem, params.projectName, true)
+ return createFilesRowData(contentItem, params.projectName, frontendSpec, true)
})
- : files.map(contentItem => createFilesRowData(contentItem, params.projectName))
- }, [files, filtersStore.groupBy, latestItems, params.projectName])
+ : files.map(contentItem => createFilesRowData(contentItem, params.projectName, frontendSpec))
+ }, [files, filtersStore.groupBy, frontendSpec, latestItems, params.projectName])
const applyDetailsChanges = useCallback(
changes => {
diff --git a/src/components/Files/files.util.js b/src/components/Files/files.util.js
index c7c6bf5e9..b0254498a 100644
--- a/src/components/Files/files.util.js
+++ b/src/components/Files/files.util.js
@@ -100,7 +100,15 @@ export const filters = [
]
export const actionsMenuHeader = 'Register artifact'
-export const fetchFilesRowData = (file, setSelectedRowData, dispatch, projectName, iter, tag) => {
+export const fetchFilesRowData = (
+ file,
+ setSelectedRowData,
+ dispatch,
+ projectName,
+ iter,
+ tag,
+ frontendSpec
+) => {
const fileIdentifier = getArtifactIdentifier(file)
setSelectedRowData(state => ({
@@ -117,7 +125,9 @@ export const fetchFilesRowData = (file, setSelectedRowData, dispatch, projectNam
setSelectedRowData(state => ({
...state,
[fileIdentifier]: {
- content: result.map(artifact => createFilesRowData(artifact, projectName)),
+ content: result.map(artifact =>
+ createFilesRowData(artifact, projectName, frontendSpec)
+ ),
error: null,
loading: false
}
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/components/ModelsPage/Models/Models.js b/src/components/ModelsPage/Models/Models.js
index 195b40d89..a14326265 100644
--- a/src/components/ModelsPage/Models/Models.js
+++ b/src/components/ModelsPage/Models/Models.js
@@ -79,6 +79,7 @@ const Models = ({ fetchModelFeatureVector }) => {
const modelsRef = useRef(null)
const pageData = useMemo(() => generatePageData(selectedModel), [selectedModel])
const { fetchData, models, setModels, toggleConvertedYaml } = useModelsPage()
+ const frontendSpec = useSelector(store => store.appStore.frontendSpec)
const detailsFormInitialValues = useMemo(
() => ({
@@ -171,10 +172,11 @@ const Models = ({ fetchModelFeatureVector }) => {
setSelectedRowData,
filtersStore.iter,
filtersStore.tag,
- params.projectName
+ params.projectName,
+ frontendSpec
)
},
- [dispatch, filtersStore.iter, filtersStore.tag, params.projectName]
+ [dispatch, filtersStore.iter, filtersStore.tag, frontendSpec, params.projectName]
)
const applyDetailsChanges = useCallback(
@@ -224,10 +226,12 @@ const Models = ({ fetchModelFeatureVector }) => {
const tableContent = useMemo(() => {
return filtersStore.groupBy === GROUP_BY_NAME
? latestItems.map(contentItem => {
- return createModelsRowData(contentItem, params.projectName, true)
+ return createModelsRowData(contentItem, params.projectName, frontendSpec, true)
})
- : models.map(contentItem => createModelsRowData(contentItem, params.projectName))
- }, [filtersStore.groupBy, latestItems, models, params.projectName])
+ : models.map(contentItem =>
+ createModelsRowData(contentItem, params.projectName, frontendSpec)
+ )
+ }, [filtersStore.groupBy, frontendSpec, latestItems, models, params.projectName])
const { sortTable, selectedColumnName, getSortingIcon, sortedTableContent } = useSortTable({
headers: tableContent[0]?.content,
diff --git a/src/components/ModelsPage/Models/models.util.js b/src/components/ModelsPage/Models/models.util.js
index a3d89456f..b7c7a85a2 100644
--- a/src/components/ModelsPage/Models/models.util.js
+++ b/src/components/ModelsPage/Models/models.util.js
@@ -80,7 +80,8 @@ export const fetchModelsRowData = async (
setSelectedRowData,
iter,
tag,
- projectName
+ projectName,
+ frontendSpec
) => {
const modelIdentifier = getArtifactIdentifier(model)
@@ -99,7 +100,9 @@ export const fetchModelsRowData = async (
return {
...state,
[modelIdentifier]: {
- content: result.map(artifact => createModelsRowData(artifact, projectName)),
+ content: result.map(artifact =>
+ createModelsRowData(artifact, projectName, frontendSpec)
+ ),
error: null,
loading: false
}
diff --git a/src/components/Table/Table.js b/src/components/Table/Table.js
index fa37344a0..42ab1e35c 100644
--- a/src/components/Table/Table.js
+++ b/src/components/Table/Table.js
@@ -68,6 +68,7 @@ const Table = ({
const { isStagingMode } = useMode()
const params = useParams()
const tableStore = useSelector(store => store.tableStore)
+ const frontendSpec = useSelector(store => store.appStore.frontendSpec)
useEffect(() => {
const calculatePanelHeight = () => {
@@ -103,6 +104,7 @@ const Table = ({
pageData.page,
tableStore.isTablePanelOpen,
params,
+ frontendSpec,
isStagingMode,
!isEveryObjectValueEmpty(selectedItem)
)
@@ -131,7 +133,8 @@ const Table = ({
pageData.mainRowItemsCount,
pageData.page,
selectedItem,
- tableStore.isTablePanelOpen
+ tableStore.isTablePanelOpen,
+ frontendSpec
])
return (
diff --git a/src/components/Table/table.scss b/src/components/Table/table.scss
index 19c22cd24..1e7eba9d1 100644
--- a/src/components/Table/table.scss
+++ b/src/components/Table/table.scss
@@ -252,7 +252,14 @@
font-size: 15px;
background-color: transparent;
border: none;
- cursor: pointer;
+
+ &:disabled {
+ cursor: default;
+ }
+
+ &:not(:disabled) {
+ cursor: pointer;
+ }
}
.expand-arrow {
@@ -318,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/constants.js b/src/constants.js
index 418f04e17..46eb6bc34 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -182,6 +182,8 @@ 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 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/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 = ({
-
resetDataInputsData(inputsDispatch, setValidation)}
- >
+ resetDataInputsData(inputsDispatch, setValidation)}>
}>
diff --git a/src/elements/TableCell/TableCell.js b/src/elements/TableCell/TableCell.js
index 1c625eb02..b6c1b760e 100644
--- a/src/elements/TableCell/TableCell.js
+++ b/src/elements/TableCell/TableCell.js
@@ -118,7 +118,9 @@ const TableCell = ({
} else if (data.type === 'buttonPopout') {
return (
-
{
dispatch(
showArtifactsPreview({
@@ -128,19 +130,18 @@ const TableCell = ({
)
}}
>
- }>
-
-
-
+
+
)
} 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/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/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:
diff --git a/src/utils/createArtifactsContent.js b/src/utils/createArtifactsContent.js
index 03d1ebfa1..a6a33cb1a 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,17 @@ const createArtifactsRowData = artifact => {
}
}
-export const createModelsRowData = (artifact, project, showExpandButton) => {
- const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}`
+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 = getIter(artifact)
+ const isTargetPathValid = getIsTargetPathValid(artifact, frontendSpec)
return {
data: {
@@ -215,14 +224,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 +247,9 @@ export const createModelsRowData = (artifact, project, showExpandButton) => {
}
}
-export const createFilesRowData = (artifact, project, showExpandButton) => {
- const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}`
+export const createFilesRowData = (artifact, project, frontendSpec, showExpandButton) => {
+ const iter = getIter(artifact)
+ const isTargetPathValid = getIsTargetPathValid(artifact, frontendSpec)
return {
data: {
@@ -331,14 +343,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}`,
@@ -374,7 +388,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: {
@@ -477,8 +491,9 @@ export const createModelEndpointsRowData = (artifact, project) => {
}
}
-export const createDatasetsRowData = (artifact, project, showExpandButton) => {
- const iter = isNaN(parseInt(artifact?.iter)) ? '' : ` #${artifact?.iter}`
+export const createDatasetsRowData = (artifact, project, frontendSpec, showExpandButton) => {
+ const iter = getIter(artifact)
+ const isTargetPathValid = getIsTargetPathValid(artifact, frontendSpec)
return {
data: {
@@ -565,14 +580,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/createFunctionsContent.js b/src/utils/createFunctionsContent.js
index e7eb8327e..0b4a0e622 100644
--- a/src/utils/createFunctionsContent.js
+++ b/src/utils/createFunctionsContent.js
@@ -18,7 +18,14 @@ 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_REMOTE,
+ 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 +147,12 @@ 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.type === FUNCTION_TYPE_REMOTE
+ ? func.container_image
+ : func.image,
class: 'table-cell-1'
},
{
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)
diff --git a/src/utils/panelPathScheme.js b/src/utils/panelPathScheme.js
index 4c0a43b8c..f7f58bd90 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',
@@ -86,3 +89,20 @@ export const pathPlaceholders = {
[AZURE_STORAGE_INPUT_PATH_SCHEME]: 'container/path',
[V3IO_INPUT_PATH_SCHEME]: 'container-name/file'
}
+
+export const pathTips = projectItem => {
+ const pathType =
+ projectItem === 'feature-vectors'
+ ? 'feature-vector'
+ : projectItem === '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`,
+ [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/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 = {