From b17dae5f69f95dc3596f49b8e41aeb0be02010f6 Mon Sep 17 00:00:00 2001 From: mariana-furyk Date: Thu, 8 May 2025 11:48:59 +0300 Subject: [PATCH] Impl [LLM Prompt Artifact] Add a main table --- src/App.jsx | 30 +- src/api/artifacts-api.js | 97 ++- src/components/ActionBar/ActionBar.jsx | 2 +- src/components/Artifacts/Artifacts.jsx | 561 +++++++++++++++++ src/components/Artifacts/ArtifactsTable.jsx | 182 ++++++ src/components/Artifacts/ArtifactsView.jsx | 175 ++++++ src/components/Artifacts/artifacts.scss | 14 + .../artifacts.util.js} | 26 +- src/components/Datasets/Datasets.jsx | 446 +------------- src/components/Datasets/DatasetsView.jsx | 206 ------- src/components/Datasets/datasets.scss | 14 - src/components/Datasets/datasets.util.jsx | 28 +- src/components/Details/Details.jsx | 4 +- src/components/Documents/Documents.jsx | 442 +------------- src/components/Documents/DocumentsView.jsx | 192 ------ src/components/Documents/documents.scss | 14 - src/components/Documents/documents.util.jsx | 24 +- src/components/Files/Files.jsx | 442 +------------- src/components/Files/FilesView.jsx | 206 ------- src/components/Files/files.scss | 10 - src/components/Files/files.util.jsx | 15 - src/components/LLMPrompts/LLMPrompts.jsx | 63 ++ src/components/LLMPrompts/llmPrompts.util.jsx | 259 ++++++++ src/components/ModelsPage/Models/Models.jsx | 575 ++---------------- .../ModelsPage/Models/ModelsView.jsx | 217 ------- src/components/ModelsPage/Models/models.scss | 10 - .../ModelsPage/Models/models.util.jsx | 26 +- src/constants.js | 1 + src/reducers/artifactsReducer.js | 109 +++- src/utils/createArtifactsContent.jsx | 72 +++ src/utils/resources.js | 4 +- 31 files changed, 1697 insertions(+), 2769 deletions(-) create mode 100644 src/components/Artifacts/Artifacts.jsx create mode 100644 src/components/Artifacts/ArtifactsTable.jsx create mode 100644 src/components/Artifacts/ArtifactsView.jsx create mode 100644 src/components/Artifacts/artifacts.scss rename src/components/{LLMPrompts/LLMPrompts.js => Artifacts/artifacts.util.js} (56%) delete mode 100644 src/components/Datasets/DatasetsView.jsx delete mode 100644 src/components/Datasets/datasets.scss delete mode 100644 src/components/Documents/DocumentsView.jsx delete mode 100644 src/components/Documents/documents.scss delete mode 100644 src/components/Files/FilesView.jsx delete mode 100644 src/components/Files/files.scss create mode 100644 src/components/LLMPrompts/LLMPrompts.jsx create mode 100644 src/components/LLMPrompts/llmPrompts.util.jsx delete mode 100644 src/components/ModelsPage/Models/ModelsView.jsx delete mode 100644 src/components/ModelsPage/Models/models.scss diff --git a/src/App.jsx b/src/App.jsx index cb2cba4d1e..ceb613522b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -253,9 +253,9 @@ const App = () => { ))} {[ 'projects/:projectName/datasets', - 'projects/:projectName/datasets/:datasetName/:id/:tab', - `projects/:projectName/datasets/:datasetName/${ALL_VERSIONS_PATH}`, - `projects/:projectName/datasets/:datasetName/${ALL_VERSIONS_PATH}/:id/:tab` + 'projects/:projectName/datasets/:artifactName/:id/:tab', + `projects/:projectName/datasets/:artifactName/${ALL_VERSIONS_PATH}`, + `projects/:projectName/datasets/:artifactName/${ALL_VERSIONS_PATH}/:id/:tab` ].map((path, index) => ( } /> @@ -288,9 +288,9 @@ const App = () => { }> {[ `${MODELS_TAB}`, - `${MODELS_TAB}/:modelName/:id/:tab`, - `${MODELS_TAB}/:modelName/${ALL_VERSIONS_PATH}`, - `${MODELS_TAB}/:modelName/${ALL_VERSIONS_PATH}/:id/:tab` + `${MODELS_TAB}/:artifactName/:id/:tab`, + `${MODELS_TAB}/:artifactName/${ALL_VERSIONS_PATH}`, + `${MODELS_TAB}/:artifactName/${ALL_VERSIONS_PATH}/:id/:tab` ].map((path, index) => ( } /> @@ -315,9 +315,9 @@ const App = () => { {[ 'projects/:projectName/files', - 'projects/:projectName/files/:fileName/:id/:tab', - `projects/:projectName/files/:fileName/${ALL_VERSIONS_PATH}`, - `projects/:projectName/files/:fileName/${ALL_VERSIONS_PATH}/:id/:tab` + 'projects/:projectName/files/:artifactName/:id/:tab', + `projects/:projectName/files/:artifactName/${ALL_VERSIONS_PATH}`, + `projects/:projectName/files/:artifactName/${ALL_VERSIONS_PATH}/:id/:tab` ].map((path, index) => ( } /> @@ -330,9 +330,9 @@ const App = () => { ))} {[ 'projects/:projectName/documents', - 'projects/:projectName/documents/:documentName/:id/:tab', - `projects/:projectName/documents/:documentName/${ALL_VERSIONS_PATH}`, - `projects/:projectName/documents/:documentName/${ALL_VERSIONS_PATH}/:id/:tab` + 'projects/:projectName/documents/:artifactName/:id/:tab', + `projects/:projectName/documents/:artifactName/${ALL_VERSIONS_PATH}`, + `projects/:projectName/documents/:artifactName/${ALL_VERSIONS_PATH}/:id/:tab` ].map((path, index) => ( } /> @@ -340,9 +340,9 @@ const App = () => { ))} {[ 'projects/:projectName/llm-prompts', - 'projects/:projectName/llm-prompts/:promptName/:id/:tab', - `projects/:projectName/llm-prompts/:promptName/${ALL_VERSIONS_PATH}`, - `projects/:projectName/llm-prompts/:promptName/${ALL_VERSIONS_PATH}/:id/:tab` + 'projects/:projectName/llm-prompts/:artifactName/:id/:tab', + `projects/:projectName/llm-prompts/:artifactName/${ALL_VERSIONS_PATH}`, + `projects/:projectName/llm-prompts/:artifactName/${ALL_VERSIONS_PATH}/:id/:tab` ].map((path, index) => ( } /> diff --git a/src/api/artifacts-api.js b/src/api/artifacts-api.js index 708d1fec8f..1bdbd57048 100644 --- a/src/api/artifacts-api.js +++ b/src/api/artifacts-api.js @@ -23,6 +23,7 @@ import { ARTIFACT_OTHER_TYPE, DATASET_TYPE, DOCUMENT_TYPE, + // LLM_PROMPT_TYPE, MODEL_TYPE, SHOW_ITERATIONS, TAG_FILTER_ALL_ITEMS, @@ -70,7 +71,7 @@ const artifactsApi = { if (mlrunVersion) { headers['x-mlrun-client-version'] = mlrunVersion } - + return mainHttpClient.post( `/projects/${data.function.metadata.project}/nuclio/${data.function.metadata.name}/deploy`, data, @@ -185,6 +186,100 @@ const artifactsApi = { return fetchArtifacts(project, filters, newConfig, true) }, + getLLMPrompts: (project, filters, config = {}) => { + // const newConfig = { + // ...config, + // params: { ...config.params, category: LLM_PROMPT_TYPE } + // } + + // return fetchArtifacts(project, filters, newConfig, true) + return Promise.resolve({ + data: { + artifacts: [ + { + kind: 'dataset', + metadata: { + key: 'test3', + project: 'default', + tree: '3c9a5fe2-1ffc-4c4c-864b-a712a8899fe0', + description: '', + iter: null, + uid: 'c4dc3113a581a11ed69ef09258fdc0584d590f74', + updated: '2025-05-06 08:50:01.714000+00:00', + created: '2025-05-06 08:50:01.714000+00:00', + tag: 'latest' + }, + status: {}, + project: 'default', + spec: { + producer: { + kind: 'api', + name: 'UI', + uri: 'dashboard.default-tenant.app.vmdev63.lab.iguazeng.com' + }, + db_key: 'test3', + target_path: 'v3io:///asd/asd' + } + }, + { + kind: 'dataset', + metadata: { + key: 'test2', + project: 'default', + tree: '31fc16e0-ce4d-4076-baf3-53e29553bf44', + description: '', + iter: null, + uid: '28ac191a940dcada5f2c09117416db06c21f0b4a', + updated: '2025-05-06 08:49:15.869000+00:00', + created: '2025-05-06 08:49:15.869000+00:00', + tag: 'latest' + }, + status: {}, + project: 'default', + spec: { + producer: { + kind: 'api', + name: 'UI', + uri: 'dashboard.default-tenant.app.vmdev63.lab.iguazeng.com' + }, + db_key: 'test2', + target_path: 'v3io:///hj/b' + } + }, + { + kind: 'dataset', + metadata: { + key: 'test', + project: 'default', + tree: '10b8405b-366b-428f-a84a-ac1e3caf52e1', + description: '', + iter: null, + uid: '7ef62fca9189261fa2443bd3fd0ebef5f03f182d', + updated: '2025-04-24 09:53:08.584000+00:00', + created: '2025-04-24 09:53:08.584000+00:00', + tag: 'latest' + }, + status: {}, + project: 'default', + spec: { + producer: { + kind: 'api', + name: 'UI', + uri: 'localhost:3000' + }, + db_key: 'test', + target_path: 's3://dfg/fg' + } + } + ], + pagination: { + page: 1, + 'page-size': 1000, + 'page-token': null + } + } + }) + }, getModels: (project, filters, config = {}) => { const newConfig = { ...config, diff --git a/src/components/ActionBar/ActionBar.jsx b/src/components/ActionBar/ActionBar.jsx index e4bbaf6a3b..eb8c909743 100644 --- a/src/components/ActionBar/ActionBar.jsx +++ b/src/components/ActionBar/ActionBar.jsx @@ -270,7 +270,7 @@ const ActionBar = ({ const actionCanBePerformed = await performDetailsActionHelper(changes, dispatch) if (actionCanBePerformed) { - handler() + handler(params, handleRefresh, filters) } } diff --git a/src/components/Artifacts/Artifacts.jsx b/src/components/Artifacts/Artifacts.jsx new file mode 100644 index 0000000000..014fac1a14 --- /dev/null +++ b/src/components/Artifacts/Artifacts.jsx @@ -0,0 +1,561 @@ +/* +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. +*/ + +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useLocation, useNavigate, useParams } from 'react-router-dom' +import PropTypes from 'prop-types' + +import ArtifactsView from './ArtifactsView' + +import { getViewMode } from '../../utils/helper' +import { getSavedSearchParams, transformSearchParams } from '../../utils/filter.util' +import { getFiltersConfig } from './artifacts.util' +import { useFiltersFromSearchParams } from '../../hooks/useFiltersFromSearchParams.hook' +import { useRefreshAfterDelete } from '../../hooks/useRefreshAfterDelete.hook' +import { getCloseDetailsLink, isDetailsTabExists } from '../../utils/link-helper.util' +import { openPopUp } from 'igz-controls/utils/common.util' +import { + ALL_VERSIONS_PATH, + BE_PAGE, + BE_PAGE_SIZE, + FUNCTION_TYPE_SERVING, + GROUP_BY_NONE, + ITERATIONS_FILTER, + REQUEST_CANCELED, + SHOW_ITERATIONS, + TAG_FILTER, + TAG_FILTER_ALL_ITEMS +} from '../../constants' +import { toggleYaml } from '../../reducers/appReducer' +import { chain, isEmpty, isNil } from 'lodash' +import { fetchArtifactsFunctions, fetchArtifactTags } from '../../reducers/artifactsReducer' +import { getFilterTagOptions, setFilters } from '../../reducers/filtersReducer' +import AddArtifactTagPopUp from '../../elements/AddArtifactTagPopUp/AddArtifactTagPopUp' +import DeployModelPopUp from '../../elements/DeployModelPopUp/DeployModelPopUp' +import { setNotification } from '../../reducers/notificationReducer' +import { usePagination } from '../../hooks/usePagination.hook' +import { checkForSelectedArtifact, setFullSelectedArtifact } from '../../utils/artifacts.util' +import { getFeatureVectorData } from '../ModelsPage/Models/models.util' +import { fetchModelFeatureVector } from '../../reducers/detailsReducer' + +const Artifacts = ({ + actionButtons = [], + artifactType, + createArtifactsRowData, + fetchArtifacts, + generateActionsMenu, + generateDetailsFormInitialValues, + generatePageData, + handleApplyDetailsChanges, + handleDeployArtifactFailure = null, + isAllVersions = false, + page = '', + renderPageTabs = null, + removeArtifacts, + storeArtifactTypeLoading, + tab +}) => { + const [artifacts, setArtifacts] = useState(null) + const [artifactVersions, setArtifactVersions] = useState(null) + const [selectedArtifact, setSelectedArtifact] = useState({}) + const [requestErrorMessage, setRequestErrorMessage] = useState('') + const [isSelectedArtifactBeyondTheList, setSelectedArtifactIsBeyondTheList] = useState(false) + const artifactsStore = useSelector(store => store.artifactsStore) + const detailsStore = useSelector(store => store.detailsStore) + const filtersStore = useSelector(store => store.filtersStore) + const frontendSpec = useSelector(store => store.appStore.frontendSpec) + const dispatch = useDispatch() + const location = useLocation() + const navigate = useNavigate() + const params = useParams() + const viewMode = getViewMode(window.location.search) + const paginationConfigArtifactsRef = useRef({}) + const paginationConfigArtifactVersionsRef = useRef({}) + const abortControllerRef = useRef(new AbortController()) + const tagAbortControllerRef = useRef(new AbortController()) + const artifactsRef = useRef(null) + const lastCheckedArtifactIdRef = useRef(null) + + const historyBackLink = useMemo( + () => + `/projects/${params.projectName}/${page ? `${page.toLowerCase()}/` : ''}${tab}${getSavedSearchParams(location.search)}`, + [location.search, page, params.projectName, tab] + ) + const filtersConfig = useMemo(() => getFiltersConfig(isAllVersions), [isAllVersions]) + const artifactsFilters = useFiltersFromSearchParams(filtersConfig) + const [refreshAfterDeleteCallback, refreshAfterDeleteTrigger] = useRefreshAfterDelete( + paginationConfigArtifactVersionsRef, + historyBackLink, + 'artifacts', + params.id && getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : tab, true), + isAllVersions + ) + const pageData = useMemo( + () => generatePageData(viewMode, selectedArtifact), + [generatePageData, selectedArtifact, viewMode] + ) + const detailsFormInitialValues = useMemo(() => { + return generateDetailsFormInitialValues(selectedArtifact, frontendSpec.internal_labels) + }, [frontendSpec.internal_labels, generateDetailsFormInitialValues, selectedArtifact]) + + const toggleConvertedYaml = useCallback( + data => { + return dispatch(toggleYaml(data)) + }, + [dispatch] + ) + + const fetchData = useCallback( + async filters => { + abortControllerRef.current = new AbortController() + + const requestParams = { + format: 'minimal' + } + + if (isAllVersions) { + requestParams.name = params.artifactName + setArtifactVersions(null) + } else { + if ( + filters[ITERATIONS_FILTER] !== SHOW_ITERATIONS || + filters[TAG_FILTER] === TAG_FILTER_ALL_ITEMS + ) { + requestParams['partition-by'] = 'project_and_name' + requestParams['partition-sort-by'] = 'updated' + } + + setArtifacts(null) + } + + if (!isAllVersions && !isEmpty(paginationConfigArtifactsRef.current)) { + requestParams.page = paginationConfigArtifactsRef.current[BE_PAGE] + requestParams['page-size'] = paginationConfigArtifactsRef.current[BE_PAGE_SIZE] + } + + if (isAllVersions && !isEmpty(paginationConfigArtifactVersionsRef.current)) { + requestParams.page = paginationConfigArtifactVersionsRef.current[BE_PAGE] + requestParams['page-size'] = paginationConfigArtifactVersionsRef.current[BE_PAGE_SIZE] + } + + lastCheckedArtifactIdRef.current = null + + return dispatch( + fetchArtifacts({ + project: params.projectName, + filters, + config: { + ui: { + controller: abortControllerRef.current, + setRequestErrorMessage + }, + params: requestParams + } + }) + ) + .unwrap() + .then(response => { + if (response?.artifacts) { + if (isAllVersions) { + paginationConfigArtifactVersionsRef.current.paginationResponse = response.pagination + setArtifactVersions(response.artifacts || []) + } else { + paginationConfigArtifactsRef.current.paginationResponse = response.pagination + setArtifacts(response.artifacts || []) + } + } else { + if (isAllVersions) { + setArtifactVersions([]) + } else { + setArtifacts([]) + } + } + + return response + }) + .catch(() => { + if (isAllVersions) { + setArtifactVersions([]) + } else { + setArtifacts([]) + } + }) + }, + [dispatch, fetchArtifacts, isAllVersions, params.artifactName, params.projectName] + ) + + const fetchTags = useCallback(() => { + tagAbortControllerRef.current = new AbortController() + + return dispatch( + getFilterTagOptions({ + dispatch, + fetchTags: fetchArtifactTags, + project: params.projectName, + category: artifactType, + config: { + signal: tagAbortControllerRef.current.signal + } + }) + ) + }, [artifactType, dispatch, params.projectName]) + + const refreshArtifacts = useCallback( + filters => { + fetchTags() + setSelectedArtifact({}) + + return fetchData(filters) + }, + [fetchData, fetchTags] + ) + + const handleAddTag = useCallback( + artifact => { + openPopUp(AddArtifactTagPopUp, { + artifact, + onAddTag: () => refreshArtifacts(artifactsFilters), + projectName: params.projectName + }) + }, + [params.projectName, refreshArtifacts, artifactsFilters] + ) + + const showAllVersions = useCallback( + artifactName => { + navigate( + `/projects/${params.projectName}/${page ? `${page.toLowerCase()}/` : ''}${tab}/${artifactName}/${ALL_VERSIONS_PATH}?${transformSearchParams(window.location.search)}` + ) + }, + [navigate, page, params.projectName, tab] + ) + + const handleDeployArtifact = useCallback( + artifact => { + abortControllerRef.current = new AbortController() + + dispatch( + fetchArtifactsFunctions({ + project: artifact.project, + filters: {}, + config: { + signal: abortControllerRef.current.signal, + params: { format: 'minimal', kind: 'serving' } + } + }) + ) + .unwrap() + .then(functions => { + if (!isNil(functions)) { + const functionOptions = chain(functions) + .filter(func => func.type === FUNCTION_TYPE_SERVING && func.graph?.kind === 'router') + .uniqBy('name') + .map(func => ({ label: func.name, id: func.name })) + .value() + + if (functionOptions.length > 0) { + openPopUp(DeployModelPopUp, { + artifact, + functionList: functions, + functionOptionList: functionOptions + }) + } else { + handleDeployArtifactFailure(params.projectName, artifact.db_key) + } + } + }) + }, + [dispatch, handleDeployArtifactFailure, params.projectName] + ) + + const actionsMenu = useMemo( + () => artifactMin => + generateActionsMenu( + artifactMin, + frontendSpec, + dispatch, + toggleConvertedYaml, + handleAddTag, + params.projectName, + refreshArtifacts, + refreshAfterDeleteCallback, + artifactsFilters, + selectedArtifact, + showAllVersions, + isAllVersions, + false, + handleDeployArtifact + ), + [ + artifactsFilters, + dispatch, + frontendSpec, + generateActionsMenu, + handleAddTag, + handleDeployArtifact, + isAllVersions, + params.projectName, + refreshAfterDeleteCallback, + refreshArtifacts, + selectedArtifact, + showAllVersions, + toggleConvertedYaml + ] + ) + + const applyDetailsChanges = useCallback( + changes => { + return handleApplyDetailsChanges( + changes, + params.projectName, + selectedArtifact, + setNotification, + dispatch + ) + }, + [dispatch, handleApplyDetailsChanges, params.projectName, selectedArtifact] + ) + + const applyDetailsChangesCallback = (changes, selectedItem) => { + if ('tag' in changes.data) { + if (isAllVersions) { + setArtifactVersions(null) + } else { + setArtifacts(null) + } + + navigate( + `/projects/${params.projectName}/${page ? `${page.toLowerCase()}/` : ''}/${tab}/${params.artifactName}${isAllVersions ? `/${ALL_VERSIONS_PATH}` : ''}/${ + changes.data.tag.currentFieldValue ? `:${changes.data.tag.currentFieldValue}` : '' + }@${selectedItem.uid}/overview${window.location.search}`, + { replace: true } + ) + } + + refreshArtifacts(artifactsFilters) + } + + const [ + handleRefreshArtifacts, + paginatedArtifacts, + searchArtifactsParams, + setSearchArtifactsParams + ] = usePagination({ + hidden: isAllVersions, + content: artifacts ?? [], + refreshContent: refreshArtifacts, + filters: artifactsFilters, + paginationConfigRef: paginationConfigArtifactsRef, + resetPaginationTrigger: `${params.projectName}_${refreshAfterDeleteTrigger}` + }) + + const [ + handleRefreshArtifactVersions, + paginatedArtifactVersions, + searchArtifactVersionsParams, + setSearchArtifactVersionsParams + ] = usePagination({ + hidden: !isAllVersions, + content: artifactVersions ?? [], + refreshContent: refreshArtifacts, + filters: artifactsFilters, + paginationConfigRef: paginationConfigArtifactVersionsRef, + resetPaginationTrigger: `${params.projectName}_${isAllVersions}` + }) + + const tableContent = useMemo(() => { + return (isAllVersions ? paginatedArtifactVersions : paginatedArtifacts).map(contentItem => + createArtifactsRowData(contentItem, params.projectName, isAllVersions) + ) + }, [ + createArtifactsRowData, + isAllVersions, + paginatedArtifactVersions, + paginatedArtifacts, + params.projectName + ]) + + const tableHeaders = useMemo(() => tableContent[0]?.content ?? [], [tableContent]) + + const getAndSetSelectedArtifact = useCallback(() => { + setFullSelectedArtifact( + tab, + dispatch, + navigate, + params.artifactName, + setSelectedArtifact, + params.projectName, + params.id, + isAllVersions + ) + }, [tab, dispatch, navigate, params.artifactName, params.projectName, params.id, isAllVersions]) + + useEffect(() => { + if (params.id && pageData.details.menu.length > 0) { + isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) + } + }, [navigate, location, pageData.details.menu, params.tab, params.id]) + + useEffect(() => { + if (isEmpty(selectedArtifact)) { + lastCheckedArtifactIdRef.current = null + } + }, [selectedArtifact]) + + useEffect(() => { + checkForSelectedArtifact({ + artifactName: params.artifactName, + artifacts: isAllVersions ? artifactVersions : artifacts, + dispatch, + isAllVersions, + navigate, + paginatedArtifacts: isAllVersions ? paginatedArtifactVersions : paginatedArtifacts, + paginationConfigRef: isAllVersions + ? paginationConfigArtifactVersionsRef + : paginationConfigArtifactsRef, + paramsId: params.id, + projectName: params.projectName, + searchParams: isAllVersions ? searchArtifactVersionsParams : searchArtifactsParams, + setSearchParams: isAllVersions ? setSearchArtifactVersionsParams : setSearchArtifactsParams, + setSelectedArtifact: setSelectedArtifact, + setSelectedArtifactIsBeyondTheList, + lastCheckedArtifactIdRef, + tab: tab + }) + }, [ + artifactVersions, + artifacts, + dispatch, + isAllVersions, + navigate, + paginatedArtifactVersions, + paginatedArtifacts, + params.artifactName, + params.id, + params.projectName, + searchArtifactVersionsParams, + searchArtifactsParams, + setSearchArtifactVersionsParams, + setSearchArtifactsParams, + tab + ]) + + useEffect(() => { + const tagAbortControllerCurrent = tagAbortControllerRef.current + + return () => { + dispatch(removeArtifacts()) + setSelectedArtifact({}) + abortControllerRef.current.abort(REQUEST_CANCELED) + tagAbortControllerCurrent.abort(REQUEST_CANCELED) + } + }, [params.projectName, dispatch, tagAbortControllerRef, removeArtifacts]) + + useEffect(() => { + return () => { + setArtifacts(null) + setArtifactVersions(null) + } + }, [params.projectName]) + + useEffect(() => { + dispatch(setFilters({ groupBy: GROUP_BY_NONE })) + }, [dispatch, params.projectName]) + + useEffect(() => { + if ( + selectedArtifact.feature_vector && + !detailsStore.error && + isEmpty(detailsStore.modelFeatureVectorData) + ) { + const { name, tag } = getFeatureVectorData(selectedArtifact.feature_vector) + dispatch(fetchModelFeatureVector({ project: params.projectName, name, reference: tag })) + } + }, [ + detailsStore.error, + detailsStore.modelFeatureVectorData, + dispatch, + params.projectName, + selectedArtifact.feature_vector + ]) + + return ( + + ) +} + +Artifacts.propTypes = { + actionButtons: PropTypes.array, + artifactType: PropTypes.string.isRequired, + createArtifactsRowData: PropTypes.func.isRequired, + fetchArtifacts: PropTypes.func.isRequired, + generateActionsMenu: PropTypes.func.isRequired, + generateDetailsFormInitialValues: PropTypes.func.isRequired, + generatePageData: PropTypes.func.isRequired, + handleApplyDetailsChanges: PropTypes.func.isRequired, + handleDeployArtifactFailure: PropTypes.func, + isAllVersions: PropTypes.bool, + page: PropTypes.string, + renderPageTabs: PropTypes.func, + removeArtifacts: PropTypes.func.isRequired, + storeArtifactTypeLoading: PropTypes.bool.isRequired, + tab: PropTypes.string.isRequired +} + +export default Artifacts diff --git a/src/components/Artifacts/ArtifactsTable.jsx b/src/components/Artifacts/ArtifactsTable.jsx new file mode 100644 index 0000000000..16da0570cf --- /dev/null +++ b/src/components/Artifacts/ArtifactsTable.jsx @@ -0,0 +1,182 @@ +/* +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. +*/ +import React from 'react' +import PropTypes from 'prop-types' +import { isEmpty } from 'lodash' + +import NoData from '../../common/NoData/NoData' +import Loader from '../../common/Loader/Loader' +import Table from '../Table/Table' +import ArtifactsFilters from '../ArtifactsActionBar/ArtifactsFilters' +import ActionBar from '../ActionBar/ActionBar' +import HistoryBackLink from '../../common/HistoryBackLink/historyBackLink' +import Details from '../Details/Details' +import ArtifactsTableRow from '../../elements/ArtifactsTableRow/ArtifactsTableRow' +import Pagination from '../../common/Pagination/Pagination' + +import { getNoDataMessage } from '../../utils/getNoDataMessage' +import { ALL_VERSIONS_PATH, FULL_VIEW_MODE } from '../../constants' +import { getCloseDetailsLink } from '../../utils/link-helper.util' +import { getDefaultFirstHeader } from '../../utils/createArtifactsContent' + +let ArtifactsTable = ({ + actionButtons, + actionsMenu, + applyDetailsChanges, + applyDetailsChangesCallback, + artifactName, + artifacts, + artifactsStore, + detailsFormInitialValues, + filters, + filtersConfig, + filtersStore, + getAndSetSelectedArtifact, + handleRefreshArtifacts, + historyBackLink, + isAllVersions, + page, + pageData, + paginationConfigArtifactsRef, + requestErrorMessage, + renderPageTabs = null, + selectedArtifact, + setSearchArtifactsParams, + setSelectedArtifact, + storeArtifactTypeLoading, + tab, + tableContent, + tableHeaders, + viewMode +}) => { + return ( +
+
+ {renderPageTabs && renderPageTabs()} + {isAllVersions && } + + + +
+ {artifactsStore.loading ? null : tableContent.length === 0 && isEmpty(selectedArtifact) ? ( + + ) : ( + <> + {storeArtifactTypeLoading && } + getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : tab)} + handleCancel={() => setSelectedArtifact({})} + pageData={pageData} + selectedItem={selectedArtifact} + tableClassName="artifacts-table" + tableHeaders={ + !isEmpty(tableHeaders) ? tableHeaders : getDefaultFirstHeader(isAllVersions) + } + viewMode={viewMode} + > + {tableContent.map((tableItem, index) => ( + + ))} +
+ + + )} + {viewMode === FULL_VIEW_MODE && !isEmpty(selectedArtifact) && ( +
+ )} +
+ ) +} + +ArtifactsTable.propTypes = { + actionButtons: PropTypes.array, + actionsMenu: PropTypes.func.isRequired, + applyDetailsChanges: PropTypes.func.isRequired, + applyDetailsChangesCallback: PropTypes.func.isRequired, + artifactName: PropTypes.string.isRequired, + artifacts: PropTypes.array.isRequired, + artifactsStore: PropTypes.object.isRequired, + detailsFormInitialValues: PropTypes.object.isRequired, + filters: PropTypes.object.isRequired, + filtersConfig: PropTypes.object.isRequired, + filtersStore: PropTypes.object.isRequired, + getAndSetSelectedArtifact: PropTypes.func.isRequired, + handleRefreshArtifacts: PropTypes.func.isRequired, + historyBackLink: PropTypes.string.isRequired, + isAllVersions: PropTypes.bool.isRequired, + page: PropTypes.string, + pageData: PropTypes.object.isRequired, + paginationConfigArtifactsRef: PropTypes.object.isRequired, + requestErrorMessage: PropTypes.string, + renderPageTabs: PropTypes.func, + selectedArtifact: PropTypes.object.isRequired, + setSearchArtifactsParams: PropTypes.func.isRequired, + setSelectedArtifact: PropTypes.func.isRequired, + storeArtifactTypeLoading: PropTypes.bool.isRequired, + tab: PropTypes.string.isRequired, + tableContent: PropTypes.array.isRequired, + tableHeaders: PropTypes.array.isRequired, + viewMode: PropTypes.string.isRequired +} + +export default ArtifactsTable diff --git a/src/components/Artifacts/ArtifactsView.jsx b/src/components/Artifacts/ArtifactsView.jsx new file mode 100644 index 0000000000..a14c21c0bf --- /dev/null +++ b/src/components/Artifacts/ArtifactsView.jsx @@ -0,0 +1,175 @@ +/* +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. +*/ +import React from 'react' +import PropTypes from 'prop-types' + +import ArtifactsTable from './ArtifactsTable' +import Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs' +import Loader from '../../common/Loader/Loader' + +import './artifacts.scss' + +let ArtifactsView = React.forwardRef( + ( + { + actionButtons = [], + actionsMenu, + applyDetailsChanges, + applyDetailsChangesCallback, + artifactName = '', + artifacts, + artifactsStore, + detailsFormInitialValues, + filters, + filtersConfig, + filtersStore, + getAndSetSelectedArtifact, + handleRefreshArtifacts, + historyBackLink, + isAllVersions, + isOnlyTabScreen, + page = '', + pageData, + paginationConfigArtifactsRef, + requestErrorMessage = '', + renderPageTabs = null, + selectedArtifact, + setSearchArtifactsParams, + setSelectedArtifact, + storeArtifactTypeLoading, + tab, + tableContent, + tableHeaders, + viewMode = '' + }, + { artifactsRef } + ) => { + return isOnlyTabScreen ? ( +
+ +
+ ) : ( + <> +
+
+ +
+
+ {artifactsStore.loading && } + +
+
+ + ) + } +) + +ArtifactsView.displayName = 'ArtifactsView' + +ArtifactsView.propTypes = { + actionButtons: PropTypes.array, + actionsMenu: PropTypes.func.isRequired, + applyDetailsChanges: PropTypes.func.isRequired, + applyDetailsChangesCallback: PropTypes.func.isRequired, + artifactName: PropTypes.string, + artifacts: PropTypes.array.isRequired, + artifactsStore: PropTypes.object.isRequired, + detailsFormInitialValues: PropTypes.object.isRequired, + filters: PropTypes.object.isRequired, + filtersConfig: PropTypes.object.isRequired, + filtersStore: PropTypes.object.isRequired, + getAndSetSelectedArtifact: PropTypes.func.isRequired, + handleRefreshArtifacts: PropTypes.func.isRequired, + historyBackLink: PropTypes.string.isRequired, + isAllVersions: PropTypes.bool.isRequired, + isOnlyTabScreen: PropTypes.bool.isRequired, + page: PropTypes.string, + pageData: PropTypes.object.isRequired, + paginationConfigArtifactsRef: PropTypes.object.isRequired, + requestErrorMessage: PropTypes.string, + renderPageTabs: PropTypes.func, + selectedArtifact: PropTypes.object.isRequired, + setSearchArtifactsParams: PropTypes.func.isRequired, + setSelectedArtifact: PropTypes.func.isRequired, + storeArtifactTypeLoading: PropTypes.bool.isRequired, + tab: PropTypes.string.isRequired, + tableContent: PropTypes.array.isRequired, + tableHeaders: PropTypes.array.isRequired, + viewMode: PropTypes.string +} + +export default ArtifactsView diff --git a/src/components/Artifacts/artifacts.scss b/src/components/Artifacts/artifacts.scss new file mode 100644 index 0000000000..6407045424 --- /dev/null +++ b/src/components/Artifacts/artifacts.scss @@ -0,0 +1,14 @@ +@use 'igz-controls/scss/variables'; +@use '/src/scss/mixins'; + +$artifactsRowHeight: variables.$rowHeight; +$artifactsHeaderRowHeight: variables.$headerRowHeight; +$artifactsRowHeightExtended: variables.$rowHeightExtended; + +.datasets-table { + @include mixins.rowsHeight( + $artifactsHeaderRowHeight, + $artifactsRowHeight, + $artifactsRowHeightExtended + ); +} diff --git a/src/components/LLMPrompts/LLMPrompts.js b/src/components/Artifacts/artifacts.util.js similarity index 56% rename from src/components/LLMPrompts/LLMPrompts.js rename to src/components/Artifacts/artifacts.util.js index b485f0f59d..d147343d62 100644 --- a/src/components/LLMPrompts/LLMPrompts.js +++ b/src/components/Artifacts/artifacts.util.js @@ -17,7 +17,27 @@ 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 { + ITERATIONS_FILTER, + LABELS_FILTER, + NAME_FILTER, + SHOW_ITERATIONS, + TAG_FILTER, + TAG_FILTER_ALL_ITEMS, + TAG_FILTER_LATEST +} from '../../constants' -const LLMPrompts = () => {} - -export default LLMPrompts +export const getFiltersConfig = isAllVersions => ({ + [NAME_FILTER]: { label: 'Name:', initialValue: '', hidden: isAllVersions }, + [TAG_FILTER]: { + label: 'Version tag:', + initialValue: isAllVersions ? TAG_FILTER_ALL_ITEMS : TAG_FILTER_LATEST, + isModal: true + }, + [LABELS_FILTER]: { label: 'Labels:', initialValue: '', isModal: true }, + [ITERATIONS_FILTER]: { + label: 'Show best iteration only:', + initialValue: isAllVersions ? '' : SHOW_ITERATIONS, + isModal: true + } +}) diff --git a/src/components/Datasets/Datasets.jsx b/src/components/Datasets/Datasets.jsx index 32e1f8d0f7..5972e5226f 100644 --- a/src/components/Datasets/Datasets.jsx +++ b/src/components/Datasets/Datasets.jsx @@ -17,450 +17,64 @@ 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, useMemo } from 'react' +import React, { useCallback } from 'react' import PropTypes from 'prop-types' -import { useDispatch, useSelector } from 'react-redux' -import { useLocation, useNavigate, useParams } from 'react-router-dom' -import { isEmpty } from 'lodash' +import { useSelector } from 'react-redux' -import AddArtifactTagPopUp from '../../elements/AddArtifactTagPopUp/AddArtifactTagPopUp' -import DatasetsView from './DatasetsView' import RegisterArtifactModal from '../RegisterArtifactModal/RegisterArtifactModal' +import Artifacts from '../Artifacts/Artifacts' +import { DATASETS_TAB, DATASET_TYPE } from '../../constants' +import { PRIMARY_BUTTON } from 'igz-controls/constants' import { - ALL_VERSIONS_PATH, - DATASETS_PAGE, - DATASETS_TAB, - DATASET_TYPE, - GROUP_BY_NONE, - REQUEST_CANCELED, - BE_PAGE, - BE_PAGE_SIZE, - ITERATIONS_FILTER, - SHOW_ITERATIONS, - TAG_FILTER, - TAG_FILTER_ALL_ITEMS -} from '../../constants' -import { - getFiltersConfig, generateActionsMenu, generatePageData, handleApplyDetailsChanges, registerDatasetTitle } from './datasets.util' import { createDatasetsRowData } from '../../utils/createArtifactsContent' -import { fetchArtifactTags, fetchDataSets, removeDataSets } from '../../reducers/artifactsReducer' -import { getFilterTagOptions, setFilters } from '../../reducers/filtersReducer' -import { getSavedSearchParams, transformSearchParams } from '../../utils/filter.util' -import { getViewMode } from '../../utils/helper' -import { getCloseDetailsLink, isDetailsTabExists } from '../../utils/link-helper.util' +import { fetchDataSets, removeDataSets } from '../../reducers/artifactsReducer' import { openPopUp } from 'igz-controls/utils/common.util' -import { checkForSelectedArtifact, setFullSelectedArtifact } from '../../utils/artifacts.util' -import { setNotification } from '../../reducers/notificationReducer' -import { toggleYaml } from '../../reducers/appReducer' -import { useFiltersFromSearchParams } from '../../hooks/useFiltersFromSearchParams.hook' -import { useMode } from '../../hooks/mode.hook' -import { usePagination } from '../../hooks/usePagination.hook' -import { useRefreshAfterDelete } from '../../hooks/useRefreshAfterDelete.hook' - -import './datasets.scss' const Datasets = ({ isAllVersions = false }) => { - const [datasets, setDatasets] = useState([]) - const [datasetVersions, setDatasetVersions] = useState([]) - const [selectedDataset, setSelectedDataset] = useState({}) - const [requestErrorMessage, setRequestErrorMessage] = useState('') const artifactsStore = useSelector(store => store.artifactsStore) - const filtersStore = useSelector(store => store.filtersStore) - const frontendSpec = useSelector(store => store.appStore.frontendSpec) - const viewMode = getViewMode(window.location.search) - const dispatch = useDispatch() - const location = useLocation() - const navigate = useNavigate() - const { isDemoMode } = useMode() - const params = useParams() - const paginationConfigDatasetsRef = useRef({}) - const paginationConfigDatasetVersionsRef = useRef({}) - const abortControllerRef = useRef(new AbortController()) - const tagAbortControllerRef = useRef(new AbortController()) - const datasetsRef = useRef(null) - const lastCheckedArtifactIdRef = useRef(null) - - const historyBackLink = useMemo( - () => `/projects/${params.projectName}/datasets${getSavedSearchParams(location.search)}`, - [location.search, params.projectName] - ) - const filtersConfig = useMemo(() => getFiltersConfig(isAllVersions), [isAllVersions]) - const datasetsFilters = useFiltersFromSearchParams(filtersConfig) - const [refreshAfterDeleteCallback, refreshAfterDeleteTrigger] = useRefreshAfterDelete( - paginationConfigDatasetVersionsRef, - historyBackLink, - 'artifacts', - params.id && getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : DATASETS_TAB, true), - isAllVersions - ) - - const pageData = useMemo( - () => generatePageData(selectedDataset, viewMode, params, false, isDemoMode), - [isDemoMode, selectedDataset, viewMode, params] - ) - - const detailsFormInitialValues = useMemo( - () => ({ + const generateDetailsFormInitialValues = useCallback( + selectedDataset => ({ tag: selectedDataset.tag ?? '' }), - [selectedDataset.tag] - ) - - const toggleConvertedYaml = useCallback( - data => { - return dispatch(toggleYaml(data)) - }, - [dispatch] - ) - - const fetchData = useCallback( - filters => { - abortControllerRef.current = new AbortController() - - const requestParams = { - format: 'minimal' - } - - if (isAllVersions) { - requestParams.name = params.datasetName - setDatasetVersions(null) - } else { - if ( - filters[ITERATIONS_FILTER] !== SHOW_ITERATIONS || - filters[TAG_FILTER] === TAG_FILTER_ALL_ITEMS - ) { - requestParams['partition-by'] = 'project_and_name' - requestParams['partition-sort-by'] = 'updated' - } - - setDatasets(null) - } - - if (!isAllVersions && !isEmpty(paginationConfigDatasetsRef.current)) { - requestParams.page = paginationConfigDatasetsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigDatasetsRef.current[BE_PAGE_SIZE] - } - - if (isAllVersions && !isEmpty(paginationConfigDatasetVersionsRef.current)) { - requestParams.page = paginationConfigDatasetVersionsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigDatasetVersionsRef.current[BE_PAGE_SIZE] - } - - lastCheckedArtifactIdRef.current = null - - return dispatch( - fetchDataSets({ - project: params.projectName, - filters, - config: { - ui: { - controller: abortControllerRef.current, - setRequestErrorMessage - }, - params: requestParams - } - }) - ) - .unwrap() - .then(response => { - if (response?.artifacts) { - if (isAllVersions) { - paginationConfigDatasetVersionsRef.current.paginationResponse = response.pagination - setDatasetVersions(response.artifacts || []) - } else { - paginationConfigDatasetsRef.current.paginationResponse = response.pagination - setDatasets(response.artifacts || []) - } - } else { - if (isAllVersions) { - setDatasetVersions([]) - } else { - setDatasets([]) - } - } - - return response - }) - .catch(() => { - if (isAllVersions) { - setDatasetVersions([]) - } else { - setDatasets([]) - } - }) - }, - [dispatch, isAllVersions, params.datasetName, params.projectName] - ) - - const fetchTags = useCallback(() => { - tagAbortControllerRef.current = new AbortController() - - return dispatch( - getFilterTagOptions({ - dispatch, - fetchTags: fetchArtifactTags, - project: params.projectName, - category: DATASET_TYPE, - config: { - signal: tagAbortControllerRef.current.signal - } - }) - ) - }, [dispatch, params.projectName]) - - const refreshDatasets = useCallback( - filters => { - fetchTags() - setSelectedDataset({}) - - return fetchData(filters) - }, - [fetchData, fetchTags] - ) - - const handleAddTag = useCallback( - artifact => { - openPopUp(AddArtifactTagPopUp, { - artifact, - onAddTag: () => refreshDatasets(datasetsFilters), - projectName: params.projectName - }) - }, - [params.projectName, refreshDatasets, datasetsFilters] - ) - - const showAllVersions = useCallback( - datasetName => { - navigate( - `/projects/${params.projectName}/datasets/${datasetName}/${ALL_VERSIONS_PATH}?${transformSearchParams(window.location.search)}` - ) - }, - [navigate, params.projectName] - ) - - const actionsMenu = useMemo( - () => datasetMin => - generateActionsMenu( - datasetMin, - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshDatasets, - refreshAfterDeleteCallback, - datasetsFilters, - selectedDataset, - showAllVersions, - isAllVersions - ), - [ - datasetsFilters, - dispatch, - frontendSpec, - handleAddTag, - isAllVersions, - params.projectName, - refreshAfterDeleteCallback, - refreshDatasets, - selectedDataset, - showAllVersions, - toggleConvertedYaml - ] + [] ) - const applyDetailsChanges = useCallback( - changes => { - return handleApplyDetailsChanges( - changes, - params.projectName, - selectedDataset, - setNotification, - dispatch - ) - }, - [dispatch, params.projectName, selectedDataset] - ) - - const applyDetailsChangesCallback = (changes, selectedItem) => { - if ('tag' in changes.data) { - if (isAllVersions) { - setDatasetVersions(null) - } else { - setDatasets(null) - } - - navigate( - `/projects/${params.projectName}/${DATASETS_PAGE.toLowerCase()}/${params.datasetName}${isAllVersions ? `/${ALL_VERSIONS_PATH}` : ''}/${ - changes.data.tag.currentFieldValue ? `:${changes.data.tag.currentFieldValue}` : '' - }@${selectedItem.uid}/overview${window.location.search}`, - { replace: true } - ) - } - - refreshDatasets(datasetsFilters) - } - - useEffect(() => { - if (params.id && pageData.details.menu.length > 0) { - isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) - } - }, [location, navigate, pageData.details.menu, params.id, params.tab]) - - useEffect(() => { - dispatch(setFilters({ groupBy: GROUP_BY_NONE })) - }, [dispatch, params.projectName]) - - useEffect(() => { - return () => { - setDatasets(null) - setDatasetVersions(null) - } - }, [params.projectName]) - - useEffect(() => { - const tagAbortControllerCurrent = tagAbortControllerRef.current - - return () => { - dispatch(removeDataSets()) - setSelectedDataset({}) - abortControllerRef.current.abort(REQUEST_CANCELED) - tagAbortControllerCurrent.abort(REQUEST_CANCELED) - } - }, [dispatch, params.projectName, tagAbortControllerRef]) - - const handleRegisterDataset = useCallback(() => { + const handleRegisterDataset = useCallback((params, refreshDatasets, datasetsFilters) => { openPopUp(RegisterArtifactModal, { artifactKind: DATASET_TYPE, params, refresh: () => refreshDatasets(datasetsFilters), title: registerDatasetTitle }) - }, [params, refreshDatasets, datasetsFilters]) - - const [handleRefreshDatasets, paginatedDatasets, searchDatasetsParams, setSearchDatasetsParams] = - usePagination({ - hidden: isAllVersions, - content: datasets ?? [], - refreshContent: refreshDatasets, - filters: datasetsFilters, - paginationConfigRef: paginationConfigDatasetsRef, - resetPaginationTrigger: `${params.projectName}_${refreshAfterDeleteTrigger}` - }) - const [ - handleRefreshDatasetVersions, - paginatedDatasetVersions, - searchDatasetVersionsParams, - setSearchDatasetVersionsParams - ] = usePagination({ - hidden: !isAllVersions, - content: datasetVersions ?? [], - refreshContent: refreshDatasets, - filters: datasetsFilters, - paginationConfigRef: paginationConfigDatasetVersionsRef, - resetPaginationTrigger: `${params.projectName}_${isAllVersions}` - }) - - const tableContent = useMemo(() => { - return (isAllVersions ? paginatedDatasetVersions : paginatedDatasets).map(contentItem => - createDatasetsRowData(contentItem, params.projectName, isAllVersions) - ) - }, [isAllVersions, paginatedDatasetVersions, paginatedDatasets, params.projectName]) - - const tableHeaders = useMemo(() => tableContent[0]?.content ?? [], [tableContent]) - - useEffect(() => { - checkForSelectedArtifact({ - artifactName: params.datasetName, - artifacts: isAllVersions ? datasetVersions : datasets, - dispatch, - isAllVersions, - navigate, - paginatedArtifacts: isAllVersions ? paginatedDatasetVersions : paginatedDatasets, - paginationConfigRef: isAllVersions - ? paginationConfigDatasetVersionsRef - : paginationConfigDatasetsRef, - paramsId: params.id, - projectName: params.projectName, - searchParams: isAllVersions ? searchDatasetVersionsParams : searchDatasetsParams, - setSearchParams: isAllVersions ? setSearchDatasetVersionsParams : setSearchDatasetsParams, - setSelectedArtifact: setSelectedDataset, - lastCheckedArtifactIdRef, - tab: DATASETS_TAB - }) - }, [ - isAllVersions, - navigate, - params.id, - params.datasetName, - params.projectName, - paginatedDatasetVersions, - paginatedDatasets, - searchDatasetVersionsParams, - searchDatasetsParams, - datasetVersions, - datasets, - setSearchDatasetVersionsParams, - setSearchDatasetsParams, - dispatch - ]) - - useEffect(() => { - if (isEmpty(selectedDataset)) { - lastCheckedArtifactIdRef.current = null - } - }, [selectedDataset]) - - const getAndSetSelectedArtifact = useCallback(() => { - setFullSelectedArtifact( - DATASETS_TAB, - dispatch, - navigate, - params.datasetName, - setSelectedDataset, - params.projectName, - params.id, - isAllVersions - ) - }, [dispatch, navigate, params.datasetName, params.projectName, params.id, isAllVersions]) + }, []) return ( - ) } diff --git a/src/components/Datasets/DatasetsView.jsx b/src/components/Datasets/DatasetsView.jsx deleted file mode 100644 index 990795af79..0000000000 --- a/src/components/Datasets/DatasetsView.jsx +++ /dev/null @@ -1,206 +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. -*/ -import React from 'react' -import PropTypes from 'prop-types' -import { isEmpty } from 'lodash' - -import ActionBar from '../ActionBar/ActionBar' -import ArtifactsFilters from '../ArtifactsActionBar/ArtifactsFilters' -import ArtifactsTableRow from '../../elements/ArtifactsTableRow/ArtifactsTableRow' -import Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs' -import Details from '../Details/Details' -import HistoryBackLink from '../../common/HistoryBackLink/historyBackLink' -import Loader from '../../common/Loader/Loader' -import NoData from '../../common/NoData/NoData' -import Pagination from '../../common/Pagination/Pagination' -import PreviewModal from '../../elements/PreviewModal/PreviewModal' -import Table from '../Table/Table' - -import { ALL_VERSIONS_PATH, DATASETS_PAGE, DATASETS_TAB, FULL_VIEW_MODE } from '../../constants' -import { ACTIONS_MENU } from '../../types' -import { PRIMARY_BUTTON } from 'igz-controls/constants' -import { getCloseDetailsLink } from '../../utils/link-helper.util' -import { getNoDataMessage } from '../../utils/getNoDataMessage' -import { registerDatasetTitle } from './datasets.util' -import { getDefaultFirstHeader } from '../../utils/createArtifactsContent' - -const DatasetsView = React.forwardRef( - ( - { - actionsMenu, - applyDetailsChanges, - applyDetailsChangesCallback, - artifactsStore, - datasetName, - datasets, - detailsFormInitialValues, - filters, - filtersConfig, - filtersStore, - getAndSetSelectedArtifact, - handleRefreshDatasets, - handleRegisterDataset, - historyBackLink, - isAllVersions, - pageData, - paginationConfigDatasetsRef, - requestErrorMessage, - selectedDataset, - setSearchDatasetsParams, - setSelectedDataset, - tableContent, - tableHeaders, - viewMode = '' - }, - { datasetsRef } - ) => { - return ( - <> -
-
- -
-
- {artifactsStore.loading && } -
-
- {isAllVersions && } - - - -
- {artifactsStore.loading ? null : tableContent.length === 0 && - isEmpty(selectedDataset) ? ( - - ) : ( - <> - {artifactsStore.dataSets.datasetLoading && } - - getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : DATASETS_TAB) - } - handleCancel={() => setSelectedDataset({})} - pageData={pageData} - selectedItem={selectedDataset} - tableClassName="datasets-table" - tableHeaders={ - !isEmpty(tableHeaders) ? tableHeaders : getDefaultFirstHeader(isAllVersions) - } - viewMode={viewMode} - > - {tableContent.map((tableItem, index) => ( - - ))} -
- - - )} - {viewMode === FULL_VIEW_MODE && !isEmpty(selectedDataset) && ( -
- )} -
-
-
- {artifactsStore?.preview?.isPreview && ( - - )} - - ) - } -) - -DatasetsView.displayName = 'DatasetsView' - -DatasetsView.propTypes = { - actionsMenu: ACTIONS_MENU.isRequired, - applyDetailsChanges: PropTypes.func.isRequired, - applyDetailsChangesCallback: PropTypes.func.isRequired, - artifactsStore: PropTypes.object.isRequired, - datasetName: PropTypes.string, - datasets: PropTypes.arrayOf(PropTypes.object).isRequired, - detailsFormInitialValues: PropTypes.object.isRequired, - filters: PropTypes.object.isRequired, - filtersConfig: PropTypes.object.isRequired, - filtersStore: PropTypes.object.isRequired, - getAndSetSelectedArtifact: PropTypes.func.isRequired, - handleRefreshDatasets: PropTypes.func.isRequired, - handleRegisterDataset: PropTypes.func.isRequired, - historyBackLink: PropTypes.string.isRequired, - isAllVersions: PropTypes.bool.isRequired, - pageData: PropTypes.object.isRequired, - paginationConfigDatasetsRef: PropTypes.object.isRequired, - requestErrorMessage: PropTypes.string.isRequired, - selectedDataset: PropTypes.object.isRequired, - setSearchDatasetsParams: PropTypes.func.isRequired, - setSelectedDataset: PropTypes.func.isRequired, - tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, - tableHeaders: PropTypes.arrayOf(PropTypes.object).isRequired, - viewMode: PropTypes.string -} - -export default DatasetsView diff --git a/src/components/Datasets/datasets.scss b/src/components/Datasets/datasets.scss deleted file mode 100644 index 74dfdce34d..0000000000 --- a/src/components/Datasets/datasets.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; - -$datasetsRowHeight: variables.$rowHeight; -$datasetsHeaderRowHeight: variables.$headerRowHeight; -$datasetsRowHeightExtended: variables.$rowHeightExtended; - -.datasets-table { - @include mixins.rowsHeight( - $datasetsHeaderRowHeight, - $datasetsRowHeight, - $datasetsRowHeightExtended - ); -} diff --git a/src/components/Datasets/datasets.util.jsx b/src/components/Datasets/datasets.util.jsx index c2a6432bd8..57b6566b3c 100644 --- a/src/components/Datasets/datasets.util.jsx +++ b/src/components/Datasets/datasets.util.jsx @@ -27,14 +27,7 @@ import { DATASET_TYPE, DATASETS_PAGE, DATASETS_TAB, - FULL_VIEW_MODE, - ITERATIONS_FILTER, - LABELS_FILTER, - NAME_FILTER, - TAG_FILTER, - TAG_FILTER_ALL_ITEMS, - TAG_FILTER_LATEST, - SHOW_ITERATIONS + FULL_VIEW_MODE } from '../../constants' import { PRIMARY_BUTTON } from 'igz-controls/constants' import { applyTagChanges, chooseOrFetchArtifact } from '../../utils/artifacts.util' @@ -71,21 +64,6 @@ export const infoHeaders = [ { label: 'Labels', id: 'labels' } ] -export const getFiltersConfig = isAllVersions => ({ - [NAME_FILTER]: { label: 'Name:', initialValue: '', hidden: isAllVersions }, - [TAG_FILTER]: { - label: 'Version tag:', - initialValue: isAllVersions ? TAG_FILTER_ALL_ITEMS : TAG_FILTER_LATEST, - isModal: true - }, - [LABELS_FILTER]: { label: 'Labels:', initialValue: '', isModal: true }, - [ITERATIONS_FILTER]: { - label: 'Show best iteration only:', - initialValue: isAllVersions ? '' : SHOW_ITERATIONS, - isModal: true - } -}) - export const registerDatasetTitle = 'Register dataset' export const generateDataSetsDetailsMenu = (selectedItem, isDemoMode) => [ @@ -100,12 +78,12 @@ export const generateDataSetsDetailsMenu = (selectedItem, isDemoMode) => [ { label: 'metadata', id: 'metadata', - hidden: !selectedItem.schema + hidden: !selectedItem?.schema }, { label: 'analysis', id: 'analysis', - hidden: !isDemoMode || !selectedItem.extra_data + hidden: !isDemoMode || !selectedItem?.extra_data } ] diff --git a/src/components/Details/Details.jsx b/src/components/Details/Details.jsx index a575facda4..520e822580 100644 --- a/src/components/Details/Details.jsx +++ b/src/components/Details/Details.jsx @@ -45,6 +45,7 @@ import { FILES_TAB, FUNCTIONS_PAGE, JOBS_PAGE, + LLM_PROMPTS_TAB, MODEL_ENDPOINTS_TAB, MODELS_TAB, VIEW_SEARCH_PARAMETER @@ -154,7 +155,8 @@ const Details = ({ pageData.details.type === MODELS_TAB || pageData.details.type === MODEL_ENDPOINTS_TAB || pageData.details.type === DATASETS_TAB || - pageData.details.type === DOCUMENTS_TAB + pageData.details.type === DOCUMENTS_TAB || + pageData.details.type === LLM_PROMPTS_TAB ) { dispatch( setDetailsInfo( diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index c63ab4f799..3eec0e2faa 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -17,442 +17,42 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useLocation, useNavigate, useParams } from 'react-router-dom' -import { useDispatch, useSelector } from 'react-redux' +import { useCallback } from 'react' import PropTypes from 'prop-types' -import { isEmpty } from 'lodash' +import { useSelector } from 'react-redux' -import DocumentsView from './DocumentsView' -import AddArtifactTagPopUp from '../../elements/AddArtifactTagPopUp/AddArtifactTagPopUp' +import Artifacts from '../Artifacts/Artifacts' -import { - ALL_VERSIONS_PATH, - BE_PAGE, - BE_PAGE_SIZE, - DOCUMENT_TYPE, - DOCUMENTS_TAB, - GROUP_BY_NONE, - ITERATIONS_FILTER, - REQUEST_CANCELED, - SHOW_ITERATIONS, - TAG_FILTER, - TAG_FILTER_ALL_ITEMS -} from '../../constants' -import { - getFiltersConfig, - generatePageData, - handleApplyDetailsChanges, - generateActionsMenu -} from './documents.util' +import { DOCUMENT_TYPE, DOCUMENTS_TAB } from '../../constants' +import { generatePageData, handleApplyDetailsChanges, generateActionsMenu } from './documents.util' import { createDocumentsRowData } from '../../utils/createArtifactsContent' -import { fetchArtifactTags, fetchDocuments, removeDocuments } from '../../reducers/artifactsReducer' -import { getFilterTagOptions, setFilters } from '../../reducers/filtersReducer' -import { getSavedSearchParams, transformSearchParams } from '../../utils/filter.util' -import { getViewMode } from '../../utils/helper' -import { getCloseDetailsLink, isDetailsTabExists } from '../../utils/link-helper.util' -import { openPopUp } from 'igz-controls/utils/common.util' +import { fetchDocuments, removeDocuments } from '../../reducers/artifactsReducer' import { parseChipsData } from '../../utils/convertChipsData' -import { checkForSelectedArtifact, setFullSelectedArtifact } from '../../utils/artifacts.util' -import { setNotification } from '../../reducers/notificationReducer' -import { toggleYaml } from '../../reducers/appReducer' -import { useFiltersFromSearchParams } from '../../hooks/useFiltersFromSearchParams.hook' -import { usePagination } from '../../hooks/usePagination.hook' -import { useRefreshAfterDelete } from '../../hooks/useRefreshAfterDelete.hook' - -import './documents.scss' const Documents = ({ isAllVersions = false }) => { - const [documents, setDocuments] = useState(null) - const [documentVersions, setDocumentVersions] = useState(null) - const [selectedDocument, setSelectedDocument] = useState({}) - const viewMode = getViewMode(window.location.search) - const [requestErrorMessage, setRequestErrorMessage] = useState('') - const [isSelectedArtifactBeyondTheList, setSelectedArtifactIsBeyondTheList] = useState(false) const artifactsStore = useSelector(store => store.artifactsStore) - const filtersStore = useSelector(store => store.filtersStore) - const frontendSpec = useSelector(store => store.appStore.frontendSpec) - const dispatch = useDispatch() - const location = useLocation() - const navigate = useNavigate() - const params = useParams() - const abortControllerRef = useRef(new AbortController()) - const tagAbortControllerRef = useRef(new AbortController()) - const documentsRef = useRef(null) - const paginationConfigDocumentsRef = useRef({}) - const paginationConfigDocumentVersionsRef = useRef({}) - const lastCheckedArtifactIdRef = useRef(null) - - const historyBackLink = useMemo( - () => - `/projects/${params.projectName}/${DOCUMENTS_TAB}${getSavedSearchParams(location.search)}`, - [location.search, params.projectName] - ) - const filtersConfig = useMemo(() => getFiltersConfig(isAllVersions), [isAllVersions]) - const documentsFilters = useFiltersFromSearchParams(filtersConfig) - const pageData = useMemo(() => generatePageData(viewMode), [viewMode]) - const [refreshAfterDeleteCallback, refreshAfterDeleteTrigger] = useRefreshAfterDelete( - paginationConfigDocumentVersionsRef, - historyBackLink, - 'artifacts', - params.id && getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : DOCUMENTS_TAB, true), - isAllVersions - ) - const detailsFormInitialValues = useMemo( - () => ({ + const generateDetailsFormInitialValues = useCallback( + (selectedDocument, internal_labels) => ({ tag: selectedDocument?.tag ?? '', - labels: parseChipsData(selectedDocument?.labels ?? {}, frontendSpec.internal_labels) + labels: parseChipsData(selectedDocument?.labels ?? {}, internal_labels) }), - [frontendSpec.internal_labels, selectedDocument?.labels, selectedDocument?.tag] - ) - - const toggleConvertedYaml = useCallback( - data => { - return dispatch(toggleYaml(data)) - }, - [dispatch] - ) - - const fetchData = useCallback( - filters => { - abortControllerRef.current = new AbortController() - - const requestParams = { - format: 'minimal' - } - - if (isAllVersions) { - requestParams.name = params.documentName - setDocumentVersions(null) - } else { - if ( - filters[ITERATIONS_FILTER] !== SHOW_ITERATIONS || - filters[TAG_FILTER] === TAG_FILTER_ALL_ITEMS - ) { - requestParams['partition-by'] = 'project_and_name' - requestParams['partition-sort-by'] = 'updated' - } - - setDocuments(null) - } - - if (!isAllVersions && !isEmpty(paginationConfigDocumentsRef.current)) { - requestParams.page = paginationConfigDocumentsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigDocumentsRef.current[BE_PAGE_SIZE] - } - - if (isAllVersions && !isEmpty(paginationConfigDocumentVersionsRef.current)) { - requestParams.page = paginationConfigDocumentVersionsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigDocumentVersionsRef.current[BE_PAGE_SIZE] - } - - return dispatch( - fetchDocuments({ - project: params.projectName, - filters, - config: { - ui: { - controller: abortControllerRef.current, - setRequestErrorMessage - }, - params: requestParams - } - }) - ) - .unwrap() - .then(response => { - if (response?.artifacts) { - if (isAllVersions) { - paginationConfigDocumentVersionsRef.current.paginationResponse = response.pagination - setDocumentVersions(response.artifacts || []) - } else { - paginationConfigDocumentsRef.current.paginationResponse = response.pagination - setDocuments(response.artifacts || []) - } - } else { - if (isAllVersions) { - setDocumentVersions([]) - } else { - setDocuments([]) - } - } - - return response - }) - .catch(() => { - if (isAllVersions) { - setDocumentVersions([]) - } else { - setDocuments([]) - } - }) - }, - [dispatch, isAllVersions, params.documentName, params.projectName] - ) - - const fetchTags = useCallback(() => { - tagAbortControllerRef.current = new AbortController() - - return dispatch( - getFilterTagOptions({ - dispatch, - fetchTags: fetchArtifactTags, - project: params.projectName, - category: DOCUMENT_TYPE, - config: { - signal: tagAbortControllerRef.current.signal - } - }) - ) - }, [dispatch, params.projectName]) - - const refreshDocuments = useCallback( - filters => { - fetchTags() - setSelectedDocument({}) - - return fetchData(filters) - }, - [fetchData, fetchTags] - ) - - const handleAddTag = useCallback( - artifact => { - openPopUp(AddArtifactTagPopUp, { - artifact, - onAddTag: () => refreshDocuments(documentsFilters), - projectName: params.projectName - }) - }, - [params.projectName, refreshDocuments, documentsFilters] + [] ) - const showAllVersions = useCallback( - documentName => { - navigate( - `/projects/${params.projectName}/${DOCUMENTS_TAB}/${documentName}/${ALL_VERSIONS_PATH}?${transformSearchParams(window.location.search)}` - ) - }, - [navigate, params.projectName] - ) - - const actionsMenu = useMemo( - () => documentMin => - generateActionsMenu( - documentMin, - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshDocuments, - refreshAfterDeleteCallback, - documentsFilters, - selectedDocument, - showAllVersions, - isAllVersions - ), - [ - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshDocuments, - refreshAfterDeleteCallback, - documentsFilters, - selectedDocument, - showAllVersions, - isAllVersions - ] - ) - - const applyDetailsChanges = useCallback( - changes => { - return handleApplyDetailsChanges( - changes, - params.projectName, - selectedDocument, - setNotification, - dispatch - ) - }, - [dispatch, params.projectName, selectedDocument] - ) - - const applyDetailsChangesCallback = (changes, selectedItem) => { - if ('tag' in changes.data) { - if (isAllVersions) { - setDocumentVersions(null) - } else { - setDocuments(null) - } - - navigate( - `/projects/${params.projectName}/${DOCUMENTS_TAB}/${params.documentName}${isAllVersions ? `/${ALL_VERSIONS_PATH}` : ''}/${ - changes.data.tag.currentFieldValue ? `:${changes.data.tag.currentFieldValue}` : '' - }@${selectedItem.uid}/overview${window.location.search}`, - { replace: true } - ) - } - - refreshDocuments(documentsFilters) - } - - const [ - handleRefreshDocuments, - paginatedDocuments, - searchDocumentsParams, - setSearchDocumentsParams - ] = usePagination({ - hidden: isAllVersions, - content: documents ?? [], - refreshContent: refreshDocuments, - filters: documentsFilters, - paginationConfigRef: paginationConfigDocumentsRef, - resetPaginationTrigger: `${params.projectName}_${refreshAfterDeleteTrigger}` - }) - - const [ - handleRefreshDocumentVersions, - paginatedDocumentVersions, - searchDocumentVersionsParams, - setSearchDocumentVersionsParams - ] = usePagination({ - hidden: !isAllVersions, - content: documentVersions ?? [], - refreshContent: refreshDocuments, - filters: documentsFilters, - paginationConfigRef: paginationConfigDocumentVersionsRef, - resetPaginationTrigger: `${params.projectName}_${isAllVersions}` - }) - - const tableContent = useMemo(() => { - return (isAllVersions ? paginatedDocumentVersions : paginatedDocuments).map(contentItem => - createDocumentsRowData(contentItem, params.projectName, isAllVersions) - ) - }, [paginatedDocumentVersions, paginatedDocuments, isAllVersions, params.projectName]) - - const tableHeaders = useMemo(() => tableContent[0]?.content ?? [], [tableContent]) - - const getAndSetSelectedArtifact = useCallback(() => { - setFullSelectedArtifact( - DOCUMENTS_TAB, - dispatch, - navigate, - params.documentName, - setSelectedDocument, - params.projectName, - params.id, - isAllVersions - ) - }, [dispatch, isAllVersions, navigate, params.projectName, params.id, params.documentName]) - - useEffect(() => { - if (params.id && pageData.details.menu.length > 0) { - isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) - } - }, [navigate, location, pageData.details.menu, params.id, params.tab]) - - useEffect(() => { - return () => { - setDocuments(null) - setDocumentVersions(null) - } - }, [params.projectName]) - - useEffect(() => { - const tagAbortControllerCurrent = tagAbortControllerRef.current - - return () => { - dispatch(removeDocuments()) - setSelectedDocument({}) - abortControllerRef.current.abort(REQUEST_CANCELED) - tagAbortControllerCurrent.abort(REQUEST_CANCELED) - } - }, [params.projectName, dispatch, tagAbortControllerRef]) - - useEffect(() => { - dispatch(setFilters({ groupBy: GROUP_BY_NONE })) - }, [dispatch, params.projectName]) - - useEffect(() => { - checkForSelectedArtifact({ - artifactName: params.documentName, - artifacts: isAllVersions ? documentVersions : documents, - dispatch, - isAllVersions, - navigate, - paginatedArtifacts: isAllVersions ? paginatedDocumentVersions : paginatedDocuments, - paginationConfigRef: isAllVersions - ? paginationConfigDocumentVersionsRef - : paginationConfigDocumentsRef, - paramsId: params.id, - projectName: params.projectName, - searchParams: isAllVersions ? searchDocumentVersionsParams : searchDocumentsParams, - setSearchParams: isAllVersions ? setSearchDocumentVersionsParams : setSearchDocumentsParams, - setSelectedArtifact: setSelectedDocument, - setSelectedArtifactIsBeyondTheList, - lastCheckedArtifactIdRef, - tab: DOCUMENTS_TAB - }) - }, [ - dispatch, - documentVersions, - documents, - isAllVersions, - navigate, - paginatedDocumentVersions, - paginatedDocuments, - params.documentName, - params.id, - params.projectName, - searchDocumentVersionsParams, - searchDocumentsParams, - setSearchDocumentVersionsParams, - setSearchDocumentsParams - ]) - - useEffect(() => { - if (isEmpty(selectedDocument)) { - lastCheckedArtifactIdRef.current = null - } - }, [selectedDocument]) - return ( - ) } diff --git a/src/components/Documents/DocumentsView.jsx b/src/components/Documents/DocumentsView.jsx deleted file mode 100644 index f5112dbd4b..0000000000 --- a/src/components/Documents/DocumentsView.jsx +++ /dev/null @@ -1,192 +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. -*/ -import React from 'react' -import PropTypes from 'prop-types' -import { isEmpty } from 'lodash' - -import ActionBar from '../ActionBar/ActionBar' -import ArtifactsFilters from '../ArtifactsActionBar/ArtifactsFilters' -import ArtifactsTableRow from '../../elements/ArtifactsTableRow/ArtifactsTableRow' -import Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs' -import Details from '../Details/Details' -import HistoryBackLink from '../../common/HistoryBackLink/historyBackLink' -import Loader from '../../common/Loader/Loader' -import NoData from '../../common/NoData/NoData' -import Pagination from '../../common/Pagination/Pagination' -import Table from '../Table/Table' - -import { ACTIONS_MENU } from '../../types' -import { ALL_VERSIONS_PATH, DOCUMENTS_PAGE, DOCUMENTS_TAB, FULL_VIEW_MODE } from '../../constants' -import { getCloseDetailsLink } from '../../utils/link-helper.util' -import { getDefaultFirstHeader } from '../../utils/createArtifactsContent' -import { getNoDataMessage } from '../../utils/getNoDataMessage' - -const DocumentsView = React.forwardRef( - ( - { - actionsMenu, - applyDetailsChanges, - applyDetailsChangesCallback, - artifactsStore, - detailsFormInitialValues, - documents, - documentName, - filters, - filtersConfig, - filtersStore, - getAndSetSelectedArtifact, - handleRefreshDocuments, - historyBackLink, - isAllVersions, - pageData, - paginationConfigDocumentsRef, - requestErrorMessage, - selectedDocument, - setSearchDocumentsParams, - setSelectedDocument, - tableContent, - tableHeaders, - viewMode = '' - }, - { documentsRef } - ) => { - return ( - <> -
-
- -
-
- {artifactsStore.loading && } -
-
- {isAllVersions && ( - - )} - - - -
- {artifactsStore.loading ? null : tableContent.length === 0 && - isEmpty(selectedDocument) ? ( - - ) : ( - <> - {artifactsStore.documents.documentLoading && } - - getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : DOCUMENTS_TAB) - } - handleCancel={() => setSelectedDocument({})} - pageData={pageData} - selectedItem={selectedDocument} - tableClassName="documents-table" - tableHeaders={ - !isEmpty(tableHeaders) ? tableHeaders : getDefaultFirstHeader(isAllVersions) - } - viewMode={viewMode} - > - {tableContent.map((tableItem, index) => ( - - ))} -
- - - )} - {viewMode === FULL_VIEW_MODE && !isEmpty(selectedDocument) && ( -
- )} -
-
-
- - ) - } -) - -DocumentsView.displayName = 'DocumentsView' - -DocumentsView.propTypes = { - actionsMenu: ACTIONS_MENU.isRequired, - applyDetailsChanges: PropTypes.func.isRequired, - applyDetailsChangesCallback: PropTypes.func.isRequired, - artifactsStore: PropTypes.object.isRequired, - detailsFormInitialValues: PropTypes.object.isRequired, - documentName: PropTypes.string, - documents: PropTypes.arrayOf(PropTypes.object).isRequired, - filters: PropTypes.object.isRequired, - filtersConfig: PropTypes.object.isRequired, - filtersStore: PropTypes.object.isRequired, - getAndSetSelectedArtifact: PropTypes.func.isRequired, - handleRefreshDocuments: PropTypes.func.isRequired, - historyBackLink: PropTypes.string.isRequired, - isAllVersions: PropTypes.bool.isRequired, - pageData: PropTypes.object.isRequired, - paginationConfigDocumentsRef: PropTypes.object.isRequired, - requestErrorMessage: PropTypes.string.isRequired, - selectedDocument: PropTypes.object.isRequired, - setSearchDocumentsParams: PropTypes.func.isRequired, - setSelectedDocument: PropTypes.func.isRequired, - tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, - tableHeaders: PropTypes.arrayOf(PropTypes.object).isRequired, - viewMode: PropTypes.string -} - -export default DocumentsView diff --git a/src/components/Documents/documents.scss b/src/components/Documents/documents.scss deleted file mode 100644 index 3d8eab13d3..0000000000 --- a/src/components/Documents/documents.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; - -$documentsRowHeight: variables.$rowHeight; -$documentsHeaderRowHeight: variables.$headerRowHeight; -$documentsRowHeightExtended: variables.$rowHeightExtended; - -.documents-table { - @include mixins.rowsHeight( - $documentsHeaderRowHeight, - $documentsRowHeight, - $documentsRowHeightExtended - ); -} diff --git a/src/components/Documents/documents.util.jsx b/src/components/Documents/documents.util.jsx index 1df659af24..90e6705971 100644 --- a/src/components/Documents/documents.util.jsx +++ b/src/components/Documents/documents.util.jsx @@ -25,14 +25,7 @@ import { DOCUMENT_TYPE, DOCUMENTS_PAGE, DOCUMENTS_TAB, - FULL_VIEW_MODE, - ITERATIONS_FILTER, - LABELS_FILTER, - NAME_FILTER, - SHOW_ITERATIONS, - TAG_FILTER, - TAG_FILTER_ALL_ITEMS, - TAG_FILTER_LATEST + FULL_VIEW_MODE } from '../../constants' import { getIsTargetPathValid } from '../../utils/createArtifactsContent' import { applyTagChanges, chooseOrFetchArtifact } from '../../utils/artifacts.util' @@ -54,21 +47,6 @@ import Delete from 'igz-controls/images/delete.svg?react' import DownloadIcon from 'igz-controls/images/download.svg?react' import HistoryIcon from 'igz-controls/images/history.svg?react' -export const getFiltersConfig = isAllVersions => ({ - [NAME_FILTER]: { label: 'Name:', initialValue: '', hidden: isAllVersions }, - [TAG_FILTER]: { - label: 'Version tag:', - initialValue: isAllVersions ? TAG_FILTER_ALL_ITEMS : TAG_FILTER_LATEST, - isModal: true - }, - [LABELS_FILTER]: { label: 'Labels:', initialValue: '', isModal: true }, - [ITERATIONS_FILTER]: { - label: 'Show best iteration only:', - initialValue: isAllVersions ? '' : SHOW_ITERATIONS, - isModal: true - } -}) - export const detailsMenu = [ { label: 'overview', diff --git a/src/components/Files/Files.jsx b/src/components/Files/Files.jsx index 79b0893bc2..baedcd878d 100644 --- a/src/components/Files/Files.jsx +++ b/src/components/Files/Files.jsx @@ -17,447 +17,65 @@ 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, useState, useMemo, useRef } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useLocation, useNavigate, useParams } from 'react-router-dom' -import { isEmpty } from 'lodash' +import React, { useCallback } from 'react' import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' -import AddArtifactTagPopUp from '../../elements/AddArtifactTagPopUp/AddArtifactTagPopUp' -import FilesView from './FilesView' import RegisterArtifactModal from '../RegisterArtifactModal/RegisterArtifactModal' +import Artifacts from '../Artifacts/Artifacts' +import { ARTIFACT_OTHER_TYPE, ARTIFACT_TYPE, FILES_TAB } from '../../constants' import { - ALL_VERSIONS_PATH, - ARTIFACT_OTHER_TYPE, - ARTIFACT_TYPE, - BE_PAGE, - BE_PAGE_SIZE, - FILES_TAB, - GROUP_BY_NONE, - ITERATIONS_FILTER, - REQUEST_CANCELED, - SHOW_ITERATIONS, - TAG_FILTER, - TAG_FILTER_ALL_ITEMS -} from '../../constants' -import { - getFiltersConfig, generateActionsMenu, generatePageData, handleApplyDetailsChanges, registerArtifactTitle } from './files.util' import { createFilesRowData } from '../../utils/createArtifactsContent' -import { fetchArtifactTags, fetchFiles, removeFiles } from '../../reducers/artifactsReducer' -import { getFilterTagOptions, setFilters } from '../../reducers/filtersReducer' -import { getSavedSearchParams, transformSearchParams } from '../../utils/filter.util' -import { getViewMode } from '../../utils/helper' -import { getCloseDetailsLink, isDetailsTabExists } from '../../utils/link-helper.util' +import { fetchFiles, removeFiles } from '../../reducers/artifactsReducer' import { openPopUp } from 'igz-controls/utils/common.util' -import { checkForSelectedArtifact, setFullSelectedArtifact } from '../../utils/artifacts.util' -import { setNotification } from '../../reducers/notificationReducer' -import { toggleYaml } from '../../reducers/appReducer' -import { useFiltersFromSearchParams } from '../../hooks/useFiltersFromSearchParams.hook' -import { usePagination } from '../../hooks/usePagination.hook' -import { useRefreshAfterDelete } from '../../hooks/useRefreshAfterDelete.hook' - -import './files.scss' +import { PRIMARY_BUTTON } from 'igz-controls/constants' const Files = ({ isAllVersions = false }) => { - const [files, setFiles] = useState(null) - const [fileVersions, setFileVersions] = useState(null) - const [selectedFile, setSelectedFile] = useState({}) - const [requestErrorMessage, setRequestErrorMessage] = useState('') - const [isSelectedArtifactBeyondTheList, setSelectedArtifactIsBeyondTheList] = useState('') const artifactsStore = useSelector(store => store.artifactsStore) - const filtersStore = useSelector(store => store.filtersStore) - const frontendSpec = useSelector(store => store.appStore.frontendSpec) - const dispatch = useDispatch() - const location = useLocation() - const navigate = useNavigate() - const params = useParams() - const viewMode = getViewMode(window.location.search) - const paginationConfigFilesRef = useRef({}) - const paginationConfigFileVersionsRef = useRef({}) - const abortControllerRef = useRef(new AbortController()) - const tagAbortControllerRef = useRef(new AbortController()) - const filesRef = useRef(null) - const lastCheckedArtifactIdRef = useRef(null) - const historyBackLink = useMemo( - () => `/projects/${params.projectName}/files${getSavedSearchParams(location.search)}`, - [location.search, params.projectName] - ) - const filtersConfig = useMemo(() => getFiltersConfig(isAllVersions), [isAllVersions]) - const filesFilters = useFiltersFromSearchParams(filtersConfig) - const [refreshAfterDeleteCallback, refreshAfterDeleteTrigger] = useRefreshAfterDelete( - paginationConfigFileVersionsRef, - historyBackLink, - 'artifacts', - params.id && getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : FILES_TAB, true), - isAllVersions - ) - - const pageData = useMemo(() => generatePageData(viewMode), [viewMode]) - - const detailsFormInitialValues = useMemo( - () => ({ + const generateDetailsFormInitialValues = useCallback( + selectedFile => ({ tag: selectedFile.tag ?? '' }), - [selectedFile.tag] - ) - - const toggleConvertedYaml = useCallback( - data => { - return dispatch(toggleYaml(data)) - }, - [dispatch] - ) - - const fetchData = useCallback( - filters => { - abortControllerRef.current = new AbortController() - - const requestParams = { - format: 'minimal' - } - - if (isAllVersions) { - requestParams.name = params.fileName - setFileVersions(null) - } else { - if ( - filters[ITERATIONS_FILTER] !== SHOW_ITERATIONS || - filters[TAG_FILTER] === TAG_FILTER_ALL_ITEMS - ) { - requestParams['partition-by'] = 'project_and_name' - requestParams['partition-sort-by'] = 'updated' - } - - setFiles(null) - } - - if (!isAllVersions && !isEmpty(paginationConfigFilesRef.current)) { - requestParams.page = paginationConfigFilesRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigFilesRef.current[BE_PAGE_SIZE] - } - - if (isAllVersions && !isEmpty(paginationConfigFileVersionsRef.current)) { - requestParams.page = paginationConfigFileVersionsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigFileVersionsRef.current[BE_PAGE_SIZE] - } - - lastCheckedArtifactIdRef.current = null - - return dispatch( - fetchFiles({ - project: params.projectName, - filters, - config: { - ui: { - controller: abortControllerRef.current, - setRequestErrorMessage - }, - params: requestParams - } - }) - ) - .unwrap() - .then(response => { - if (response?.artifacts) { - if (isAllVersions) { - paginationConfigFileVersionsRef.current.paginationResponse = response.pagination - setFileVersions(response.artifacts || []) - } else { - paginationConfigFilesRef.current.paginationResponse = response.pagination - setFiles(response.artifacts || []) - } - } else { - if (isAllVersions) { - setFileVersions([]) - } else { - setFiles([]) - } - } - - return response - }) - .catch(() => { - if (isAllVersions) { - setFileVersions([]) - } else { - setFiles([]) - } - }) - }, - [dispatch, isAllVersions, params.fileName, params.projectName] - ) - - const fetchTags = useCallback(() => { - tagAbortControllerRef.current = new AbortController() - - return dispatch( - getFilterTagOptions({ - dispatch, - fetchTags: fetchArtifactTags, - project: params.projectName, - category: ARTIFACT_OTHER_TYPE, - config: { - signal: tagAbortControllerRef.current.signal - } - }) - ) - }, [dispatch, params.projectName]) - - const refreshFiles = useCallback( - filters => { - fetchTags() - setSelectedFile({}) - - return fetchData(filters) - }, - [fetchData, fetchTags] + [] ) - const handleAddTag = useCallback( - artifact => { - openPopUp(AddArtifactTagPopUp, { - artifact, - onAddTag: () => refreshFiles(filesFilters), - projectName: params.projectName - }) - }, - [params.projectName, refreshFiles, filesFilters] - ) - - const showAllVersions = useCallback( - filetName => { - navigate( - `/projects/${params.projectName}/files/${filetName}/${ALL_VERSIONS_PATH}?${transformSearchParams(window.location.search)}` - ) - }, - [navigate, params.projectName] - ) - - const actionsMenu = useMemo( - () => fileMin => - generateActionsMenu( - fileMin, - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshFiles, - refreshAfterDeleteCallback, - filesFilters, - selectedFile, - showAllVersions, - isAllVersions - ), - [ - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshFiles, - refreshAfterDeleteCallback, - filesFilters, - selectedFile, - showAllVersions, - isAllVersions - ] - ) - - const applyDetailsChanges = useCallback( - changes => { - return handleApplyDetailsChanges( - changes, - params.projectName, - selectedFile, - setNotification, - dispatch - ) - }, - [dispatch, params.projectName, selectedFile] - ) - - const applyDetailsChangesCallback = (changes, selectedItem) => { - if ('tag' in changes.data) { - if (isAllVersions) { - setFileVersions(null) - } else { - setFiles(null) - } - - navigate( - `/projects/${params.projectName}/files/${params.fileName}${isAllVersions ? `/${ALL_VERSIONS_PATH}` : ''}/${ - changes.data.tag.currentFieldValue ? `:${changes.data.tag.currentFieldValue}` : '' - }@${selectedItem.uid}/overview${window.location.search}`, - { replace: true } - ) - } - - refreshFiles(filesFilters) - } - - useEffect(() => { - if (params.id && pageData.details.menu.length > 0) { - isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) - } - }, [navigate, location, pageData.details.menu, params.id, params.tab]) - - useEffect(() => { - return () => { - setFiles(null) - setFileVersions(null) - } - }, [params.projectName]) - - useEffect(() => { - const tagAbortControllerCurrent = tagAbortControllerRef.current - - return () => { - dispatch(removeFiles()) - setSelectedFile({}) - abortControllerRef.current.abort(REQUEST_CANCELED) - tagAbortControllerCurrent.abort(REQUEST_CANCELED) - } - }, [params.projectName, dispatch, tagAbortControllerRef]) - - useEffect(() => { - dispatch(setFilters({ groupBy: GROUP_BY_NONE })) - }, [dispatch, params.projectName]) - - const handleRegisterArtifact = useCallback(() => { + const handleRegisterArtifact = useCallback((params, refreshFiles, filesFilters) => { openPopUp(RegisterArtifactModal, { artifactKind: ARTIFACT_TYPE, params, refresh: () => refreshFiles(filesFilters), title: registerArtifactTitle }) - }, [params, refreshFiles, filesFilters]) - - const [handleRefreshFiles, paginatedFiles, searchFilesParams, setSearchFilesParams] = - usePagination({ - hidden: isAllVersions, - content: files ?? [], - refreshContent: refreshFiles, - filters: filesFilters, - paginationConfigRef: paginationConfigFilesRef, - resetPaginationTrigger: `${params.projectName}_${refreshAfterDeleteTrigger}` - }) - const [ - handleRefreshFileVersions, - paginatedFileVersions, - searchFileVersionsParams, - setSearchFileVersionsParams - ] = usePagination({ - hidden: !isAllVersions, - content: fileVersions ?? [], - refreshContent: refreshFiles, - filters: filesFilters, - paginationConfigRef: paginationConfigFileVersionsRef, - resetPaginationTrigger: `${params.projectName}_${isAllVersions}` - }) - - const tableContent = useMemo(() => { - return (isAllVersions ? paginatedFileVersions : paginatedFiles).map(contentItem => - createFilesRowData(contentItem, params.projectName, isAllVersions) - ) - }, [paginatedFileVersions, paginatedFiles, isAllVersions, params.projectName]) - - const tableHeaders = useMemo(() => tableContent[0]?.content ?? [], [tableContent]) - - useEffect(() => { - checkForSelectedArtifact({ - artifactName: params.fileName, - artifacts: isAllVersions ? fileVersions : files, - dispatch, - isAllVersions, - navigate, - paginatedArtifacts: isAllVersions ? paginatedFileVersions : paginatedFiles, - paginationConfigRef: isAllVersions - ? paginationConfigFileVersionsRef - : paginationConfigFilesRef, - paramsId: params.id, - projectName: params.projectName, - searchParams: isAllVersions ? searchFileVersionsParams : searchFilesParams, - setSearchParams: isAllVersions ? setSearchFileVersionsParams : setSearchFilesParams, - setSelectedArtifact: setSelectedFile, - setSelectedArtifactIsBeyondTheList, - lastCheckedArtifactIdRef, - tab: FILES_TAB - }) - }, [ - dispatch, - fileVersions, - files, - isAllVersions, - navigate, - paginatedFileVersions, - paginatedFiles, - params.fileName, - params.id, - params.projectName, - searchFileVersionsParams, - searchFilesParams, - setSearchFileVersionsParams, - setSearchFilesParams - ]) - - useEffect(() => { - if (isEmpty(selectedFile)) { - lastCheckedArtifactIdRef.current = null - } - }, [selectedFile]) - - const getAndSetSelectedArtifact = useCallback(() => { - setFullSelectedArtifact( - FILES_TAB, - dispatch, - navigate, - params.fileName, - setSelectedFile, - params.projectName, - params.id, - isAllVersions - ) - }, [dispatch, isAllVersions, navigate, params.projectName, params.id, params.fileName]) + }, []) return ( - ) } diff --git a/src/components/Files/FilesView.jsx b/src/components/Files/FilesView.jsx deleted file mode 100644 index f0af15737f..0000000000 --- a/src/components/Files/FilesView.jsx +++ /dev/null @@ -1,206 +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. -*/ -import React from 'react' -import PropTypes from 'prop-types' -import { isEmpty } from 'lodash' - -import ActionBar from '../ActionBar/ActionBar' -import ArtifactsFilters from '../ArtifactsActionBar/ArtifactsFilters' -import ArtifactsTableRow from '../../elements/ArtifactsTableRow/ArtifactsTableRow' -import Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs' -import Details from '../Details/Details' -import HistoryBackLink from '../../common/HistoryBackLink/historyBackLink' -import Loader from '../../common/Loader/Loader' -import NoData from '../../common/NoData/NoData' -import Pagination from '../../common/Pagination/Pagination' -import PreviewModal from '../../elements/PreviewModal/PreviewModal' -import Table from '../Table/Table' - -import { FILES_PAGE, FULL_VIEW_MODE, ALL_VERSIONS_PATH, FILES_TAB } from '../../constants' -import { ACTIONS_MENU } from '../../types' -import { PRIMARY_BUTTON } from 'igz-controls/constants' -import { getCloseDetailsLink } from '../../utils/link-helper.util' -import { getNoDataMessage } from '../../utils/getNoDataMessage' -import { registerArtifactTitle } from './files.util' -import { getDefaultFirstHeader } from '../../utils/createArtifactsContent' - -const FilesView = React.forwardRef( - ( - { - actionsMenu, - applyDetailsChanges, - applyDetailsChangesCallback, - artifactsStore, - detailsFormInitialValues, - fileName, - files, - filters, - filtersConfig, - filtersStore, - getAndSetSelectedArtifact, - handleRefreshFiles, - handleRegisterArtifact, - historyBackLink, - isAllVersions, - pageData, - paginationConfigFilesRef, - requestErrorMessage, - selectedFile, - setSearchFilesParams, - setSelectedFile, - tableContent, - tableHeaders, - viewMode = '' - }, - { filesRef } - ) => { - return ( - <> -
-
- -
-
- {artifactsStore.loading && } -
-
- {isAllVersions && } - - - -
- {artifactsStore.loading ? null : tableContent.length === 0 && - isEmpty(selectedFile) ? ( - - ) : ( - <> - {artifactsStore.files.fileLoading && } - - getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : FILES_TAB) - } - handleCancel={() => setSelectedFile({})} - pageData={pageData} - selectedItem={selectedFile} - tableClassName="files-table" - tableHeaders={ - !isEmpty(tableHeaders) ? tableHeaders : getDefaultFirstHeader(isAllVersions) - } - viewMode={viewMode} - > - {tableContent.map((tableItem, index) => ( - - ))} -
- - - )} - {viewMode === FULL_VIEW_MODE && !isEmpty(selectedFile) && ( -
- )} -
-
-
- {artifactsStore?.preview?.isPreview && ( - - )} - - ) - } -) - -FilesView.displayName = 'FilesView' - -FilesView.propTypes = { - actionsMenu: ACTIONS_MENU.isRequired, - applyDetailsChanges: PropTypes.func.isRequired, - applyDetailsChangesCallback: PropTypes.func.isRequired, - artifactsStore: PropTypes.object.isRequired, - detailsFormInitialValues: PropTypes.object.isRequired, - fileName: PropTypes.string, - files: PropTypes.arrayOf(PropTypes.object).isRequired, - filters: PropTypes.object.isRequired, - filtersConfig: PropTypes.object.isRequired, - filtersStore: PropTypes.object.isRequired, - getAndSetSelectedArtifact: PropTypes.func.isRequired, - handleRefreshFiles: PropTypes.func.isRequired, - handleRegisterArtifact: PropTypes.func.isRequired, - historyBackLink: PropTypes.string.isRequired, - isAllVersions: PropTypes.bool.isRequired, - pageData: PropTypes.object.isRequired, - paginationConfigFilesRef: PropTypes.object.isRequired, - requestErrorMessage: PropTypes.string.isRequired, - selectedFile: PropTypes.object.isRequired, - setSearchFilesParams: PropTypes.func.isRequired, - setSelectedFile: PropTypes.func.isRequired, - tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, - tableHeaders: PropTypes.arrayOf(PropTypes.object).isRequired, - viewMode: PropTypes.string -} - -export default FilesView diff --git a/src/components/Files/files.scss b/src/components/Files/files.scss deleted file mode 100644 index ce5afe33aa..0000000000 --- a/src/components/Files/files.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; - -$filesRowHeight: variables.$rowHeight; -$filesHeaderRowHeight: variables.$headerRowHeight; -$filesRowHeightExtended: variables.$rowHeightExtended; - -.files-table { - @include mixins.rowsHeight($filesHeaderRowHeight, $filesRowHeight, $filesRowHeightExtended); -} diff --git a/src/components/Files/files.util.jsx b/src/components/Files/files.util.jsx index c313663af7..37d8fc5393 100644 --- a/src/components/Files/files.util.jsx +++ b/src/components/Files/files.util.jsx @@ -54,21 +54,6 @@ import Delete from 'igz-controls/images/delete.svg?react' import DownloadIcon from 'igz-controls/images/download.svg?react' import HistoryIcon from 'igz-controls/images/history.svg?react' -export const getFiltersConfig = isAllVersions => ({ - [NAME_FILTER]: { label: 'Name:', initialValue: '', hidden: isAllVersions }, - [TAG_FILTER]: { - label: 'Version tag:', - initialValue: isAllVersions ? TAG_FILTER_ALL_ITEMS : TAG_FILTER_LATEST, - isModal: true - }, - [LABELS_FILTER]: { label: 'Labels:', initialValue: '', isModal: true }, - [ITERATIONS_FILTER]: { - label: 'Show best iteration only:', - initialValue: isAllVersions ? '' : SHOW_ITERATIONS, - isModal: true - } -}) - export const pageDataInitialState = { details: { menu: [], diff --git a/src/components/LLMPrompts/LLMPrompts.jsx b/src/components/LLMPrompts/LLMPrompts.jsx new file mode 100644 index 0000000000..0f2d07e8fe --- /dev/null +++ b/src/components/LLMPrompts/LLMPrompts.jsx @@ -0,0 +1,63 @@ +/* +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. +*/ + +import React, { useCallback } from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' + +import Artifacts from '../Artifacts/Artifacts' + +import { LLM_PROMPT_TYPE, LLM_PROMPTS_TAB } from '../../constants' +import { createLLMPromptsRowData } from '../../utils/createArtifactsContent' +import { fetchLLMPrompts, removeDataSets } from '../../reducers/artifactsReducer' +import { generateActionsMenu, generatePageData, handleApplyDetailsChanges } from './llmPrompts.util' + +const LLMPrompts = ({ isAllVersions }) => { + const artifactsStore = useSelector(store => store.artifactsStore) + + const generateDetailsFormInitialValues = useCallback( + selectedLLMPrompt => ({ + tag: selectedLLMPrompt.tag ?? '' + }), + [] + ) + + return ( + + ) +} + +LLMPrompts.propTypes = { + isAllVersions: PropTypes.bool.isRequired +} + +export default LLMPrompts diff --git a/src/components/LLMPrompts/llmPrompts.util.jsx b/src/components/LLMPrompts/llmPrompts.util.jsx new file mode 100644 index 0000000000..0f7d1095cf --- /dev/null +++ b/src/components/LLMPrompts/llmPrompts.util.jsx @@ -0,0 +1,259 @@ +/* +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. +*/ +import React from 'react' + +import { + ARTIFACT_MAX_DOWNLOAD_SIZE, + FULL_VIEW_MODE, + LLM_PROMPT_TYPE, + LLM_PROMPTS_PAGE, + LLM_PROMPTS_TAB +} from '../../constants' +import { getIsTargetPathValid } from '../../utils/createArtifactsContent' +import { applyTagChanges, chooseOrFetchArtifact } from '../../utils/artifacts.util' +import { setDownloadItem, setShowDownloadsList } from '../../reducers/downloadReducer' +import { copyToClipboard } from '../../utils/copyToClipboard' +import { generateUri } from '../../utils/resources' +import { openDeleteConfirmPopUp, openPopUp } from 'igz-controls/utils/common.util' +import DeleteArtifactPopUp from '../../elements/DeleteArtifactPopUp/DeleteArtifactPopUp' +import { handleDeleteArtifact } from '../../utils/handleDeleteArtifact' +import { showArtifactsPreview } from '../../reducers/artifactsReducer' + +import TagIcon from 'igz-controls/images/tag-icon.svg?react' +import YamlIcon from 'igz-controls/images/yaml.svg?react' +import ArtifactView from 'igz-controls/images/eye-icon.svg?react' +import Copy from 'igz-controls/images/copy-to-clipboard-icon.svg?react' +import Delete from 'igz-controls/images/delete.svg?react' +import DownloadIcon from 'igz-controls/images/download.svg?react' +import HistoryIcon from 'igz-controls/images/history.svg?react' + +export const detailsMenu = [ + { + label: 'overview', + id: 'overview' + }, + { + label: 'prompt-template', + id: 'Prompt template' + }, + { + label: 'generation-configuration', + id: 'Generation configuration' + } +] + +export const infoHeaders = [ + { label: 'Key', id: 'db_key' }, + { + label: 'Hash', + id: 'hash', + tip: 'Represents hash of the data. when the data changes the hash would change' + }, + { label: 'Version tag', id: 'tag' }, + { label: 'Original source', id: 'original_source' }, + { label: 'Iter', id: 'iter' }, + { label: 'URI', id: 'target_uri' }, + { label: 'Path', id: 'target_path' }, + { label: 'UID', id: 'uid' }, + { label: 'Updated', id: 'updated' }, + { label: 'Labels', id: 'labels' } +] + +export const generatePageData = viewMode => { + return { + page: LLM_PROMPTS_PAGE, + details: { + type: LLM_PROMPTS_TAB, + menu: detailsMenu, + infoHeaders, + hideBackBtn: viewMode === FULL_VIEW_MODE, + withToggleViewBtn: true + } + } +} + +export const handleApplyDetailsChanges = ( + changes, + projectName, + selectedItem, + setNotification, + dispatch +) => { + return applyTagChanges(changes, selectedItem, projectName, dispatch, setNotification) +} + +export const generateActionsMenu = ( + llmPromptMin, + frontendSpec, + dispatch, + toggleConvertedYaml, + handleAddTag, + projectName, + refreshArtifacts, + refreshAfterDeleteCallback, + llmPromptsFilters, + selectedLLMPrompt, + showAllVersions, + isAllVersions, + isDetailsPopUp = false +) => { + const isTargetPathValid = getIsTargetPathValid(llmPromptMin ?? {}, frontendSpec) + const datasetDataCouldBeDeleted = + llmPromptMin?.target_path?.endsWith('.pq') || llmPromptMin?.target_path?.endsWith('.parquet') + + const getFullLLMPrompt = llmPromptMin => { + return chooseOrFetchArtifact(dispatch, LLM_PROMPTS_TAB, selectedLLMPrompt, llmPromptMin) + } + + return [ + [ + { + label: 'Add a tag', + hidden: isDetailsPopUp, + icon: , + onClick: handleAddTag, + allowLeaveWarning: true + }, + { + label: 'Download', + disabled: + !isTargetPathValid || + llmPromptMin.size > + (frontendSpec.artifact_limits.max_download_size ?? ARTIFACT_MAX_DOWNLOAD_SIZE), + icon: , + onClick: llmPromptMin => { + getFullLLMPrompt(llmPromptMin).then(llmPrompt => { + if (llmPrompt) { + const downloadPath = `${llmPrompt?.target_path}${llmPrompt?.model_file || ''}` + dispatch( + setDownloadItem({ + path: downloadPath, + user: llmPrompt.producer?.owner, + id: downloadPath, + artifactLimits: frontendSpec?.artifact_limits, + fileSize: llmPrompt.size, + projectName + }) + ) + dispatch(setShowDownloadsList(true)) + } + }) + } + }, + { + label: 'Copy URI', + icon: , + onClick: llmPromptMin => + copyToClipboard(generateUri(llmPromptMin, LLM_PROMPTS_TAB), dispatch) + }, + { + label: 'View YAML', + icon: , + onClick: llmPromptMin => getFullLLMPrompt(llmPromptMin).then(toggleConvertedYaml) + }, + { + label: 'Delete', + icon: , + hidden: isDetailsPopUp, + className: 'danger', + onClick: () => + datasetDataCouldBeDeleted + ? openPopUp(DeleteArtifactPopUp, { + artifact: llmPromptMin, + artifactType: LLM_PROMPT_TYPE, + category: LLM_PROMPT_TYPE, + filters: llmPromptsFilters, + refreshArtifacts, + refreshAfterDeleteCallback + }) + : openDeleteConfirmPopUp( + 'Delete LLM Prompt?', + `Do you want to delete the LLM Prompt "${llmPromptMin.db_key}"? Deleted LLM Prompt can not be restored.`, + () => { + handleDeleteArtifact( + dispatch, + projectName, + llmPromptMin.db_key, + llmPromptMin.uid, + refreshArtifacts, + refreshAfterDeleteCallback, + llmPromptsFilters, + LLM_PROMPT_TYPE + ) + } + ), + allowLeaveWarning: true + }, + { + label: 'Delete all versions', + icon: , + hidden: isDetailsPopUp || isAllVersions, + className: 'danger', + onClick: () => + openDeleteConfirmPopUp( + 'Delete LLM Prompt?', + `Do you want to delete all versions of the LLM Prompt "${llmPromptMin.db_key}"? Deleted LLM prompt can not be restored.`, + () => { + handleDeleteArtifact( + dispatch, + projectName, + llmPromptMin.db_key, + llmPromptMin.uid, + refreshArtifacts, + refreshAfterDeleteCallback, + llmPromptsFilters, + LLM_PROMPT_TYPE, + LLM_PROMPT_TYPE, + true + ) + } + ), + allowLeaveWarning: true + } + ], + [ + { + id: 'show-all-versions', + label: 'Show all versions', + icon: , + onClick: () => showAllVersions(llmPromptMin.db_key), + hidden: isAllVersions + }, + { + label: 'Preview', + id: 'llm-prompt-preview', + disabled: !isTargetPathValid, + icon: , + onClick: llmPromptMin => { + getFullLLMPrompt(llmPromptMin).then(llmPrompt => { + if (llmPrompt) { + dispatch( + showArtifactsPreview({ + isPreview: true, + selectedItem: llmPrompt + }) + ) + } + }) + } + } + ] + ] +} diff --git a/src/components/ModelsPage/Models/Models.jsx b/src/components/ModelsPage/Models/Models.jsx index ba232c81e2..c125e3a7ff 100644 --- a/src/components/ModelsPage/Models/Models.jsx +++ b/src/components/ModelsPage/Models/Models.jsx @@ -17,571 +17,92 @@ 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, useMemo, useState, useRef } from 'react' +import React, { useCallback } from 'react' import PropTypes from 'prop-types' -import { useDispatch, useSelector } from 'react-redux' -import { chain, isEmpty, isNil } from 'lodash' -import { useLocation, useNavigate, useParams } from 'react-router-dom' +import { useSelector } from 'react-redux' -import AddArtifactTagPopUp from '../../../elements/AddArtifactTagPopUp/AddArtifactTagPopUp' -import DeployModelPopUp from '../../../elements/DeployModelPopUp/DeployModelPopUp' -import ModelsView from './ModelsView' import RegisterModelModal from '../../../elements/RegisterModelModal/RegisterModelModal' import JobWizard from '../../JobWizard/JobWizard' +import Artifacts from '../../Artifacts/Artifacts' +import ModelsPageTabs from '../ModelsPageTabs/ModelsPageTabs' +import { fetchModels, removeModels } from '../../../reducers/artifactsReducer' +import { MODELS_PAGE, MODELS_TAB, MODEL_TYPE } from '../../../constants' import { - fetchArtifactsFunctions, - fetchArtifactTags, - fetchModels, - removeModels -} from '../../../reducers/artifactsReducer' -import { - MODELS_PAGE, - MODELS_TAB, - GROUP_BY_NONE, - REQUEST_CANCELED, - MODEL_TYPE, - FUNCTION_TYPE_SERVING, - ALL_VERSIONS_PATH, - BE_PAGE, - BE_PAGE_SIZE, - ITERATIONS_FILTER, - SHOW_ITERATIONS, - TAG_FILTER, - TAG_FILTER_ALL_ITEMS -} from '../../../constants' -import { - getFiltersConfig, generateActionsMenu, generatePageData, - getFeatureVectorData, handleApplyDetailsChanges, handleDeployModelFailure } from './models.util' -import { checkForSelectedArtifact, setFullSelectedArtifact } from '../../../utils/artifacts.util' import { createModelsRowData } from '../../../utils/createArtifactsContent' -import { fetchModelFeatureVector } from '../../../reducers/detailsReducer' -import { getCloseDetailsLink, isDetailsTabExists } from '../../../utils/link-helper.util' -import { getFilterTagOptions, setFilters } from '../../../reducers/filtersReducer' -import { getSavedSearchParams, transformSearchParams } from '../../../utils/filter.util' -import { getViewMode } from '../../../utils/helper' import { openPopUp } from 'igz-controls/utils/common.util' import { parseChipsData } from '../../../utils/convertChipsData' -import { setNotification } from '../../../reducers/notificationReducer' -import { useFiltersFromSearchParams } from '../../../hooks/useFiltersFromSearchParams.hook' import { useMode } from '../../../hooks/mode.hook' -import { useModelsPage } from '../ModelsPage.context' -import { usePagination } from '../../../hooks/usePagination.hook' -import { useRefreshAfterDelete } from '../../../hooks/useRefreshAfterDelete.hook' - -import './models.scss' +import { PRIMARY_BUTTON, TERTIARY_BUTTON } from 'igz-controls/constants' const Models = ({ isAllVersions }) => { - const [models, setModels] = useState(null) - const [modelVersions, setModelVersions] = useState(null) - const [requestErrorMessage, setRequestErrorMessage] = useState('') - const [selectedModel, setSelectedModel] = useState({}) - const [isSelectedArtifactBeyondTheList, setSelectedArtifactIsBeyondTheList] = useState(false) - //temporarily commented till ML-5606 will be done - // const [metricsCounter, setMetricsCounter] = useState(0) - // const [dataIsLoaded, setDataIsLoaded] = useState(false) - // const [tableHeaders, setTableHeaders] = useState([]) - const artifactsStore = useSelector(store => store.artifactsStore) - const detailsStore = useSelector(store => store.detailsStore) - const filtersStore = useSelector(store => store.filtersStore) - const frontendSpec = useSelector(store => store.appStore.frontendSpec) - const dispatch = useDispatch() - const location = useLocation() - const navigate = useNavigate() - const params = useParams() - const viewMode = getViewMode(window.location.search) - const { toggleConvertedYaml } = useModelsPage() - const paginationConfigModelsRef = useRef({}) - const paginationConfigModelVersionsRef = useRef({}) - const abortControllerRef = useRef(new AbortController()) - const tagAbortControllerRef = useRef(new AbortController()) - const modelsRef = useRef(null) - const lastCheckedArtifactIdRef = useRef(null) - - const historyBackLink = useMemo( - () => `/projects/${params.projectName}/models/models${getSavedSearchParams(location.search)}`, - [location.search, params.projectName] - ) - const filtersConfig = useMemo(() => getFiltersConfig(isAllVersions), [isAllVersions]) - const modelsFilters = useFiltersFromSearchParams(filtersConfig) - const [refreshAfterDeleteCallback, refreshAfterDeleteTrigger] = useRefreshAfterDelete( - paginationConfigModelVersionsRef, - historyBackLink, - 'artifacts', - params.id && getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : MODELS_TAB, true), - isAllVersions - ) - const { isDemoMode } = useMode() + const artifactsStore = useSelector(store => store.artifactsStore) - const pageData = useMemo( - () => generatePageData(selectedModel, viewMode), - [selectedModel, viewMode] - ) - - const detailsFormInitialValues = useMemo( - () => ({ + const generateDetailsFormInitialValues = useCallback( + (selectedModel, internal_labels) => ({ tag: selectedModel.tag ?? '', - labels: parseChipsData(selectedModel.labels ?? {}, frontendSpec.internal_labels) + labels: parseChipsData(selectedModel.labels ?? {}, internal_labels) }), - [frontendSpec.internal_labels, selectedModel.labels, selectedModel.tag] + [] ) - const fetchData = useCallback( - async filters => { - abortControllerRef.current = new AbortController() - - const requestParams = { - format: 'minimal' - } - - if (isAllVersions) { - requestParams.name = params.modelName - setModelVersions(null) - } else { - if ( - filters[ITERATIONS_FILTER] !== SHOW_ITERATIONS || - filters[TAG_FILTER] === TAG_FILTER_ALL_ITEMS - ) { - requestParams['partition-by'] = 'project_and_name' - requestParams['partition-sort-by'] = 'updated' - } - - setModels(null) - } - - if (!isAllVersions && !isEmpty(paginationConfigModelsRef.current)) { - requestParams.page = paginationConfigModelsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigModelsRef.current[BE_PAGE_SIZE] - } - - if (isAllVersions && !isEmpty(paginationConfigModelVersionsRef.current)) { - requestParams.page = paginationConfigModelVersionsRef.current[BE_PAGE] - requestParams['page-size'] = paginationConfigModelVersionsRef.current[BE_PAGE_SIZE] - } - - lastCheckedArtifactIdRef.current = null - - return dispatch( - fetchModels({ - project: params.projectName, - filters, - config: { - ui: { - controller: abortControllerRef.current, - setRequestErrorMessage - }, - params: requestParams - } - }) - ) - .unwrap() - .then(response => { - if (response?.artifacts) { - if (isAllVersions) { - paginationConfigModelVersionsRef.current.paginationResponse = response.pagination - setModelVersions(response.artifacts || []) - } else { - paginationConfigModelsRef.current.paginationResponse = response.pagination - setModels(response.artifacts || []) - } - } else { - if (isAllVersions) { - setModelVersions([]) - } else { - setModels([]) - } - } - - return response - }) - .catch(() => { - if (isAllVersions) { - setModelVersions([]) - } else { - setModels([]) - } - }) - }, - [dispatch, isAllVersions, params.modelName, params.projectName] - ) - - const fetchTags = useCallback(() => { - tagAbortControllerRef.current = new AbortController() - - return dispatch( - getFilterTagOptions({ - dispatch, - fetchTags: fetchArtifactTags, - project: params.projectName, - category: MODEL_TYPE, - config: { - signal: tagAbortControllerRef.current.signal - } - }) - ) - }, [dispatch, params.projectName]) - - const handleDeployModel = useCallback( - model => { - abortControllerRef.current = new AbortController() - - dispatch( - fetchArtifactsFunctions({ - project: model.project, - filters: {}, - config: { - signal: abortControllerRef.current.signal, - params: { format: 'minimal', kind: 'serving' } - } - }) - ) - .unwrap() - .then(functions => { - if (!isNil(functions)) { - const functionOptions = chain(functions) - .filter(func => func.type === FUNCTION_TYPE_SERVING && func.graph?.kind === 'router') - .uniqBy('name') - .map(func => ({ label: func.name, id: func.name })) - .value() - - if (functionOptions.length > 0) { - openPopUp(DeployModelPopUp, { - model, - functionList: functions, - functionOptionList: functionOptions - }) - } else { - handleDeployModelFailure(params.projectName, model.db_key) - } - } - }) - }, - [dispatch, params.projectName] - ) - - const refreshModels = useCallback( - filters => { - fetchTags() - setSelectedModel({}) - //temporarily commented till ML-5606 will be done - // setTableHeaders([]) - // setDataIsLoaded(false) - - return fetchData(filters) - }, - [fetchData, fetchTags] - ) - - const handleAddTag = useCallback( - artifact => { - openPopUp(AddArtifactTagPopUp, { - artifact, - onAddTag: () => refreshModels(modelsFilters), - projectName: params.projectName - }) - }, - [params.projectName, refreshModels, modelsFilters] - ) - - const showAllVersions = useCallback( - modelName => { - navigate( - `/projects/${params.projectName}/${MODELS_PAGE.toLowerCase()}/${MODELS_TAB}/${modelName}/${ALL_VERSIONS_PATH}?${transformSearchParams( - window.location.search - )}` - ) - }, - [navigate, params.projectName] - ) - - const actionsMenu = useMemo( - () => modelMin => - generateActionsMenu( - modelMin, - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshModels, - refreshAfterDeleteCallback, - modelsFilters, - selectedModel, - showAllVersions, - isAllVersions, - false, - handleDeployModel - ), - [ - frontendSpec, - dispatch, - toggleConvertedYaml, - handleAddTag, - params.projectName, - refreshModels, - refreshAfterDeleteCallback, - modelsFilters, - selectedModel, - handleDeployModel, - showAllVersions, - isAllVersions - ] - ) - - const applyDetailsChanges = useCallback( - changes => { - return handleApplyDetailsChanges( - changes, - params.projectName, - selectedModel, - setNotification, - dispatch - ) - }, - [dispatch, params.projectName, selectedModel] - ) - - const applyDetailsChangesCallback = (changes, selectedItem) => { - if ('tag' in changes.data) { - if (isAllVersions) { - setModelVersions(null) - } else { - setModels(null) - } - - navigate( - `/projects/${params.projectName}/${MODELS_PAGE.toLowerCase()}/${MODELS_TAB}/${params.modelName}${isAllVersions ? `/${ALL_VERSIONS_PATH}` : ''}/${ - changes.data.tag.currentFieldValue ? `:${changes.data.tag.currentFieldValue}` : '' - }@${selectedItem.uid}/overview${window.location.search}`, - { replace: true } - ) - } - - refreshModels(modelsFilters) - } - - useEffect(() => { - if (params.id && pageData.details.menu.length > 0) { - isDetailsTabExists(params.tab, pageData.details.menu, navigate, location) - } - }, [navigate, location, pageData.details.menu, params.tab, params.id]) - - useEffect(() => { - return () => { - setModels(null) - setModelVersions(null) - } - }, [params.projectName]) - - useEffect(() => { - const tagAbortControllerCurrent = tagAbortControllerRef.current - - return () => { - dispatch(removeModels()) - setSelectedModel({}) - //temporarily commented till ML-5606 will be done - // setTableHeaders([]) - // setDataIsLoaded(false) - abortControllerRef.current.abort(REQUEST_CANCELED) - tagAbortControllerCurrent.abort(REQUEST_CANCELED) - } - }, [dispatch, params.projectName, tagAbortControllerRef]) - - useEffect(() => { - dispatch(setFilters({ groupBy: GROUP_BY_NONE })) - }, [dispatch, params.projectName]) - - useEffect(() => { - if ( - selectedModel.feature_vector && - !detailsStore.error && - isEmpty(detailsStore.modelFeatureVectorData) - ) { - const { name, tag } = getFeatureVectorData(selectedModel.feature_vector) - dispatch(fetchModelFeatureVector({ project: params.projectName, name, reference: tag })) - } - }, [ - detailsStore.error, - detailsStore.modelFeatureVectorData, - dispatch, - params.projectName, - selectedModel.feature_vector - ]) - - //temporarily commented till ML-5606 will be done - // useEffect(() => { - // if (tableContent?.[0]?.content?.length > 0 && tableHeaders.length === 0) { - // setTableHeaders(tableContent?.[0]?.content) - // } - // }, [tableContent, tableHeaders.length]) - - // useEffect(() => { - // if (models.length > 0 && tableHeaders.length > 0 && !dataIsLoaded) { - // const newTableHeaders = [] - // const maxMetricsModel = models.reduce((modelWithMaxMetrics, model) => { - // if ( - // model.metrics && - // Object.keys(model.metrics).length > Object.keys(modelWithMaxMetrics.metrics || {}).length - // ) { - // return model - // } - // - // return modelWithMaxMetrics - // }, {}) - // Object.keys(maxMetricsModel.metrics ?? {}).forEach(metricKey => { - // newTableHeaders.push({ - // headerId: metricKey, - // headerLabel: metricKey, - // className: 'table-cell-1' - // }) - // }) - // - // setMetricsCounter(Object.keys(maxMetricsModel.metrics ?? {}).length) - // setTableHeaders(state => [...state, ...newTableHeaders]) - // setDataIsLoaded(true) - // } - // }, [dataIsLoaded, models, tableHeaders]) - - const handleRegisterModel = useCallback(() => { + const handleRegisterModel = useCallback((params, refreshModels, modelsFilters) => { openPopUp(RegisterModelModal, { params, refresh: () => refreshModels(modelsFilters) }) - }, [params, refreshModels, modelsFilters]) + }, []) - const handleTrainModel = () => { + const handleTrainModel = params => { openPopUp(JobWizard, { params, + tab: MODELS_TAB, isTrain: true, wizardTitle: 'Train model' }) } - const [handleRefreshModels, paginatedModels, searchModelsParams, setSearchModelsParams] = - usePagination({ - hidden: isAllVersions, - content: models ?? [], - refreshContent: refreshModels, - filters: modelsFilters, - paginationConfigRef: paginationConfigModelsRef, - resetPaginationTrigger: `${params.projectName}_${refreshAfterDeleteTrigger}` - }) - const [ - handleRefreshModelVersions, - paginatedModelVersions, - searchModelVersionsParams, - setSearchModelVersionsParams - ] = usePagination({ - hidden: !isAllVersions, - content: modelVersions ?? [], - refreshContent: refreshModels, - filters: modelsFilters, - paginationConfigRef: paginationConfigModelVersionsRef, - resetPaginationTrigger: `${params.projectName}_${isAllVersions}` - }) - - const tableContent = useMemo(() => { - return (isAllVersions ? paginatedModelVersions : paginatedModels).map(contentItem => - createModelsRowData(contentItem, params.projectName, isAllVersions) - ) - }, [isAllVersions, paginatedModelVersions, paginatedModels, params.projectName]) - - const tableHeaders = useMemo(() => tableContent[0]?.content ?? [], [tableContent]) - - useEffect(() => { - checkForSelectedArtifact({ - artifactName: params.modelName, - artifacts: isAllVersions ? modelVersions : models, - dispatch, - isAllVersions, - navigate, - paginatedArtifacts: isAllVersions ? paginatedModelVersions : paginatedModels, - paginationConfigRef: isAllVersions - ? paginationConfigModelVersionsRef - : paginationConfigModelsRef, - paramsId: params.id, - projectName: params.projectName, - searchParams: isAllVersions ? searchModelVersionsParams : searchModelsParams, - setSearchParams: isAllVersions ? setSearchModelVersionsParams : setSearchModelsParams, - setSelectedArtifact: setSelectedModel, - setSelectedArtifactIsBeyondTheList, - lastCheckedArtifactIdRef, - tab: MODELS_TAB - }) - }, [ - dispatch, - isAllVersions, - modelVersions, - models, - navigate, - paginatedModelVersions, - paginatedModels, - params.id, - params.modelName, - params.projectName, - searchModelVersionsParams, - searchModelsParams, - setSearchModelVersionsParams, - setSearchModelsParams - ]) - - useEffect(() => { - if (isEmpty(selectedModel)) { - lastCheckedArtifactIdRef.current = null - } - }, [selectedModel]) - - const getAndSetSelectedArtifact = useCallback(() => { - setFullSelectedArtifact( - MODELS_TAB, - dispatch, - navigate, - params.modelName, - setSelectedModel, - params.projectName, - params.id, - isAllVersions - ) - }, [dispatch, isAllVersions, navigate, params.projectName, params.id, params.modelName]) + const renderPageTabs = () => { + return + } return ( - ) } diff --git a/src/components/ModelsPage/Models/ModelsView.jsx b/src/components/ModelsPage/Models/ModelsView.jsx deleted file mode 100644 index 6984372129..0000000000 --- a/src/components/ModelsPage/Models/ModelsView.jsx +++ /dev/null @@ -1,217 +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. -*/ -import React from 'react' -import PropTypes from 'prop-types' -import { isEmpty } from 'lodash' - -import ActionBar from '../../ActionBar/ActionBar' -import ArtifactsFilters from '../../ArtifactsActionBar/ArtifactsFilters' -import ArtifactsTableRow from '../../../elements/ArtifactsTableRow/ArtifactsTableRow' -import Details from '../../Details/Details' -import HistoryBackLink from '../../../common/HistoryBackLink/historyBackLink' -import Loader from '../../../common/Loader/Loader' -import ModelsPageTabs from '../ModelsPageTabs/ModelsPageTabs' -import NoData from '../../../common/NoData/NoData' -import Pagination from '../../../common/Pagination/Pagination' -import Table from '../../Table/Table' - -import { ACTIONS_MENU } from '../../../types' -import { FULL_VIEW_MODE, MODELS_PAGE, MODELS_TAB, ALL_VERSIONS_PATH } from '../../../constants' -import { PRIMARY_BUTTON, TERTIARY_BUTTON } from 'igz-controls/constants' -import { getCloseDetailsLink } from '../../../utils/link-helper.util' -import { getDefaultFirstHeader } from '../../../utils/createArtifactsContent' -import { getNoDataMessage } from '../../../utils/getNoDataMessage' - -const ModelsView = React.forwardRef( - ( - { - actionsMenu, - applyDetailsChanges, - applyDetailsChangesCallback, - artifactsStore, - detailsFormInitialValues, - filters, - filtersConfig, - filtersStore, - getAndSetSelectedArtifact, - handleRefreshModels, - handleRegisterModel, - handleTrainModel, - historyBackLink, - isAllVersions, - isDemoMode, - modelName, - models, - pageData, - paginationConfigModelsRef, - requestErrorMessage, - selectedModel, - setSearchModelsParams, - setSelectedModel, - tableContent, - tableHeaders, - viewMode = '' - }, - { modelsRef } - ) => { - return ( - <> -
-
-
- - {/* TODO: remove from demo in 1.7 */} - - - -
- {isAllVersions && ( -
- -
- )} - {artifactsStore.loading ? null : tableContent.length === 0 && isEmpty(selectedModel) ? ( - - ) : ( - <> - {(artifactsStore.models.modelLoading || artifactsStore.pipelines.loading) && ( - - )} - - getCloseDetailsLink(isAllVersions ? ALL_VERSIONS_PATH : MODELS_TAB) - } - handleCancel={() => setSelectedModel({})} - pageData={pageData} - selectedItem={selectedModel} - tab={MODELS_TAB} - tableClassName="models-table" - tableHeaders={ - !isEmpty(tableHeaders) ? tableHeaders : getDefaultFirstHeader(isAllVersions) - } - viewMode={viewMode} - > - {tableContent.map((tableItem, index) => ( - - ))} -
- - - )} - {viewMode === FULL_VIEW_MODE && !isEmpty(selectedModel) && ( -
- )} -
-
- - ) - } -) - -ModelsView.displayName = 'ModelsView' - -ModelsView.propTypes = { - actionsMenu: ACTIONS_MENU.isRequired, - applyDetailsChanges: PropTypes.func.isRequired, - applyDetailsChangesCallback: PropTypes.func.isRequired, - artifactsStore: PropTypes.object.isRequired, - detailsFormInitialValues: PropTypes.object.isRequired, - filters: PropTypes.object.isRequired, - filtersConfig: PropTypes.object.isRequired, - filtersStore: PropTypes.object.isRequired, - getAndSetSelectedArtifact: PropTypes.func.isRequired, - handleRefreshModels: PropTypes.func.isRequired, - handleRegisterModel: PropTypes.func.isRequired, - handleTrainModel: PropTypes.func.isRequired, - historyBackLink: PropTypes.string.isRequired, - isAllVersions: PropTypes.bool.isRequired, - isDemoMode: PropTypes.bool.isRequired, - modelName: PropTypes.string, - models: PropTypes.arrayOf(PropTypes.object).isRequired, - pageData: PropTypes.object.isRequired, - paginationConfigModelsRef: PropTypes.object.isRequired, - requestErrorMessage: PropTypes.string.isRequired, - selectedModel: PropTypes.object.isRequired, - setSearchModelsParams: PropTypes.func.isRequired, - setSelectedModel: PropTypes.func.isRequired, - tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, - tableHeaders: PropTypes.arrayOf(PropTypes.object).isRequired, - viewMode: PropTypes.string -} - -export default ModelsView diff --git a/src/components/ModelsPage/Models/models.scss b/src/components/ModelsPage/Models/models.scss deleted file mode 100644 index 1a332be1d6..0000000000 --- a/src/components/ModelsPage/Models/models.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use 'igz-controls/scss/variables'; -@use '/src/scss/mixins'; - -$modelsRowHeight: variables.$rowHeight; -$modelsHeaderRowHeight: variables.$headerRowHeightBig; -$modelsRowHeightExtended: variables.$rowHeightExtended; - -.models-table { - @include mixins.rowsHeight($modelsHeaderRowHeight, $modelsRowHeight, $modelsRowHeightExtended); -} diff --git a/src/components/ModelsPage/Models/models.util.jsx b/src/components/ModelsPage/Models/models.util.jsx index a500fbfe23..d70618a846 100644 --- a/src/components/ModelsPage/Models/models.util.jsx +++ b/src/components/ModelsPage/Models/models.util.jsx @@ -24,19 +24,12 @@ import Prism from 'prismjs' import { PopUpDialog } from 'igz-controls/components' import { - NAME_FILTER, MODELS_PAGE, MODELS_TAB, TAG_LATEST, FULL_VIEW_MODE, MODEL_TYPE, - ARTIFACT_MAX_DOWNLOAD_SIZE, - LABELS_FILTER, - TAG_FILTER, - ITERATIONS_FILTER, - TAG_FILTER_LATEST, - TAG_FILTER_ALL_ITEMS, - SHOW_ITERATIONS + ARTIFACT_MAX_DOWNLOAD_SIZE } from '../../../constants' import { showArtifactsPreview, updateArtifact } from '../../../reducers/artifactsReducer' import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' @@ -59,21 +52,6 @@ import DeployIcon from 'igz-controls/images/deploy-icon.svg?react' import DownloadIcon from 'igz-controls/images/download.svg?react' import HistoryIcon from 'igz-controls/images/history.svg?react' -export const getFiltersConfig = isAllVersions => ({ - [NAME_FILTER]: { label: 'Name:', initialValue: '', hidden: isAllVersions }, - [TAG_FILTER]: { - label: 'Version tag:', - initialValue: isAllVersions ? TAG_FILTER_ALL_ITEMS : TAG_FILTER_LATEST, - isModal: true - }, - [LABELS_FILTER]: { label: 'Labels:', initialValue: '', isModal: true }, - [ITERATIONS_FILTER]: { - label: 'Show best iteration only:', - initialValue: isAllVersions ? '' : SHOW_ITERATIONS, - isModal: true - } -}) - export const infoHeaders = [ { label: 'Hash', @@ -127,7 +105,7 @@ export const generateModelsDetailsMenu = selectedModel => [ } ] -export const generatePageData = (selectedItem, viewMode) => ({ +export const generatePageData = (viewMode, selectedItem) => ({ page: MODELS_PAGE, details: { menu: generateModelsDetailsMenu(selectedItem), diff --git a/src/constants.js b/src/constants.js index 2402175935..9f84ed1692 100644 --- a/src/constants.js +++ b/src/constants.js @@ -464,6 +464,7 @@ export const DATASET_TYPE = 'dataset' export const DOCUMENT_TYPE = 'document' export const MODEL_TYPE = 'model' export const ARTIFACT_OTHER_TYPE = 'other' +export const LLM_PROMPT_TYPE = 'llm-prompt' /*=========== ROLES =============*/ diff --git a/src/reducers/artifactsReducer.js b/src/reducers/artifactsReducer.js index e25e689a93..402129e7ee 100644 --- a/src/reducers/artifactsReducer.js +++ b/src/reducers/artifactsReducer.js @@ -22,7 +22,13 @@ import { defaultPendingHandler, hideLoading, showLoading } from './redux.util' import artifactsApi from '../api/artifacts-api' import functionsApi from '../api/functions-api' import modelEndpointsApi from '../api/modelEndpoints-api' -import { ARTIFACTS_TAB, DATASETS_TAB, MODELS_TAB, DOCUMENTS_TAB } from '../constants' +import { + ARTIFACTS_TAB, + DATASETS_TAB, + MODELS_TAB, + DOCUMENTS_TAB, + LLM_PROMPTS_TAB +} from '../constants' import { filterArtifacts } from '../utils/filterArtifacts' import { generateArtifacts } from '../utils/generateArtifacts' import { parseModelEndpoints } from '../utils/parseModelEndpoints' @@ -44,7 +50,7 @@ const initialState = { loading: false } }, - dataSets: { + datasets: { allData: [], filteredData: [], loading: false, @@ -55,6 +61,17 @@ const initialState = { loading: false } }, + LLMPrompts: { + allData: [], + filteredData: [], + loading: false, + LLMPromptLoading: false, + selectedRowData: { + content: {}, + error: null, + loading: false + } + }, error: null, files: { allData: [], @@ -326,6 +343,45 @@ export const fetchArtifactsFunction = createAsyncThunk( }) } ) +export const fetchLLMPrompt = createAsyncThunk( + 'fetchLLMPrompt', + ({ projectName, artifactName, uid, tree, tag, iter }) => { + return artifactsApi + .getArtifact(projectName, artifactName, uid, tree, tag, iter) + .then(response => { + const result = parseArtifacts([response.data]) + + return generateArtifacts(filterArtifacts(result), LLM_PROMPTS_TAB, [response.data])?.[0] + }) + } +) +export const fetchLLMPrompts = createAsyncThunk( + 'fetchLLMPrompts', + ({ project, filters, config }, thunkAPI) => { + config?.ui?.setRequestErrorMessage?.('') + + return artifactsApi + .getLLMPrompts(project, filters, config) + .then(({ data }) => { + const result = parseArtifacts(data.artifacts) + + return { + ...data, + artifacts: generateArtifacts(filterArtifacts(result), LLM_PROMPTS_TAB, data.artifacts) + } + }) + .catch(error => { + largeResponseCatchHandler( + error, + 'Failed to fetch LLM prompts', + thunkAPI.dispatch, + config?.ui?.setRequestErrorMessage + ) + + throw error + }) + } +) export const fetchModelEndpoint = createAsyncThunk( 'fetchModelEndpoint', ({ project, name, uid }) => { @@ -408,7 +464,7 @@ const artifactsSlice = createSlice({ } }, removeDataSet(state, action) { - state.dataSets.selectedRowData = { + state.datasets.selectedRowData = { content: action.payload, error: null, loading: false @@ -425,7 +481,7 @@ const artifactsSlice = createSlice({ } }, removeDataSets(state) { - state.dataSets = initialState.dataSets + state.datasets = initialState.datasets }, removeFile(state, action) { state.files.selectedRowData = { @@ -515,18 +571,41 @@ const artifactsSlice = createSlice({ state.pipelines.loading = false }) builder.addCase(fetchDataSet.pending, state => { - state.dataSets.datasetLoading = true + state.datasets.datasetLoading = true }) builder.addCase(fetchDataSet.fulfilled, state => { - state.dataSets.datasetLoading = false + state.datasets.datasetLoading = false }) builder.addCase(fetchDataSet.rejected, state => { - state.dataSets.datasetLoading = false + state.datasets.datasetLoading = false }) builder.addCase(fetchDataSets.pending, state => { - state.dataSets.loading = true + state.datasets.loading = true + state.loading = true + }) + builder.addCase(fetchLLMPrompt.pending, state => { + state.LLMPrompts.LLMPromptLoading = true + }) + builder.addCase(fetchLLMPrompt.fulfilled, state => { + state.LLMPrompts.LLMPromptLoading = false + }) + builder.addCase(fetchLLMPrompt.rejected, state => { + state.LLMPrompts.LLMPromptLoading = false + }) + builder.addCase(fetchLLMPrompts.pending, state => { + state.LLMPrompts.loading = true state.loading = true }) + builder.addCase(fetchLLMPrompts.fulfilled, (state, action) => { + state.error = null + state.LLMPrompts.allData = action.payload?.artifacts ?? [] + state.LLMPrompts.loading = false + state.loading = state.models.loading || state.files.loading + }) + builder.addCase(fetchLLMPrompts.rejected, state => { + state.LLMPrompts.loading = false + state.loading = state.models.loading || state.files.loading + }) builder.addCase(fetchDocument.pending, state => { state.documents.documentLoading = true }) @@ -552,12 +631,12 @@ const artifactsSlice = createSlice({ }) builder.addCase(fetchDataSets.fulfilled, (state, action) => { state.error = null - state.dataSets.allData = action.payload?.artifacts ?? [] - state.dataSets.loading = false + state.datasets.allData = action.payload?.artifacts ?? [] + state.datasets.loading = false state.loading = state.models.loading || state.files.loading }) builder.addCase(fetchDataSets.rejected, state => { - state.dataSets.loading = false + state.datasets.loading = false state.loading = state.models.loading || state.files.loading }) builder.addCase(fetchFile.pending, state => { @@ -577,11 +656,11 @@ const artifactsSlice = createSlice({ state.error = null state.files.allData = action.payload?.artifacts ?? [] state.files.loading = false - state.loading = state.models.loading || state.dataSets.loading + state.loading = state.models.loading || state.datasets.loading }) builder.addCase(fetchFiles.rejected, state => { state.files.loading = false - state.loading = state.models.loading || state.dataSets.loading + state.loading = state.models.loading || state.datasets.loading }) builder.addCase(fetchModel.pending, state => { state.models.modelLoading = true @@ -620,11 +699,11 @@ const artifactsSlice = createSlice({ state.error = null state.models.allData = action.payload?.artifacts ?? [] state.models.loading = false - state.loading = state.files.loading || state.dataSets.loading + state.loading = state.files.loading || state.datasets.loading }) builder.addCase(fetchModels.rejected, state => { state.models.loading = false - state.loading = state.files.loading || state.dataSets.loading + state.loading = state.files.loading || state.datasets.loading }) } }) diff --git a/src/utils/createArtifactsContent.jsx b/src/utils/createArtifactsContent.jsx index 7cfddcd3ea..96c76bd266 100644 --- a/src/utils/createArtifactsContent.jsx +++ b/src/utils/createArtifactsContent.jsx @@ -615,4 +615,76 @@ export const createDatasetsRowData = (artifact, project, isAllVersions) => { } } +export const createLLMPromptsRowData = (artifact, project, isAllVersions) => { + return { + data: { + ...artifact + }, + content: [ + { + id: `key.${artifact.ui.identifierUnique}`, + headerId: isAllVersions ? 'uid' : 'name', + headerLabel: isAllVersions ? 'UID' : 'Name', + value: isAllVersions ? artifact.uid : artifact.db_key, + className: 'table-cell-name', + getLink: tab => + getArtifactsDetailsLink(artifact, 'llm-prompts', tab, project, isAllVersions), + showTag: true + }, + { + id: `labels.${artifact.ui.identifierUnique}`, + headerId: 'labels', + headerLabel: 'Labels', + value: parseKeyValues(artifact.labels), + className: 'table-cell-1', + type: 'labels' + }, + { + id: `producer.${artifact.ui.identifierUnique}`, + headerId: 'producer', + headerLabel: 'Producer', + value: artifact.producer?.name || '', + template: ( + + ), + className: 'table-cell-1', + type: 'producer' + }, + { + id: `owner.${artifact.ui.identifierUnique}`, + headerId: 'owner', + headerLabel: 'Owner', + value: artifact.producer?.owner, + className: 'table-cell-1', + type: 'owner' + }, + { + id: `updated.${artifact.ui.identifierUnique}`, + headerId: 'updated', + headerLabel: 'Updated', + value: formatDatetime(artifact.updated, 'N/A'), + className: 'table-cell-1' + }, + { + id: `size.${artifact.ui.identifierUnique}`, + headerId: 'size', + headerLabel: 'Size', + value: isNumber(artifact.size) && artifact.size >= 0 ? prettyBytes(artifact.size) : 'N/A', + className: 'table-cell-1' + }, + { + id: `version.${artifact.ui.identifierUnique}`, + headerId: 'tag', + value: artifact.tag, + className: 'table-cell-1', + type: 'hidden' + } + ] + } +} + export default createArtifactsContent diff --git a/src/utils/resources.js b/src/utils/resources.js index 41667a41cb..3c5f5d9bd1 100644 --- a/src/utils/resources.js +++ b/src/utils/resources.js @@ -23,6 +23,7 @@ import { DOCUMENTS_TAB, FEATURE_SETS_TAB, FEATURE_VECTORS_TAB, + LLM_PROMPTS_TAB, MODELS_TAB } from '../constants' import { isNil } from 'lodash' @@ -34,7 +35,8 @@ export const generateUri = (item, tab) => { tab === MODELS_TAB || tab === DATASETS_TAB || tab === ARTIFACTS_TAB || - tab === DOCUMENTS_TAB + tab === DOCUMENTS_TAB || + tab === LLM_PROMPTS_TAB ) { uri += item.db_key uri += getArtifactReference(item)