diff --git a/src/api/events-api.js b/src/api/events-api.js new file mode 100644 index 000000000..f5f2768e8 --- /dev/null +++ b/src/api/events-api.js @@ -0,0 +1,29 @@ +/* +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 { iguazioHttpClient } from '../httpClient' + +const eventsApi = { + getProjectSyncIssues: () => + iguazioHttpClient.get('/v1/events/activations', { + params: { class: 'Software.ProjectSync', severity: 'major,critical' } + }) +} + +export default eventsApi diff --git a/src/api/tasks-api.js b/src/api/tasks-api.js index de9bfdd34..667044299 100644 --- a/src/api/tasks-api.js +++ b/src/api/tasks-api.js @@ -34,6 +34,9 @@ const tasksApi = { getProjectBackgroundTask: (project, id) => { return mainHttpClient.get(`/projects/${project}/background-tasks/${id}`) }, + getBackgroundTask: id => { + return mainHttpClient.get(`/background-tasks/${id}`) + }, getProjectBackgroundTasks: (project, state) => { const config = {} diff --git a/src/components/ProjectsPage/Projects.jsx b/src/components/ProjectsPage/Projects.jsx index 2292fa8b6..64fe14093 100644 --- a/src/components/ProjectsPage/Projects.jsx +++ b/src/components/ProjectsPage/Projects.jsx @@ -61,6 +61,7 @@ import { setDeletingProjects } from '../../reducers/projectReducer' import { fetchAllNuclioFunctions } from '../../reducers/nuclioReducer' +import { useProjectsSyncStatus } from '../../hooks/useProjectsSyncStatus.hook' const Projects = () => { const [actionsMenu, setActionsMenu] = useState({}) @@ -91,14 +92,22 @@ const Projects = () => { deletingProjectsRef.current = projectStore.deletingProjects }, [projectStore.deletingProjects]) - const fetchMinimalProjects = useCallback(() => { - dispatch( - fetchProjects({ - params: { format: 'minimal' }, - setRequestErrorMessage: setProjectsRequestErrorMessage - }) - ) - }, [dispatch]) + const fetchMinimalProjects = useCallback( + (silent = false) => { + dispatch( + fetchProjects({ + params: { format: 'minimal' }, + setRequestErrorMessage: setProjectsRequestErrorMessage, + silent + }) + ) + }, + [dispatch] + ) + + const fetchMinimalProjectsSilently = useCallback(() => { + fetchMinimalProjects(true) + }, [fetchMinimalProjects]) const isValidProjectState = useCallback( project => { @@ -129,15 +138,17 @@ const Projects = () => { [isDescendingOrder, sortProjectId] ) - const refreshProjects = useCallback(() => { + const refreshProjects = useCallback((silent = false) => { abortControllerRef.current = new AbortController() if (!isNuclioModeDisabled) { dispatch(fetchAllNuclioFunctions()) } - dispatch(removeProjects()) - fetchMinimalProjects() + if (!silent) { + dispatch(removeProjects()) + } + fetchMinimalProjects(silent) dispatch( fetchProjectsSummary({ signal: abortControllerRef.current.signal, refresh: refreshProjects }) ) @@ -185,6 +196,11 @@ const Projects = () => { } }, [isNuclioModeDisabled, dispatch, fetchMinimalProjects]) + const { projectSyncStatusMap } = useProjectsSyncStatus( + projectStore.projects, + fetchMinimalProjectsSilently + ) + const handleSearchOnChange = useCallback( name => { setFilterByName(name) @@ -425,7 +441,7 @@ const Projects = () => { .then(result => { if (result) { setCreateProject(false) - refreshProjects() + refreshProjects(true) dispatch(fetchProjectsNames()) dispatch( setNotification({ @@ -457,6 +473,7 @@ const Projects = () => { isDescendingOrder={isDescendingOrder} projectsRequestErrorMessage={projectsRequestErrorMessage} projectStore={projectStore} + projectSyncStatusMap={projectSyncStatusMap} refreshProjects={refreshProjects} selectedProjectsState={selectedProjectsState} setCreateProject={setCreateProject} diff --git a/src/components/ProjectsPage/ProjectsView.jsx b/src/components/ProjectsPage/ProjectsView.jsx index 64522cc25..c1db00188 100644 --- a/src/components/ProjectsPage/ProjectsView.jsx +++ b/src/components/ProjectsPage/ProjectsView.jsx @@ -57,6 +57,7 @@ const ProjectsView = ({ isDescendingOrder, projectsRequestErrorMessage, projectStore, + projectSyncStatusMap, refreshProjects, selectedProjectsState, setCreateProject, @@ -176,6 +177,7 @@ const ProjectsView = ({ projectSummary={projectStore.projectsSummary.data.find( item => item.name === project.metadata.name )} + syncStatusTooltip={projectSyncStatusMap[project.metadata.name]} /> ) })} @@ -215,6 +217,7 @@ ProjectsView.propTypes = { isDescendingOrder: PropTypes.bool.isRequired, projectStore: PropTypes.object.isRequired, projectsRequestErrorMessage: PropTypes.string.isRequired, + projectSyncStatusMap: PropTypes.object.isRequired, refreshProjects: PropTypes.func.isRequired, selectedProjectsState: PropTypes.string.isRequired, setCreateProject: PropTypes.func.isRequired, diff --git a/src/components/ProjectsPage/projects.util.jsx b/src/components/ProjectsPage/projects.util.jsx index 70631352a..c38719aed 100644 --- a/src/components/ProjectsPage/projects.util.jsx +++ b/src/components/ProjectsPage/projects.util.jsx @@ -39,7 +39,13 @@ import { } from '../../reducers/projectReducer' import tasksApi from '../../api/tasks-api' import { DANGER_BUTTON, FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' -import { PROJECT_ONLINE_STATUS } from '../../constants' +import { + PROJECT_CREATING_STATUS, + PROJECT_DELETING_STATUS, + PROJECT_ONLINE_STATUS, + PROJECT_SYNC_ISSUE_TOOLTIP, + PROJECT_TRANSITIONAL_STATUSES +} from '../../constants' import { setNotification } from 'igz-controls/reducers/notificationReducer' import { showErrorNotification } from 'igz-controls/utils/notification.util' @@ -255,6 +261,31 @@ export const pollDeletingProjects = (terminatePollRef, deletingProjects, refresh }) } +const projectSyncStateWords = { + [PROJECT_CREATING_STATUS]: { noun: 'creation', gerund: 'creating' }, + [PROJECT_DELETING_STATUS]: { noun: 'deletion', gerund: 'deleting' } +} + +export const getProjectSyncTooltip = (project, backgroundTaskState, hasSystemSyncIssue) => { + const state = project.status?.state + + if (!PROJECT_TRANSITIONAL_STATUSES.includes(state)) { + return null + } + + if (hasSystemSyncIssue) { + return PROJECT_SYNC_ISSUE_TOOLTIP + } + + const { noun, gerund } = projectSyncStateWords[state] + + if (backgroundTaskState === BG_TASK_FAILED) { + return `Issues were detected while ${gerund} the project. The system will automatically retry: no manual action is needed.` + } + + return `The project is in ${noun} process.` +} + export const generateAlerts = (data, dispatch) => { const projectAlerts = {} data.forEach(project => { @@ -412,6 +443,8 @@ export const handleDeleteProject = ( dispatch(setDeletingProjects(newDeletingProjects)) + fetchMinimalProjects(true) + if (refreshProjects) { pollDeletingProjects(terminatePollRef, newDeletingProjects, refreshProjects, dispatch) } diff --git a/src/constants.js b/src/constants.js index a3452a1b3..3b945098a 100644 --- a/src/constants.js +++ b/src/constants.js @@ -45,6 +45,12 @@ export const NAVBAR_WIDTH_OPENED = 245 export const CANCEL_REQUEST_TIMEOUT = 120000 export const PROJECT_ONLINE_STATUS = 'online' +export const PROJECT_CREATING_STATUS = 'creating' +export const PROJECT_DELETING_STATUS = 'deleting' +export const PROJECT_TRANSITIONAL_STATUSES = [PROJECT_CREATING_STATUS, PROJECT_DELETING_STATUS] +export const PROJECT_SYNC_POLL_INTERVAL_MS = 10000 +export const PROJECT_SYNC_ISSUE_TOOLTIP = + 'Project synchronization issues were detected. Contact the system admin.' export const ABORTED_STATE = 'aborted' export const ABORTING_STATE = 'aborting' diff --git a/src/elements/ProjectCard/ProjectCard.jsx b/src/elements/ProjectCard/ProjectCard.jsx index 64d248b81..43995b9fb 100644 --- a/src/elements/ProjectCard/ProjectCard.jsx +++ b/src/elements/ProjectCard/ProjectCard.jsx @@ -25,7 +25,7 @@ import ProjectCardView from './ProjectCardView' import { generateProjectStatistic } from './projectCard.util' -const ProjectCard = ({ actionsMenu, alert, project, projectSummary }) => { +const ProjectCard = ({ actionsMenu, alert, project, projectSummary, syncStatusTooltip }) => { const [fetchNuclioFunctionsFailure, setFetchNuclioFunctionsFailure] = useState(false) const projectStore = useSelector(store => store.projectStore) const nuclioStore = useSelector(store => store.nuclioStore) @@ -62,6 +62,7 @@ const ProjectCard = ({ actionsMenu, alert, project, projectSummary }) => { alert={alert} project={project} statistics={statistics} + syncStatusTooltip={syncStatusTooltip} ref={actionsMenuRef} /> ) @@ -71,7 +72,8 @@ ProjectCard.propTypes = { actionsMenu: PropTypes.object.isRequired, alert: PropTypes.number.isRequired, project: PropTypes.object.isRequired, - projectSummary: PropTypes.object + projectSummary: PropTypes.object, + syncStatusTooltip: PropTypes.string } export default ProjectCard diff --git a/src/elements/ProjectCard/ProjectCardView.jsx b/src/elements/ProjectCard/ProjectCardView.jsx index 01a51056f..2ec996535 100644 --- a/src/elements/ProjectCard/ProjectCardView.jsx +++ b/src/elements/ProjectCard/ProjectCardView.jsx @@ -18,18 +18,12 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ import React, { useRef } from 'react' -import { useSelector } from 'react-redux' import PropTypes from 'prop-types' +import classnames from 'classnames' import { useNavigate } from 'react-router-dom' import ProjectStatistics from '../ProjectStatistics/ProjectStatistics' -import { - Tooltip, - TextTooltipTemplate, - Loader, - ActionsMenu, - ReadOnlyChips -} from 'igz-controls/components' +import { Tooltip, TextTooltipTemplate, ActionsMenu, ReadOnlyChips } from 'igz-controls/components' import { getTimeElapsedByDate } from 'igz-controls/utils/datetime.util' @@ -38,92 +32,104 @@ import ClockIcon from 'igz-controls/images/clock.svg?react' import './projectCard.scss' -const ProjectCardView = React.forwardRef(({ actionsMenu, alert, project, statistics }, ref) => { - const cardRef = useRef() - const chipRef = useRef() - const navigate = useNavigate() - - const { deletingProjects, projectsToDelete } = useSelector(state => state.projectStore) - - return ( -