diff --git a/src/components/ProjectSettings/ProjectSettings.js b/src/components/ProjectSettings/ProjectSettings.js
index b9d5d883d..45b08bca9 100644
--- a/src/components/ProjectSettings/ProjectSettings.js
+++ b/src/components/ProjectSettings/ProjectSettings.js
@@ -17,8 +17,8 @@ illegal under applicable law, and the grant of the foregoing license
under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
-import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
-import { connect, useDispatch } from 'react-redux'
+import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import ProjectSettingsGeneral from '../../elements/ProjectSettingsGeneral/ProjectSettingsGeneral'
@@ -27,6 +27,9 @@ import ProjectSettingsSecrets from '../../elements/ProjectSettingsSecrets/Projec
import Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs'
import ContentMenu from '../../elements/ContentMenu/ContentMenu'
+import { Button, ConfirmDialog } from 'igz-controls/components'
+import { DANGER_BUTTON, TERTIARY_BUTTON } from 'igz-controls/constants'
+
import {
COMPLETED_STATE,
generateMembers,
@@ -35,6 +38,8 @@ import {
tabs,
validTabs
} from './projectSettings.util'
+import { onDeleteProject } from '../ProjectsPage/projects.util'
+import projectsAction from '../../actions/projects'
import {
initialMembersState,
membersActions,
@@ -47,14 +52,19 @@ import { showErrorNotification } from '../../utils/notifications.util'
import './projectSettings.scss'
-const ProjectSettings = ({ frontendSpec }) => {
+const ProjectSettings = () => {
const [projectMembersIsShown, setProjectMembersIsShown] = useState(false)
const [projectOwnerIsShown, setProjectOwnerIsShown] = useState(false)
+ const [confirmData, setConfirmData] = useState(null)
const [membersState, membersDispatch] = useReducer(membersReducer, initialMembersState)
const location = useLocation()
const navigate = useNavigate()
const params = useParams()
const dispatch = useDispatch()
+ const deletingProjectsRef = useRef({})
+ const terminatePollRef = useRef(null)
+ const projectStore = useSelector(state => state.projectStore)
+ const frontendSpec = useSelector(store => store.appStore.frontendSpec)
const projectMembershipIsEnabled = useMemo(
() => frontendSpec?.feature_flags?.project_membership === 'enabled',
@@ -122,7 +132,7 @@ const ProjectSettings = ({ frontendSpec }) => {
[]
) || [])
])
-
+
membersDispatch({
type: membersActions.SET_ACTIVE_USER,
payload: activeUser
@@ -208,6 +218,10 @@ const ProjectSettings = ({ frontendSpec }) => {
})
}, [])
+ const fetchMinimalProjects = useCallback(() => {
+ dispatch(projectsAction.fetchProjects({ format: 'minimal' }))
+ }, [dispatch])
+
useEffect(() => {
membersDispatch({
type: membersActions.GET_PROJECT_USERS_DATA_BEGIN
@@ -226,43 +240,83 @@ const ProjectSettings = ({ frontendSpec }) => {
}, [navigate, params.pageTab, params.projectName])
return (
-
-
-
-
-
-
+ {confirmData && (
+
- {params.pageTab === PROJECTS_SETTINGS_MEMBERS_TAB && projectMembersTabIsShown ? (
-
- ) : params.pageTab === PROJECTS_SETTINGS_SECRETS_TAB ? (
-
- ) : (
-
- )}
+ )}
+
+
+
+
+
+
+
+
+
+ {projectMembershipIsEnabled && (
+
+
+ {params.pageTab === PROJECTS_SETTINGS_MEMBERS_TAB && projectMembersTabIsShown ? (
+
+ ) : params.pageTab === PROJECTS_SETTINGS_SECRETS_TAB ? (
+
+ ) : (
+
+ )}
+
-
+ >
)
}
-export default connect(
- ({ appStore }) => ({
- frontendSpec: appStore.frontendSpec
- }),
- null
-)(ProjectSettings)
+export default ProjectSettings
diff --git a/src/components/ProjectsPage/Projects.js b/src/components/ProjectsPage/Projects.js
index a22cfcb2b..a4778cde9 100644
--- a/src/components/ProjectsPage/Projects.js
+++ b/src/components/ProjectsPage/Projects.js
@@ -28,7 +28,6 @@ import ProjectsView from './ProjectsView'
import {
generateMonitoringCounters,
generateProjectActionsMenu,
- handleDeleteProjectError,
pollDeletingProjects,
projectDeletionKind,
projectDeletionWrapperKind,
@@ -36,9 +35,10 @@ import {
} from './projects.util'
import nuclioActions from '../../actions/nuclio'
import projectsAction from '../../actions/projects'
-import { BG_TASK_RUNNING, isBackgroundTaskRunning } from '../../utils/poll.util'
+import { BG_TASK_RUNNING } from '../../utils/poll.util'
+import { onDeleteProject } from './projects.util'
import { PROJECT_ONLINE_STATUS } from '../../constants'
-import { DANGER_BUTTON, FORBIDDEN_ERROR_STATUS_CODE, PRIMARY_BUTTON } from 'igz-controls/constants'
+import { FORBIDDEN_ERROR_STATUS_CODE, PRIMARY_BUTTON } from 'igz-controls/constants'
import { fetchBackgroundTasks } from '../../reducers/tasksReducer'
import { setNotification } from '../../reducers/notificationReducer'
import { showErrorNotification } from '../../utils/notifications.util'
@@ -135,7 +135,9 @@ const Projects = () => {
backgroundTask =>
backgroundTask.metadata.kind.startsWith(
wrapperIsUsed ? projectDeletionWrapperKind : projectDeletionKind
- ) && backgroundTask?.status?.state === BG_TASK_RUNNING && deletingProjectsRef.current[backgroundTask.metadata.name]
+ ) &&
+ backgroundTask?.status?.state === BG_TASK_RUNNING &&
+ deletingProjectsRef.current[backgroundTask.metadata.name]
)
.reduce((acc, backgroundTask) => {
acc[backgroundTask.metadata.name] = last(backgroundTask.metadata.kind.split('.'))
@@ -188,53 +190,6 @@ const Projects = () => {
[dispatch, fetchMinimalProjects]
)
- const handleDeleteProject = useCallback(
- (project, deleteNonEmpty) => {
- setConfirmData(null)
-
- dispatch(projectsAction.deleteProject(project.metadata.name, deleteNonEmpty))
- .then(response => {
- if (isBackgroundTaskRunning(response)) {
- dispatch(
- setNotification({
- status: 200,
- id: Math.random(),
- message: 'Project deletion in progress'
- })
- )
-
- const newDeletingProjects = {
- ...deletingProjectsRef.current,
- [response.data.metadata.name]: last(response.data.metadata.kind.split('.'))
- }
-
- dispatch(projectsAction.setDeletingProjects(newDeletingProjects))
- pollDeletingProjects(terminatePollRef, newDeletingProjects, refreshProjects, dispatch)
- } else {
- fetchMinimalProjects()
- dispatch(
- setNotification({
- status: 200,
- id: Math.random(),
- message: `Project "${project}" was deleted successfully`
- })
- )
- }
- })
- .catch(error => {
- handleDeleteProjectError(
- error,
- handleDeleteProject,
- project,
- setConfirmData,
- dispatch,
- deleteNonEmpty
- )
- })
- },
- [dispatch, fetchMinimalProjects, refreshProjects]
- )
-
const handleUnarchiveProject = useCallback(
project => {
dispatch(
@@ -276,23 +231,6 @@ const Projects = () => {
[handleArchiveProject]
)
- const onDeleteProject = useCallback(
- project => {
- setConfirmData({
- item: project,
- header: 'Delete project?',
- message: `You are trying to delete the project "${project.metadata.name}". Deleted projects cannot be restored`,
- btnConfirmLabel: 'Delete',
- btnConfirmType: DANGER_BUTTON,
- rejectHandler: () => {
- setConfirmData(null)
- },
- confirmHandler: handleDeleteProject
- })
- },
- [handleDeleteProject]
- )
-
const exportYaml = useCallback(
projectMinimal => {
if (projectMinimal?.metadata?.name) {
@@ -303,7 +241,7 @@ const Projects = () => {
FileSaver.saveAs(blob, `${projectMinimal.metadata.name}.yaml`)
})
.catch(error => {
- showErrorNotification(dispatch, error, '', 'Failed to fetch project\'s YAML', () =>
+ showErrorNotification(dispatch, error, '', "Failed to fetch project's YAML", () =>
exportYaml(projectMinimal)
)
})
@@ -322,7 +260,7 @@ const Projects = () => {
.catch(error => {
setConvertedYaml('')
- showErrorNotification(dispatch, error, '', 'Failed to fetch project\'s YAML', () =>
+ showErrorNotification(dispatch, error, '', "Failed to fetch project's YAML", () =>
viewYaml(projectMinimal)
)
})
@@ -338,6 +276,21 @@ const Projects = () => {
[dispatch]
)
+ const handleOnDeleteProject = useCallback(
+ project =>
+ onDeleteProject(
+ project,
+ setConfirmData,
+ dispatch,
+ deletingProjectsRef,
+ terminatePollRef,
+ fetchMinimalProjects,
+ null,
+ refreshProjects
+ ),
+ [dispatch, fetchMinimalProjects, refreshProjects]
+ )
+
useEffect(() => {
setActionsMenu(
generateProjectActionsMenu(
@@ -347,17 +300,17 @@ const Projects = () => {
viewYaml,
onArchiveProject,
handleUnarchiveProject,
- onDeleteProject
+ handleOnDeleteProject
)
)
}, [
convertToYaml,
+ handleOnDeleteProject,
projectStore.deletingProjects,
exportYaml,
handleUnarchiveProject,
isDemoMode,
onArchiveProject,
- onDeleteProject,
projectStore.projects,
viewYaml
])
diff --git a/src/components/ProjectsPage/projects.util.js b/src/components/ProjectsPage/projects.util.js
index e86228610..6bbec1a1a 100644
--- a/src/components/ProjectsPage/projects.util.js
+++ b/src/components/ProjectsPage/projects.util.js
@@ -18,7 +18,7 @@ under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import React from 'react'
-import { get, omit } from 'lodash'
+import { get, omit, last } from 'lodash'
import tasksApi from '../../api/tasks-api'
import {
@@ -26,7 +26,12 @@ import {
SERVICE_UNAVAILABLE_ERROR_STATUS_CODE,
GATEWAY_TIMEOUT_STATUS_CODE
} from 'igz-controls/constants'
-import { BG_TASK_FAILED, BG_TASK_SUCCEEDED, pollTask } from '../../utils/poll.util'
+import {
+ BG_TASK_FAILED,
+ BG_TASK_SUCCEEDED,
+ isBackgroundTaskRunning,
+ pollTask
+} from '../../utils/poll.util'
import { PROJECT_ONLINE_STATUS } from '../../constants'
import { DANGER_BUTTON, FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants'
import { setNotification } from '../../reducers/notificationReducer'
@@ -138,7 +143,11 @@ export const handleDeleteProjectError = (
project,
setConfirmData,
dispatch,
- deleteNonEmpty
+ deleteNonEmpty,
+ deletingProjectsRef,
+ terminatePollRef,
+ fetchMinimalProjects,
+ navigate
) => {
if (error.response?.status === 412 && !deleteNonEmpty) {
setConfirmData({
@@ -153,7 +162,16 @@ export const handleDeleteProjectError = (
setConfirmData(null)
},
confirmHandler: project => {
- handleDeleteProject(project, true)
+ handleDeleteProject(
+ project,
+ true,
+ setConfirmData,
+ dispatch,
+ deletingProjectsRef,
+ terminatePollRef,
+ fetchMinimalProjects,
+ navigate
+ )
}
})
} else {
@@ -162,7 +180,18 @@ export const handleDeleteProjectError = (
? `You do not have permission to delete the project ${project.metadata.name} `
: `Failed to delete the project ${project.metadata.name}`
- showErrorNotification(dispatch, error, '', customErrorMsg, () => handleDeleteProject(project))
+ showErrorNotification(dispatch, error, '', customErrorMsg, () =>
+ handleDeleteProject(
+ project,
+ false,
+ setConfirmData,
+ dispatch,
+ deletingProjectsRef,
+ terminatePollRef,
+ fetchMinimalProjects,
+ navigate
+ )
+ )
}
}
@@ -244,21 +273,117 @@ export const generateMonitoringCounters = (data, dispatch) => {
monitoringCounters.jobs.all += project.runs_completed_recent_count || 0
monitoringCounters.jobs.all += project.runs_failed_recent_count || 0
monitoringCounters.jobs.all += project.runs_running_count || 0
+ monitoringCounters.jobs.all +=
+ project.runs_completed_recent_count ||
+ 0 + project.runs_failed_recent_count ||
+ 0 + project.runs_running_count ||
+ 0
monitoringCounters.jobs.completed += project.runs_completed_recent_count || 0
monitoringCounters.jobs.failed += project.runs_failed_recent_count || 0
monitoringCounters.jobs.running += project.runs_running_count || 0
monitoringCounters.workflows.all += project.pipelines_completed_recent_count || 0
monitoringCounters.workflows.all += project.pipelines_failed_recent_count || 0
monitoringCounters.workflows.all += project.pipelines_running_count || 0
+ monitoringCounters.workflows.all +=
+ project.pipelines_completed_recent_count ||
+ 0 + project.pipelines_failed_recent_count ||
+ 0 + project.pipelines_running_count ||
+ 0
monitoringCounters.workflows.completed += project.pipelines_completed_recent_count || 0
monitoringCounters.workflows.failed += project.pipelines_failed_recent_count || 0
monitoringCounters.workflows.running += project.pipelines_running_count || 0
- monitoringCounters.scheduled.all += project.distinct_scheduled_jobs_pending_count || 0
- monitoringCounters.scheduled.all += project.distinct_scheduled_pipelines_pending_count || 0
+ monitoringCounters.scheduled.all +=
+ project.distinct_scheduled_jobs_pending_count ||
+ 0 + project.distinct_scheduled_pipelines_pending_count ||
+ 0
monitoringCounters.scheduled.jobs += project.distinct_scheduled_jobs_pending_count || 0
monitoringCounters.scheduled.workflows +=
project.distinct_scheduled_pipelines_pending_count || 0
+ monitoringCounters.scheduled.workflows +=
+ project.distinct_scheduled_pipelines_pending_count || 0
})
dispatch(projectsAction.setJobsMonitoringData(monitoringCounters))
}
+
+export const onDeleteProject = (project, setConfirmData, ...args) => {
+ setConfirmData({
+ item: project,
+ header: 'Delete project?',
+ message: `You are trying to delete the project "${project.metadata.name}". Deleted projects cannot be restored`,
+ btnConfirmLabel: 'Delete',
+ btnConfirmType: DANGER_BUTTON,
+ rejectHandler: () => {
+ setConfirmData(null)
+ },
+ confirmHandler: deleteNonEmpty =>
+ handleDeleteProject(project, deleteNonEmpty, setConfirmData, ...args)
+ })
+}
+
+export const handleDeleteProject = (
+ project,
+ deleteNonEmpty,
+ setConfirmData,
+ dispatch,
+ deletingProjectsRef,
+ terminatePollRef,
+ fetchMinimalProjects,
+ navigate,
+ refreshProjects
+) => {
+ setConfirmData && setConfirmData(null)
+
+ dispatch(projectsAction.deleteProject(project.metadata.name, deleteNonEmpty))
+ .then(response => {
+ if (isBackgroundTaskRunning(response)) {
+ dispatch(
+ setNotification({
+ status: 200,
+ id: Math.random(),
+ message: 'Project deletion in progress'
+ })
+ )
+
+ const newDeletingProjects = {
+ ...deletingProjectsRef.current,
+ [response.data.metadata.name]: last(response.data.metadata.kind.split('.'))
+ }
+
+ dispatch(projectsAction.setDeletingProjects(newDeletingProjects))
+ if (refreshProjects) {
+ pollDeletingProjects(terminatePollRef, newDeletingProjects, refreshProjects, dispatch)
+ }
+
+ if (navigate) {
+ navigate('/projects')
+ }
+ } else {
+ fetchMinimalProjects()
+ dispatch(
+ setNotification({
+ status: 200,
+ id: Math.random(),
+ message: `Project "${project.metadata.name}" was deleted successfully`
+ })
+ )
+ if (navigate) {
+ navigate('/projects')
+ }
+ }
+ })
+ .catch(error => {
+ handleDeleteProjectError(
+ error,
+ handleDeleteProject,
+ project,
+ setConfirmData,
+ dispatch,
+ deleteNonEmpty,
+ deletingProjectsRef,
+ terminatePollRef,
+ fetchMinimalProjects,
+ navigate
+ )
+ })
+}
diff --git a/src/elements/ChangeOwnerPopUp/ChangeOwnerPopUp.js b/src/elements/ChangeOwnerPopUp/ChangeOwnerPopUp.js
index 7ddeac6fd..7cb8c3930 100644
--- a/src/elements/ChangeOwnerPopUp/ChangeOwnerPopUp.js
+++ b/src/elements/ChangeOwnerPopUp/ChangeOwnerPopUp.js
@@ -176,6 +176,7 @@ const ChangeOwnerPopUp = ({ changeOwnerCallback, projectId }) => {
state.projectStore)
+
return (
-
{
- if (
- event.target.tagName !== 'A' &&
- !ref.current.contains(event.target) &&
- !chipRef.current?.contains(event.target) &&
- !event.target.closest('#overlay_container')
- ) {
- navigate(`/projects/${project.metadata.name}/monitor`)
- }
- }}
- ref={cardRef}
- >
-
-
-
-
}
- >
- {project.metadata.name}
-
+
+ {Object.values(deletingProjects).includes(project.metadata.name) &&
}
+
{
+ if (
+ event.target.tagName !== 'A' &&
+ !ref.current.contains(event.target) &&
+ !chipRef.current?.contains(event.target) &&
+ !event.target.closest('#overlay_container')
+ ) {
+ navigate(`/projects/${project.metadata.name}/monitor`)
+ }
+ }}
+ ref={cardRef}
+ >
+
+
+
+
}
+ >
+ {project.metadata.name}
+
-
-
-
Created {getTimeElapsedByDate(project.metadata.created)}
+
+
+ Created {getTimeElapsedByDate(project.metadata.created)}
+
-
-
-
Owner:
-
{project.spec.owner}
+
+ Owner:
+ {project.spec.owner}
+
-
-
-
- {project?.spec.description && (
- }>
- {project.spec.description}
-
- )}
-
+
+
+ {project?.spec.description && (
+ }>
+ {project.spec.description}
+
+ )}
+
-
-
- {project.metadata.labels && (
-
- Labels:
-
-
- )}
-
+ {project.metadata.labels && (
+
+ Labels:
+
+
+ )}
+
-
)
diff --git a/src/elements/ProjectCard/projectCard.scss b/src/elements/ProjectCard/projectCard.scss
index 2424eee2e..29573335d 100644
--- a/src/elements/ProjectCard/projectCard.scss
+++ b/src/elements/ProjectCard/projectCard.scss
@@ -93,6 +93,10 @@
box-shadow: $previewBoxShadow;
}
+ .loader-wrapper {
+ position: absolute;
+ }
+
.project-data-card__statistics {
&-item {
flex: 0 0 auto;
diff --git a/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js b/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js
index 7dfb1aa86..c882e6e3e 100644
--- a/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js
+++ b/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js
@@ -45,6 +45,7 @@ import {
DEFAULT_IMAGE,
DESCRIPTION,
GOALS,
+ KEY_CODES,
LABELS,
LOAD_SOURCE_ON_RUN,
NODE_SELECTORS,
@@ -58,7 +59,6 @@ import {
setFieldState
} from 'igz-controls/utils/form.util'
import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants'
-import { KEY_CODES } from '../../constants'
import { getChipOptions } from '../../utils/getChipOptions'
import { getErrorMsg } from 'igz-controls/utils/common.util'
import {
@@ -84,7 +84,8 @@ const ProjectSettingsGeneral = ({
}) => {
const [projectIsInitialized, setProjectIsInitialized] = useState(false)
const [lastEditedProjectValues, setLastEditedProjectValues] = useState({})
- const formRef = React.useRef(
+
+ const formRef = useRef(
createForm({
initialValues: {},
mutators: { ...arrayMutators, setFieldState },
@@ -228,164 +229,174 @@ const ProjectSettingsGeneral = ({
}, [])
return (
-