Skip to content

Commit

Permalink
Impl [Functions] Ability to delete a remote function from DB using UI (
Browse files Browse the repository at this point in the history
  • Loading branch information
illia-prokopchuk authored May 19, 2024
1 parent 429010a commit 0cfb915
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 30 deletions.
4 changes: 2 additions & 2 deletions src/actions/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ const functionsActions = {
createNewFunctionSuccess: () => ({
type: CREATE_NEW_FUNCTION_SUCCESS
}),
deleteFunction: (func, project) => () => {
return functionsApi.deleteSelectedFunction(func, project)
deleteFunction: (funcName, project) => () => {
return functionsApi.deleteSelectedFunction(funcName, project)
},
deployFunction: data => dispatch => {
dispatch(functionsActions.deployFunctionBegin())
Expand Down
6 changes: 3 additions & 3 deletions src/api/functions-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license
under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import { functionTemplatesHttpClient, mainHttpClient } from '../httpClient'
import { functionTemplatesHttpClient, mainHttpClient, mainHttpClientV2 } from '../httpClient'

const functionsApi = {
createNewFunction: (project, data) =>
Expand All @@ -27,8 +27,8 @@ const functionsApi = {
versioned: true
}
}),
deleteSelectedFunction: (func, project) =>
mainHttpClient.delete(`/projects/${project}/functions/${func}`),
deleteSelectedFunction: (funcName, project) =>
mainHttpClientV2.delete(`/projects/${project}/functions/${funcName}`),
deployFunction: data => mainHttpClient.post('/build/function', data),
getFunctions: (project, filters, config = {}, hash) => {
const newConfig = {
Expand Down
85 changes: 62 additions & 23 deletions src/components/FunctionsPage/Functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ import createFunctionsContent from '../../utils/createFunctionsContent'
import functionsActions from '../../actions/functions'
import jobsActions from '../../actions/jobs'
import { DANGER_BUTTON, LABEL_BUTTON } from 'igz-controls/constants'
import { generateActionsMenu,filters, generateFunctionsPageData } from './functions.util'
import { generateActionsMenu, filters, generateFunctionsPageData, pollDeletingFunctions } from './functions.util'
import { getFunctionIdentifier } from '../../utils/getUniqueIdentifier'
import { getFunctionNuclioLogs, getFunctionLogs } from '../../utils/getFunctionLogs'
import { isDetailsTabExists } from '../../utils/isDetailsTabExists'
import { openPopUp } from 'igz-controls/utils/common.util'
import { parseFunctions } from '../../utils/parseFunctions'
import { setFilters } from '../../reducers/filtersReducer'
import { setNotification } from '../../reducers/notificationReducer'
import { isBackgroundTaskRunning } from '../../utils/poll.util'
import { showErrorNotification } from '../../utils/notifications.util'
import { useGroupContent } from '../../hooks/groupContent.hook'
import { useMode } from '../../hooks/mode.hook'
Expand Down Expand Up @@ -79,20 +80,28 @@ const Functions = ({
const filtersStore = useSelector(store => store.filtersStore)
const [selectedRowData, setSelectedRowData] = useState({})
const [largeRequestErrorMessage, setLargeRequestErrorMessage] = useState('')
const [deletingFunctions, setDeletingFunctions] = useState({})
const abortControllerRef = useRef(new AbortController())
const fetchFunctionLogsTimeout = useRef(null)
const fetchFunctionNuclioLogsTimeout = useRef(null)
const tableBodyRef = useRef(null)
const tableRef = useRef(null)
const nameFilterRef = useRef('')
const terminatePollRef = useRef(null)
const { isDemoMode, isStagingMode } = useMode()
const params = useParams()
const navigate = useNavigate()
const location = useLocation()
const dispatch = useDispatch()

const terminateDeleteTasksPolling = useCallback(() => {
terminatePollRef?.current?.()
setDeletingFunctions({})
}, [])

const fetchData = useCallback(
filters => {
terminateDeleteTasksPolling()
abortControllerRef.current = new AbortController()
nameFilterRef.current = filters?.name ?? ''

Expand All @@ -104,14 +113,34 @@ const Functions = ({
}).then(functions => {
if (functions) {
const newFunctions = parseFunctions(functions, params.projectName)
const deletingFunctions = newFunctions.reduce((acc, func) => {
if (func.deletion_task_id && !func.deletion_error && !acc[func.deletion_task_id]) {
acc[func.deletion_task_id] = {
name: func.name
}
}

return acc
}, {})

if (!isEmpty(deletingFunctions)) {
setDeletingFunctions(deletingFunctions)
pollDeletingFunctions(
params.projectName,
terminatePollRef,
deletingFunctions,
() => fetchData(filters),
dispatch
)
}

setFunctions(newFunctions)

return newFunctions
}
})
},
[fetchFunctions, params.projectName]
[dispatch, fetchFunctions, params.projectName, terminateDeleteTasksPolling]
)

const refreshFunctions = useCallback(
Expand Down Expand Up @@ -226,30 +255,39 @@ const Functions = ({
const removeFunction = useCallback(
func => {
deleteFunction(func.name, params.projectName)
.then(() => {
if (!isEmpty(selectedFunction)) {
setSelectedFunction({})
navigate(`/projects/${params.projectName}/functions`, { replace: true })
}
.then(response => {
if (isBackgroundTaskRunning(response)) {
dispatch(
setNotification({
status: 200,
id: Math.random(),
message: 'Function deletion in progress'
})
)

setDeletingFunctions(prevDeletingFunctions => {
const newDeletingFunctions = {
...prevDeletingFunctions,
[response.data.metadata.name]: {
name: func.name
}
}

dispatch(
setNotification({
status: 200,
id: Math.random(),
message: 'Function was deleted'
pollDeletingFunctions(params.projectName, terminatePollRef, newDeletingFunctions, fetchData, dispatch)

return newDeletingFunctions
})
)
fetchData()
})
.catch(error => {
showErrorNotification(dispatch, error, 'Failed to delete the function ', '', () => {
removeFunction(func)
})

if (!isEmpty(selectedFunction)) {
setSelectedFunction({})
navigate(`/projects/${params.projectName}/functions`, { replace: true })
}
}
})

setConfirmData(null)
},
[deleteFunction, dispatch, navigate, params.projectName, fetchData, selectedFunction]
[deleteFunction, params.projectName, dispatch, fetchData, selectedFunction, navigate]
)

const onRemoveFunction = useCallback(
Expand Down Expand Up @@ -398,9 +436,10 @@ const Functions = ({
setEditableItem,
onRemoveFunction,
toggleConvertedYaml,
buildAndRunFunc
buildAndRunFunc,
deletingFunctions
),
[buildAndRunFunc, dispatch, isDemoMode, isStagingMode, onRemoveFunction, toggleConvertedYaml]
[buildAndRunFunc, dispatch, isDemoMode, isStagingMode, onRemoveFunction, toggleConvertedYaml, deletingFunctions]
)

const functionsFilters = useMemo(() => [filters[0]], [])
Expand Down Expand Up @@ -435,7 +474,7 @@ const Functions = ({
if (isEmpty(nameFilterRef.current)) {
showErrorNotification(dispatch, {}, 'This function either does not exist or was deleted')
}

navigate(`/projects/${params.projectName}/functions`, { replace: true })
}
}
Expand Down
1 change: 1 addition & 0 deletions src/components/FunctionsPage/FunctionsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const FunctionsView = React.forwardRef(
<div className="table-container">
<div className="content__action-bar-wrapper">
<ActionBar
page={FUNCTIONS_PAGE}
expand={expand}
filters={functionsFilters}
filterMenuName={FUNCTION_FILTERS}
Expand Down
72 changes: 71 additions & 1 deletion src/components/FunctionsPage/functions.util.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import React from 'react'
import { get } from 'lodash'

import {
DETAILS_BUILD_LOG_TAB,
Expand All @@ -42,6 +43,9 @@ import {
} from '../../constants'
import jobsActions from '../../actions/jobs'
import { showErrorNotification } from '../../utils/notifications.util'
import { BG_TASK_FAILED, BG_TASK_SUCCEEDED, pollTask } from '../../utils/poll.util'
import { setNotification } from '../../reducers/notificationReducer'
import tasksApi from '../../api/tasks-api'

import { ReactComponent as Delete } from 'igz-controls/images/delete.svg'
import { ReactComponent as Run } from 'igz-controls/images/run.svg'
Expand Down Expand Up @@ -189,14 +193,18 @@ export const generateActionsMenu = (
setEditableItem,
onRemoveFunction,
toggleConvertedYaml,
buildAndRunFunc
buildAndRunFunc,
deletingFunctions
) => {
const functionIsDeleting = isFunctionDeleting(func, deletingFunctions)

return [
[
{
id: 'run',
label: 'Run',
icon: <Run />,
disabled: functionIsDeleting,
onClick: func => {
if (func?.project && func?.name && func?.hash && func?.ui?.originalContent) {
dispatch(jobsActions.fetchJobFunctionSuccess(func.ui.originalContent))
Expand All @@ -212,6 +220,7 @@ export const generateActionsMenu = (
{
label: 'Edit',
icon: <Edit />,
disabled: functionIsDeleting,
onClick: func => {
setFunctionsPanelIsOpen(true)
setEditableItem(func)
Expand All @@ -225,11 +234,13 @@ export const generateActionsMenu = (
label: 'Delete',
icon: <Delete />,
className: 'danger',
disabled: functionIsDeleting,
onClick: onRemoveFunction
},
{
label: 'View YAML',
icon: <Yaml />,
disabled: functionIsDeleting,
onClick: toggleConvertedYaml
}
],
Expand All @@ -238,6 +249,7 @@ export const generateActionsMenu = (
id: 'build-and-run',
label: 'Build and run',
icon: <DeployIcon />,
disabled: functionIsDeleting,
onClick: func => {
buildAndRunFunc(func)
},
Expand All @@ -249,6 +261,7 @@ export const generateActionsMenu = (
id: 'deploy',
label: 'Deploy',
icon: <DeployIcon />,
disabled: functionIsDeleting,
onClick: func => {
setFunctionsPanelIsOpen(true)
setEditableItem(func)
Expand All @@ -258,3 +271,60 @@ export const generateActionsMenu = (
]
]
}

export const pollDeletingFunctions = (project, terminatePollRef, deletingFunctions, refresh, dispatch) => {
const taskIds = Object.keys(deletingFunctions)

const pollMethod = () => {
if (taskIds.length === 1) {
return tasksApi.getProjectBackgroundTask(project, taskIds[0])
}

return tasksApi.getProjectBackgroundTasks(project)
}

const isDone = result => {
const tasks = taskIds.length === 1 ? [result.data] : get(result, 'data.background_tasks', [])
const finishedTasks = tasks.filter(
task =>
deletingFunctions?.[task.metadata.name] &&
[BG_TASK_SUCCEEDED, BG_TASK_FAILED].includes(task.status?.state)
)

if (finishedTasks.length > 0) {
finishedTasks.forEach(task => {
if (task.status.state === BG_TASK_SUCCEEDED) {
functionDeletingSuccessHandler(dispatch, deletingFunctions[task.metadata.name])
} else {
showErrorNotification(dispatch, {}, task.status.error || 'Failed to delete the function')
}
})

refresh(project)
}

return finishedTasks.length > 0
}

terminatePollRef?.current?.()
terminatePollRef.current = null

pollTask(pollMethod, isDone, { terminatePollRef })
}

const functionDeletingSuccessHandler = (dispatch, func) => {
dispatch(
setNotification({
status: 200,
id: Math.random(),
message: `Function ${func.name} is successfully deleted`
})
)
}

const isFunctionDeleting = (func, deletingFunctions) => {
return Object.values(deletingFunctions).some(deletingFunction => {
return deletingFunction.name === func.name
})

}
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ export const SET_NEW_FUNCTION_VOLUMES = 'SET_NEW_FUNCTION_VOLUMES'
export const SET_NEW_FUNCTION_VOLUME_MOUNTS = 'SET_NEW_FUNCTION_VOLUME_MOUNTS'
export const FUNCTION_CREATING_STATE = 'creating'
export const FUNCTION_FAILED_STATE = 'failed'
export const FUNCTION_FAILED_TO_DELETE_STATE = 'failedToDelete'
export const FUNCTION_ERROR_STATE = 'error'
export const FUNCTION_INITIALIZED_STATE = 'initialized'
export const FUNCTION_READY_STATE = 'ready'
Expand Down
1 change: 1 addition & 0 deletions src/elements/ProjectTable/projectTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
}

&_failed,
&_failedToDelete,
&_error,
&_unhealthy {
color: $amaranth;
Expand Down
1 change: 1 addition & 0 deletions src/utils/getState.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const functionStateLabels = {
deploying: 'Deploying',
error: 'Error',
failed: 'Error',
failedToDelete: 'Failed to delete',
omitted: 'Omitted',
pending: 'Deploying',
ready: 'Ready',
Expand Down
7 changes: 6 additions & 1 deletion src/utils/parseFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import getState from './getState'
import { page } from '../components/FunctionsPage/functions.util'
import { getFunctionIdentifier } from './getUniqueIdentifier'
import { parseKeyValues } from './object'
import { FUNCTION_FAILED_TO_DELETE_STATE } from '../constants'

export const parseFunction = (func, projectName, customState) => {
const state = (func.status?.deletion_error ? FUNCTION_FAILED_TO_DELETE_STATE : customState) || func.status?.state

const item = {
access_key: func.metadata.credentials?.access_key ?? '',
application_image: func.status?.application_image ?? '',
Expand All @@ -33,6 +36,8 @@ export const parseFunction = (func, projectName, customState) => {
container_image: func?.status?.container_image ?? '',
default_class: func.spec?.default_class ?? '',
default_handler: func.spec?.default_handler ?? '',
deletion_error: func.status?.deletion_error ?? '',
deletion_task_id: func.status?.deletion_task_id ?? '',
description: func.spec?.description ?? '',
disable_auto_mount: func.spec?.disable_auto_mount ?? true,
env: func.spec?.env ?? [],
Expand All @@ -54,7 +59,7 @@ export const parseFunction = (func, projectName, customState) => {
project: func.metadata?.project || projectName,
resources: func.spec?.resources ?? {},
secret_sources: func.spec?.secret_sources ?? [],
state: getState(customState || func.status?.state, page, 'function'),
state: getState(state, page, 'function'),
tag: func.metadata?.tag ?? '',
track_models: func.spec?.track_models ?? false,
type: func.kind,
Expand Down

0 comments on commit 0cfb915

Please sign in to comment.