-
- {!isExpressEditComparisonView &&
- (isPatchMode ? (
-
- ) : (
- <>
- {isHashiOrAWS && renderHashiOrAwsDeprecatedInfo()}
-
- {renderDataTypeSelector()}
- {renderName()}
-
- {renderESOInfo(isESO)}
- {renderExternalInfo(data.externalType, data.external, componentType)}
- {renderMountData()}
- {renderVolumeMountPath()}
- {renderRollARN()}
- >
- ))}
-
-
+
)
}
diff --git a/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretNullState.tsx b/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretNullState.tsx
index 4d76020558..b54136d337 100644
--- a/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretNullState.tsx
+++ b/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretNullState.tsx
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-import { generatePath, useRouteMatch } from 'react-router-dom'
+import { generatePath, useParams } from 'react-router-dom'
import { Button, ButtonComponentType, GenericEmptyState, ImageType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
+import ICAdd from '@Icons/ic-add.svg?react'
import EmptyStateImg from '@Images/cm-cs-empty-state.png'
import EmptyFolder from '@Images/empty-folder.webp'
@@ -29,10 +29,10 @@ export const ConfigMapSecretNullState = ({
componentType,
componentName,
nullStateType,
+ routePath,
}: ConfigMapSecretNullStateProps) => {
// HOOKS
- const { path, params } = useRouteMatch()
-
+ const params = useParams()
const noCMSecretPresent = nullStateType === 'NO_CM_CS'
return (
@@ -49,7 +49,7 @@ export const ConfigMapSecretNullState = ({
startIcon={
}
text={CM_SECRET_EMPTY_STATE_TEXT[componentType].buttonText}
linkProps={{
- to: generatePath(path, { ...params, name: 'create' }),
+ to: generatePath(routePath, { ...params, name: 'create' }),
}}
/>
)}
diff --git a/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretProtected.tsx b/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretProtected.tsx
index 8ca52468fc..3f69c89e00 100644
--- a/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretProtected.tsx
+++ b/src/Pages/Shared/ConfigMapSecret/ConfigMapSecretProtected.tsx
@@ -67,6 +67,7 @@ export const ConfigMapSecretProtected = ({
isExpressEditComparisonView,
handleMergeStrategyChange,
handleNoPublishedStateRedirectClick,
+ routePath,
}: ConfigMapSecretProtectedProps) => {
// HOOKS
const { data: formData } = useFormProps
@@ -299,7 +300,7 @@ export const ConfigMapSecretProtected = ({
}
if (cmSecretStateLabel === CM_SECRET_STATE.INHERITED) {
- return
+ return
}
return (
@@ -315,9 +316,13 @@ export const ConfigMapSecretProtected = ({
case ProtectConfigTabsType.EDIT_DRAFT:
if (draftData.action === DraftAction.Delete) {
return cmSecretStateLabel === CM_SECRET_STATE.OVERRIDDEN ? (
-
+
) : (
-
+
)
}
diff --git a/src/Pages/Shared/ConfigMapSecret/helpers.tsx b/src/Pages/Shared/ConfigMapSecret/helpers.tsx
index f790e56f86..3b083b562b 100644
--- a/src/Pages/Shared/ConfigMapSecret/helpers.tsx
+++ b/src/Pages/Shared/ConfigMapSecret/helpers.tsx
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
import { Link } from 'react-router-dom'
import {
@@ -23,10 +24,9 @@ import {
DocLink,
Icon,
InfoBlock,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { URLS } from '@Config/routes'
-
import { EXTERNAL_INFO_TEXT } from './constants'
export const renderESOInfo = (isESO: boolean) =>
@@ -35,7 +35,7 @@ export const renderESOInfo = (isESO: boolean) =>
description={
diff --git a/src/Pages/Shared/ConfigMapSecret/types.ts b/src/Pages/Shared/ConfigMapSecret/types.ts
index 23871922f8..5ed4d60514 100644
--- a/src/Pages/Shared/ConfigMapSecret/types.ts
+++ b/src/Pages/Shared/ConfigMapSecret/types.ts
@@ -80,6 +80,7 @@ export interface CMSecretWrapperProps
appName: string
isExceptionUser: boolean
isTemplateView: AppConfigProps['isTemplateView']
+ routePath: string
}
export interface ConfigMapSecretContainerProps extends Omit {
@@ -167,7 +168,9 @@ export interface ConfigMapSecretDeleteModalProps
handleError: (actionType: DraftAction, err: any, payloadData?: CMSecretPayloadType) => void
}
-export type ConfigMapSecretNullStateProps =
+export type ConfigMapSecretNullStateProps = {
+ routePath: string
+} & (
| {
componentType?: never
componentName: string
@@ -183,6 +186,7 @@ export type ConfigMapSecretNullStateProps =
componentName?: never
nullStateType: 'NO_CM_CS'
}
+)
export type ConfigMapSecretProtectedProps = Pick &
Pick<
@@ -210,6 +214,7 @@ export type ConfigMapSecretProtectedProps = Pick void
+ routePath: string
}
export type ConfigMapSecretDryRunProps = Pick<
@@ -238,6 +243,7 @@ export type ConfigMapSecretDryRunProps = Pick<
dryRunEditorMode: DryRunEditorMode
handleChangeDryRunEditorMode: (mode: DryRunEditorMode) => void
showCrudButtons: boolean
+ routePath: string
}
// DTO
diff --git a/src/Pages/Shared/EnvironmentOverride/EnvironmentOverride.tsx b/src/Pages/Shared/EnvironmentOverride/EnvironmentOverride.tsx
index a82e57c053..236757b786 100644
--- a/src/Pages/Shared/EnvironmentOverride/EnvironmentOverride.tsx
+++ b/src/Pages/Shared/EnvironmentOverride/EnvironmentOverride.tsx
@@ -15,15 +15,16 @@
*/
import { useEffect } from 'react'
-import { generatePath, Route, Switch, useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom'
+import { generatePath, Navigate, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
import {
ApprovalConfigDataKindType,
CMSecretComponentType,
getIsApprovalPolicyConfigured,
+ mapByKey,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ErrorBoundary, mapByKey, useAppContext } from '@Components/common'
+import { ErrorBoundary, useAppContext } from '@Components/common'
import { APP_COMPOSE_STAGE, getAppComposeURL, URLS } from '@Config/index'
import { DeploymentTemplate } from '@Pages/Applications'
import { ConfigMapSecretWrapper } from '@Pages/Shared/ConfigMapSecret/ConfigMapSecret.wrapper'
@@ -44,11 +45,11 @@ const EnvironmentOverride = ({
fetchEnvConfig,
appOrEnvIdToResourceApprovalConfigurationMap,
isTemplateView,
+ routePath,
}: EnvironmentOverrideComponentProps) => {
const isAppGroupView = !!envName
const params = useParams<{ appId: string; envId: string }>()
- const { path, url } = useRouteMatch()
- const { push } = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const { environmentId, setEnvironmentId } = useAppContext()
const environmentsMap = mapByKey(environments || [], 'environmentId')
@@ -69,11 +70,11 @@ const EnvironmentOverride = ({
return
}
if (environmentsMap.has(environmentId)) {
- const newUrl = generatePath(path, { appId: params.appId, envId: environmentId })
- push(newUrl)
+ const newUrl = generatePath(routePath, { appId: params.appId, envId: String(environmentId) })
+ navigate(newUrl)
} else {
const workflowUrl = getAppComposeURL(params.appId, APP_COMPOSE_STAGE.WORKFLOW_EDITOR, null, isTemplateView)
- push(workflowUrl)
+ navigate(workflowUrl)
}
}, [])
@@ -82,11 +83,11 @@ const EnvironmentOverride = ({
}
if (params.envId && !environmentsMap.has(+params.envId) && environments.length) {
- const newUrl = url.replace(
+ const newUrl = location.pathname.replace(
`${URLS.APP_ENV_OVERRIDE_CONFIG}/${params.envId}`,
`${URLS.APP_ENV_OVERRIDE_CONFIG}/${environments[0].environmentId}`,
)
- push(newUrl)
+ navigate(newUrl)
}
const getParentName = (): string => {
@@ -124,65 +125,79 @@ const EnvironmentOverride = ({
return (
-
-
-
-
-
-
-
-
-
-
-
+
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+ } />
+
)
diff --git a/src/Pages/Shared/EnvironmentOverride/EnvironmentOverrides.types.ts b/src/Pages/Shared/EnvironmentOverride/EnvironmentOverrides.types.ts
index 5ee6b21194..af7c5eaa4a 100644
--- a/src/Pages/Shared/EnvironmentOverride/EnvironmentOverrides.types.ts
+++ b/src/Pages/Shared/EnvironmentOverride/EnvironmentOverrides.types.ts
@@ -18,6 +18,7 @@ import { AppConfigProps } from '@devtron-labs/devtron-fe-common-lib'
import {
AppConfigState,
+ EnvConfigType,
EnvConfigurationState,
} from '@Pages/Applications/DevtronApps/Details/AppConfigurations/AppConfig.types'
@@ -32,8 +33,9 @@ export interface EnvironmentOverrideComponentProps extends Required void
+ fetchEnvConfig: (envId: number, callback?: (res: EnvConfigType) => void) => void
appOrEnvIdToResourceApprovalConfigurationMap: AppConfigState['envIdToEnvApprovalConfigurationMap']
+ routePath: string
}
export interface ListComponentType {
diff --git a/src/Pages/Shared/EnvironmentOverviewTable/EnvironmentOverviewTable.component.tsx b/src/Pages/Shared/EnvironmentOverviewTable/EnvironmentOverviewTable.component.tsx
index 1bc0a7cc02..8dad9e27c2 100644
--- a/src/Pages/Shared/EnvironmentOverviewTable/EnvironmentOverviewTable.component.tsx
+++ b/src/Pages/Shared/EnvironmentOverviewTable/EnvironmentOverviewTable.component.tsx
@@ -34,9 +34,9 @@ import {
useUrlFilters,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICActivity } from '@Icons/ic-activity.svg'
-import { ReactComponent as ICArrowLineDown } from '@Icons/ic-arrow-line-down.svg'
-import { ReactComponent as DevtronIcon } from '@Icons/ic-devtron-app.svg'
+import ICActivity from '@Icons/ic-activity.svg?react'
+import ICArrowLineDown from '@Icons/ic-arrow-line-down.svg?react'
+import DevtronIcon from '@Icons/ic-devtron-app.svg?react'
import {
EnvironmentOverviewTableHeaderFixedKeys,
diff --git a/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetail.tsx b/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetail.tsx
index b2ba021833..50bdd5d8a1 100644
--- a/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetail.tsx
+++ b/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetail.tsx
@@ -14,26 +14,19 @@
* limitations under the License.
*/
-import { Route, Switch, useRouteMatch } from 'react-router-dom'
-
-import { Drawer } from '@devtron-labs/devtron-fe-common-lib'
+import { Route, Routes } from 'react-router-dom'
import { URLS } from '../../../config'
import LinkedCIDetailsModal from './LinkedCIDetailsModal'
import { LinkedCIDetailModalProps } from './types'
-const LinkedCIDetail = ({ workflows, handleClose }: LinkedCIDetailModalProps) => {
- const { path } = useRouteMatch()
-
- return (
-
-
-
-
-
-
-
- )
-}
+const LinkedCIDetail = ({ workflows }: LinkedCIDetailModalProps) => (
+
+ }
+ />
+
+)
export default LinkedCIDetail
diff --git a/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetailsModal.tsx b/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetailsModal.tsx
index 1f2648eeed..d5729b3363 100644
--- a/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetailsModal.tsx
+++ b/src/Pages/Shared/LinkedCIDetailsModal/LinkedCIDetailsModal.tsx
@@ -15,12 +15,13 @@
*/
import { useMemo, useRef } from 'react'
-import { useParams } from 'react-router-dom'
+import { useNavigate, useParams } from 'react-router-dom'
import {
abortPreviousRequests,
CommonNodeAttr,
DEFAULT_BASE_PAGE_SIZE,
+ Drawer,
ErrorScreenNotAuthorized,
GenericEmptyState,
getIsRequestAborted,
@@ -35,7 +36,7 @@ import {
WorkflowNodeType,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Close } from '../../../assets/icons/ic-close.svg'
+import Close from '../../../assets/icons/ic-close.svg?react'
import { API_STATUS_CODES, SELECT_ALL_VALUE } from '../../../config'
import { ALL_ENVIRONMENT_OPTION, SortableKeys } from './constants'
import LinkedCIAppList from './LinkedCIAppList'
@@ -45,7 +46,8 @@ import { getLinkedCITippyContent, parseSearchParams } from './utils'
import './linkedCIAppList.scss'
-const LinkedCIDetailsModal = ({ handleClose, workflows }: LinkedCIDetailModalProps) => {
+const LinkedCIDetailsModal = ({ workflows }: LinkedCIDetailModalProps) => {
+ const navigate = useNavigate()
const { ciPipelineId } = useParams<{ ciPipelineId: string }>()
const selectedNode =
@@ -102,6 +104,10 @@ const LinkedCIDetailsModal = ({ handleClose, workflows }: LinkedCIDetailModalPro
!!result,
)
+ const handleClose = () => {
+ navigate('..')
+ }
+
const updateEnvironmentFilter = (environmentOption: OptionType) => {
updateSearchParams({
environment: environmentOption.value,
@@ -148,62 +154,64 @@ const LinkedCIDetailsModal = ({ handleClose, workflows }: LinkedCIDetailModalPro
}
return (
-
-
-
-
-
{ciPipelineName}
-
+
+
+
+
+
+
{ciPipelineName}
+
+
+
-
-
-
+ {!showLoadingState && result.totalCount > DEFAULT_BASE_PAGE_SIZE && (
+
+ )}
- {!showLoadingState && result.totalCount > DEFAULT_BASE_PAGE_SIZE && (
-
- )}
-
+
)
}
diff --git a/src/Pages/Shared/LinkedCIDetailsModal/types.ts b/src/Pages/Shared/LinkedCIDetailsModal/types.ts
index 4775244c4b..b1c6baa201 100644
--- a/src/Pages/Shared/LinkedCIDetailsModal/types.ts
+++ b/src/Pages/Shared/LinkedCIDetailsModal/types.ts
@@ -16,18 +16,17 @@
import {
BaseFilterQueryParams,
+ DEPLOYMENT_STATUS,
StatusType,
TriggerType,
UseUrlFiltersReturnType,
WorkflowType,
} from '@devtron-labs/devtron-fe-common-lib'
-import { DEPLOYMENT_STATUS } from '../../../config'
import { SortableKeys } from './constants'
export interface LinkedCIDetailModalProps {
workflows: WorkflowType[]
- handleClose: () => void
}
export interface LinkedCIAppDto {
diff --git a/src/Pages/Shared/LinkedCIDetailsModal/utils.ts b/src/Pages/Shared/LinkedCIDetailsModal/utils.ts
index e7fc0f4b22..24730e480a 100644
--- a/src/Pages/Shared/LinkedCIDetailsModal/utils.ts
+++ b/src/Pages/Shared/LinkedCIDetailsModal/utils.ts
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-import { SELECT_ALL_VALUE, URLS } from '../../../config'
+import { generatePath } from 'react-router-dom'
+
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
+
+import { SELECT_ALL_VALUE } from '../../../config'
import { ENVIRONMENT_FILTER_SEARCH_KEY } from './constants'
import { LinkedCIAppUrlProps } from './types'
@@ -27,6 +31,9 @@ export const getLinkedCITippyContent = (workflowCount: number = 0) =>
export const getLinkedCIAppUrl = ({ appId, environmentId }: LinkedCIAppUrlProps): string => {
const envId = environmentId ? `/${environmentId}` : ''
- const link = `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${URLS.APP_DETAILS}${envId}`
+ const link = generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.ENV_DETAILS, {
+ appId: String(appId),
+ envId,
+ })
return link
}
diff --git a/src/Pages/Shared/OrganizationFrame/OrganizationFrame.component.tsx b/src/Pages/Shared/OrganizationFrame/OrganizationFrame.component.tsx
index 9a7b61c22c..64071b1a45 100644
--- a/src/Pages/Shared/OrganizationFrame/OrganizationFrame.component.tsx
+++ b/src/Pages/Shared/OrganizationFrame/OrganizationFrame.component.tsx
@@ -16,7 +16,7 @@
import { getRandomColor } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICDevtron } from '../../../assets/icons/ic-devtron-blue-outline.svg'
+import ICDevtron from '../../../assets/icons/ic-devtron-blue-outline.svg?react'
import './organizationFrame.scss'
diff --git a/src/components/AppSelector/AppSelector.tsx b/src/components/AppSelector/AppSelector.tsx
index 360ae99dcb..66fef0c0c6 100644
--- a/src/components/AppSelector/AppSelector.tsx
+++ b/src/components/AppSelector/AppSelector.tsx
@@ -67,7 +67,9 @@ const AppSelector = ({ onChange, appId, appName, isJobView }: AppSelectorType) =
selectedOption: RecentlyVisitedOptions,
actionMeta: ActionMeta
>,
) => {
- if (selectedOption.label === appName) return
+ if (selectedOption.label === appName) {
+ return
+ }
onChange(selectedOption, actionMeta)
diff --git a/src/components/AppSelector/AppSelector.types.ts b/src/components/AppSelector/AppSelector.types.ts
index 58d88512da..835fbbd2f4 100644
--- a/src/components/AppSelector/AppSelector.types.ts
+++ b/src/components/AppSelector/AppSelector.types.ts
@@ -39,4 +39,5 @@ export interface ChartSelectorType {
onChange?: ({ label, value }) => void
formatOptionLabel?: ({ label, value, ...rest }) => React.ReactNode
filterOption?: (option: any, searchString: string) => boolean
+ path: string // path pattern for generatePath
}
diff --git a/src/components/AppSelector/ChartSelector.tsx b/src/components/AppSelector/ChartSelector.tsx
index a0d2754573..a55016581d 100644
--- a/src/components/AppSelector/ChartSelector.tsx
+++ b/src/components/AppSelector/ChartSelector.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useParams, useHistory, generatePath, useRouteMatch } from 'react-router-dom'
+import { useParams, generatePath, useNavigate } from 'react-router-dom'
import { GroupBase } from 'react-select'
import {
useAsync,
@@ -36,12 +36,12 @@ export default function ChartSelector({
onChange,
formatOptionLabel = null,
filterOption = null,
+ path,
}: ChartSelectorType) {
const [loading, result, error, reload] = useAsync(api, [])
const listMap = mapByKey(result?.result || [], apiPrimaryKey || primaryKey)
- const { path } = useRouteMatch()
const params = useParams()
- const { push } = useHistory()
+ const navigate = useNavigate()
const _primaryKey = Number(params[primaryKey])
const selectApp = (selected) => {
if (onChange) {
@@ -55,7 +55,7 @@ export default function ChartSelector({
const keys = listMap.get(selected.value)
const replacements = [...matchedKeys].reduce((agg, curr) => ({ ...agg, [curr]: keys[curr] }), {})
const newUrl = generatePath(path, { ...replacements, [primaryKey]: selected.value })
- push(newUrl)
+ navigate(newUrl)
}
const getChartsOptions = (): GroupBase>[] => [
diff --git a/src/components/ApplicationGroup/AppGroup.utils.ts b/src/components/ApplicationGroup/AppGroup.utils.ts
index c53937261d..2328646917 100644
--- a/src/components/ApplicationGroup/AppGroup.utils.ts
+++ b/src/components/ApplicationGroup/AppGroup.utils.ts
@@ -29,10 +29,11 @@ import {
RecentlyVisitedGroupedOptionsType,
RecentlyVisitedOptions,
BaseRecentlyVisitedEntitiesTypes,
+ ROUTER_URLS,
DeploymentNodeType,
} from '@devtron-labs/devtron-fe-common-lib'
import { getParsedBranchValuesForPlugin } from '@Components/common'
-import { DEFAULT_GIT_BRANCH_VALUE, DOCKER_FILE_ERROR_TITLE, SOURCE_NOT_CONFIGURED, URLS } from '../../config'
+import { DEFAULT_GIT_BRANCH_VALUE, DOCKER_FILE_ERROR_TITLE, SOURCE_NOT_CONFIGURED } from '../../config'
import { getEnvAppList } from './AppGroup.service'
import {
AppGroupUrlFilters,
@@ -42,6 +43,7 @@ import {
AppGroupListType,
} from './AppGroup.types'
import { getMinCharSearchPlaceholderGroup } from '@Components/AppSelector/constants'
+import { generatePath } from 'react-router-dom'
let timeoutId
@@ -362,13 +364,16 @@ export const getAppGroupDeploymentHistoryLink = (
) => {
if (!redirectToAppGroup) {
// It will redirect to application deployment history in case of other environments
- return `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${URLS.APP_CD_DETAILS}/${envId}/${pipelineId}${type ? `?type=${type}` : ''}`
+ return `${generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS, { appId: String(appId) })}/${envId}/${pipelineId}${type ? `?type=${type}` : ''}`
}
// If deployment is failed or it is PRE/POST CD node then it will redirect to history page
if (status?.toLowerCase() === DEPLOYMENT_STATUS.FAILED || type !== DeploymentNodeType.CD) {
- return `${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/${envId}/${URLS.APP_CD_DETAILS}/${appId}/${pipelineId}${type ? `?type=${type}` : ''}`
+ return `${generatePath(ROUTER_URLS.APP_GROUP_DETAILS.CD_DETAILS, { envId })}/${appId}/${pipelineId}${type ? `?type=${type}` : ''}`
}
- return `${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/${envId}/${URLS.APP_DETAILS}/${appId}`
+ return generatePath(ROUTER_URLS.APP_GROUP_DETAILS.APP_DETAILS, {
+ envId,
+ appId: String(appId),
+ })
}
export const parseAppListData = (
@@ -400,9 +405,4 @@ export const parseAppListData = (
return parsedData
}
-
-export const getDeploymentHistoryLink = (appId: number, pipelineId: number, envId: string) =>
- `${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/${envId}/cd-details/${appId}/${pipelineId}/`
-
-export const getAppRedirectLink = (appId: number, envId: number) =>
- `${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/${envId}${URLS.DETAILS}/${appId}`
+
diff --git a/src/components/ApplicationGroup/AppGroupAppFilter.components.tsx b/src/components/ApplicationGroup/AppGroupAppFilter.components.tsx
index d30048e272..55229a5234 100644
--- a/src/components/ApplicationGroup/AppGroupAppFilter.components.tsx
+++ b/src/components/ApplicationGroup/AppGroupAppFilter.components.tsx
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-import React from 'react'
+import { cloneElement, type JSX } from 'react'
import { components } from 'react-select'
import { ComponentSizeType, ConditionalWrap, TabGroup } from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import { useAppGroupAppFilterContext } from './AppGroupDetailsRoute'
import { getOptionBGClass } from './AppGroup.utils'
-import { ReactComponent as ShowIconFilter } from '../../assets/icons/ic-group-filter.svg'
-import { ReactComponent as ShowIconFilterApplied } from '../../assets/icons/ic-group-filter-applied.svg'
-import { ReactComponent as Search } from '../../assets/icons/ic-search.svg'
-import { ReactComponent as Clear } from '../../assets/icons/ic-error.svg'
-import { ReactComponent as InfoIcon } from '../../assets/icons/ic-info-outlined.svg'
-import { ReactComponent as Edit } from '../../assets/icons/ic-pencil.svg'
-import { ReactComponent as Trash } from '../../assets/icons/ic-delete-interactive.svg'
-import { ReactComponent as CheckIcon } from '../../assets/icons/ic-check.svg'
+import ShowIconFilter from '../../assets/icons/ic-group-filter.svg?react'
+import ShowIconFilterApplied from '../../assets/icons/ic-group-filter-applied.svg?react'
+import Search from '../../assets/icons/ic-search.svg?react'
+import Clear from '../../assets/icons/ic-error.svg?react'
+import InfoIcon from '../../assets/icons/ic-info-outlined.svg?react'
+import Edit from '../../assets/icons/ic-pencil.svg?react'
+import Trash from '../../assets/icons/ic-delete-interactive.svg?react'
+import CheckIcon from '../../assets/icons/ic-check.svg?react'
import { AppGroupAppFilterContextType, FilterParentType } from './AppGroup.types'
import { AppFilterTabs } from './Constants'
import { ShortcutKeyBadge } from '@Components/common/formFields/Widgets/Widgets'
@@ -78,7 +78,7 @@ export const ValueContainer = (props): JSX.Element => {
) : (
)}
- {React.cloneElement(props.children[1])}
+ {cloneElement(props.children[1])}
)
}
diff --git a/src/components/ApplicationGroup/AppGroupAppFilter.tsx b/src/components/ApplicationGroup/AppGroupAppFilter.tsx
index 3906ed90c7..51a8cdc2df 100644
--- a/src/components/ApplicationGroup/AppGroupAppFilter.tsx
+++ b/src/components/ApplicationGroup/AppGroupAppFilter.tsx
@@ -43,7 +43,7 @@ const AppGroupAppFilter = () => {
setSelectedGroupFilter,
filterParentType,
}: AppGroupAppFilterContextType = useAppGroupAppFilterContext()
- const appGroupFilterRef = useRef>()
+ const appGroupFilterRef = useRef | null>(null)
const { registerShortcut, unregisterShortcut } = useRegisterShortcut()
const [appFilterAppInput, setAppFilterAppInput] = useState('')
const [appFilterGroupInput, setAppFilterGroupInput] = useState('')
diff --git a/src/components/ApplicationGroup/AppGroupDetailsRoute.tsx b/src/components/ApplicationGroup/AppGroupDetailsRoute.tsx
index 8bf5261e4b..8dae32844d 100644
--- a/src/components/ApplicationGroup/AppGroupDetailsRoute.tsx
+++ b/src/components/ApplicationGroup/AppGroupDetailsRoute.tsx
@@ -14,39 +14,39 @@
* limitations under the License.
*/
-import React, { Suspense, useCallback, useRef, useEffect, useState, useMemo } from 'react'
-import {
- Switch,
- Route,
- Redirect,
- useParams,
- useRouteMatch,
- useHistory,
- generatePath,
- useLocation,
-} from 'react-router-dom'
+import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import ReactGA from 'react-ga4'
+import { generatePath, Navigate, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
+import { MultiValue } from 'react-select'
+
import {
- Progressing,
+ BASE_ROUTES,
BreadCrumb,
- useBreadcrumb,
- stopPropagation,
- showError,
+ DeleteConfirmationModal,
+ DOCUMENTATION,
GenericEmptyState,
- useAsync,
+ getApplicationManagementBreadcrumb,
+ OptionType,
PageHeader,
+ Progressing,
+ showError,
+ stopPropagation,
TabGroup,
TabProps,
ToastManager,
ToastVariantType,
- URLS as CommonURLS,
- DeleteConfirmationModal,
+ useAsync,
+ useBreadcrumb,
useMainContext,
- OptionType,
- getApplicationManagementBreadcrumb,
- DOCUMENTATION
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import ReactGA from 'react-ga4'
-import { MultiValue } from 'react-select'
+
+import ICSlidersVertical from '@Icons/ic-sliders-vertical.svg?react'
+import AppDetail from '@Components/app/details/appDetails/AppDetails'
+
+import EmptyFolder from '../../assets/img/empty-folder.webp'
+import { CONTEXT_NOT_AVAILABLE_ERROR, DeleteComponentsName } from '../../config/constantMessaging'
+import { EditDescRequest } from '../app/types'
import {
ErrorBoundary,
getAndSetAppGroupFilters,
@@ -54,21 +54,18 @@ import {
sortOptionsByLabel,
useAppContext,
} from '../common'
-import { URLS } from '../../config'
-import EnvTriggerView from './Details/TriggerView/EnvTriggerView'
+import EnvCDDetails from './Details/EnvCDDetails/EnvCDDetails'
+import EnvCIDetails from './Details/EnvCIDetails/EnvCIDetails'
import EnvConfig from './Details/EnvironmentConfig/EnvConfig'
import EnvironmentOverview from './Details/EnvironmentOverview/EnvironmentOverview'
-import { EnvSelector } from './EnvSelector'
-import EmptyFolder from '../../assets/img/empty-folder.webp'
-import { AppFilterTabs, EMPTY_LIST_MESSAGING, ENV_APP_GROUP_GA_EVENTS, NO_ACCESS_TOAST_MESSAGE } from './Constants'
-import { ReactComponent as ICSlidersVertical } from '@Icons/ic-sliders-vertical.svg'
+import EnvTriggerView from './Details/TriggerView/EnvTriggerView'
import {
+ appGroupPermission,
deleteEnvGroup,
+ editDescription,
getAppGroupList,
getEnvAppList,
getEnvGroupList,
- appGroupPermission,
- editDescription,
} from './AppGroup.service'
import {
AppGroupAdminType,
@@ -81,14 +78,12 @@ import {
FilterParentType,
GroupOptionType,
} from './AppGroup.types'
-import { EditDescRequest } from '../app/types'
import AppGroupAppFilter from './AppGroupAppFilter'
-import EnvCIDetails from './Details/EnvCIDetails/EnvCIDetails'
-import EnvCDDetails from './Details/EnvCDDetails/EnvCDDetails'
-import '../app/details/app.scss'
-import { CONTEXT_NOT_AVAILABLE_ERROR, DeleteComponentsName } from '../../config/constantMessaging'
+import { AppFilterTabs, EMPTY_LIST_MESSAGING, ENV_APP_GROUP_GA_EVENTS, NO_ACCESS_TOAST_MESSAGE } from './Constants'
import CreateAppGroup from './CreateAppGroup'
-import AppDetail from '@Components/app/details/appDetails/AppDetails'
+import { EnvSelector } from './EnvSelector'
+
+import '../app/details/app.scss'
export const AppGroupAppFilterContext = React.createContext(null)
@@ -100,8 +95,7 @@ export function useAppGroupAppFilterContext() {
return context
}
-export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType) {
- const { path } = useRouteMatch()
+function AppGroupDetails({ isSuperAdmin }: AppGroupAdminType) {
const { setIntelligenceConfig } = useMainContext()
const { envId } = useParams<{ envId: string }>()
const [envName, setEnvName] = useState('')
@@ -245,12 +239,12 @@ export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType
setDescription(result.description)
const _appListOptions = result.apps?.length
? result.apps
- .map((app): OptionType => {
- return {
+ .map(
+ (app): OptionType => ({
value: `${app.appId}`,
label: app.appName,
- }
- })
+ }),
+ )
.sort(sortOptionsByLabel)
: []
@@ -262,13 +256,12 @@ export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType
return _appListOptions
}
- const handleToast = (action: string) => {
- return ToastManager.showToast({
+ const handleToast = (action: string) =>
+ ToastManager.showToast({
variant: ToastVariantType.notAuthorized,
title: `Cannot ${action} filter`,
description: `You can ${action} a filter with only those applications for which you have admin/manager permission.`,
})
- }
async function getPermissionCheck(payload: CheckPermissionType, _edit?: boolean, _delete?: boolean): Promise {
try {
@@ -281,12 +274,12 @@ export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType
}
} catch (err) {
const _map = new Map()
- if (err['code'] === 403) {
+ if (err.code === 403) {
let arrUnauthorized = []
let unauthorizedCount = 0
- err['errors'].map((errors) => {
- arrUnauthorized.push([...errors['userMessage']['unauthorizedApps']])
- errors['userMessage']['unauthorizedApps'].forEach((element) => {
+ err.errors.map((errors) => {
+ arrUnauthorized.push([...errors.userMessage.unauthorizedApps])
+ errors.userMessage.unauthorizedApps.forEach((element) => {
if (!_map.get(element)) {
_map.set(element, true)
}
@@ -388,15 +381,13 @@ export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType
setShowDeleteGroup(false)
}
- const renderEmpty = () => {
- return (
-
- )
- }
+ const renderEmpty = () => (
+
+ )
const filteredAppListData = useMemo(() => {
const _appListData = { ...appGroupListData }
@@ -427,41 +418,43 @@ export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType
return (
}>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
-
-
-
-
-
-
-
+ path={`${BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.CONFIGURATIONS}/:appId?/*`}
+ element={}
+ />
+ } />
+
)
@@ -506,6 +499,14 @@ export default function AppGroupDetailsRoute({ isSuperAdmin }: AppGroupAdminType
)
}
+const AppGroupDetailsRoute = ({ isSuperAdmin }: AppGroupAdminType) => {
+ const params = useParams<{ envId: string }>()
+
+ return
+}
+
+export default AppGroupDetailsRoute
+
const EnvHeader = ({
envName,
setEnvName,
@@ -522,8 +523,7 @@ const EnvHeader = ({
isSuperAdmin,
}: EnvHeaderType) => {
const { envId } = useParams<{ envId: string }>()
- const match = useRouteMatch()
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const currentPathname = useRef('')
const { setCurrentEnvironmentName } = useAppContext()
@@ -574,9 +574,10 @@ const EnvHeader = ({
if (+envId !== value) {
setEnvName(label)
setShowEmpty(!appCount)
- const tab = currentPathname.current.replace(match.url, '').split('/')[1]
- const newUrl = generatePath(match.path, { envId: value })
- history.push(`${newUrl}/${tab}`)
+ const currentUrl = generatePath(ROUTER_URLS.APP_GROUP_DETAILS.ROOT, { envId })
+ const tab = currentPathname.current.replace(currentUrl, '').split('/')[1]
+ const newUrl = generatePath(ROUTER_URLS.APP_GROUP_DETAILS.ROOT, { envId: value })
+ navigate(`${newUrl}/${tab}`)
ReactGA.event({
category: 'Env Selector',
action: 'Env Selection Changed',
@@ -588,6 +589,7 @@ const EnvHeader = ({
)
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.APP_GROUP_DETAILS.ROOT,
{
alias: {
...getApplicationManagementBreadcrumb(),
@@ -635,7 +637,7 @@ const EnvHeader = ({
label: 'Overview',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_OVERVIEW}`,
+ to: BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.OVERVIEW,
onClick: (event) => handleEventRegistration(event, ENV_APP_GROUP_GA_EVENTS.OverviewClicked.action),
},
},
@@ -644,7 +646,7 @@ const EnvHeader = ({
label: 'App Details',
tabType: 'navLink',
props: {
- to: `${match.url}${URLS.DETAILS}`,
+ to: BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.APP_DETAILS,
onClick: (event) =>
handleEventRegistration(event, ENV_APP_GROUP_GA_EVENTS.EnvDetailsClicked.action),
},
@@ -654,7 +656,7 @@ const EnvHeader = ({
label: 'Build & Deploy',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_TRIGGER}`,
+ to: BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.TRIGGER,
onClick: (event) =>
handleEventRegistration(event, ENV_APP_GROUP_GA_EVENTS.BuildDeployClicked.action),
'data-testid': 'group-build-deploy',
@@ -665,7 +667,7 @@ const EnvHeader = ({
label: 'Build history',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_CI_DETAILS}`,
+ to: BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.CI_DETAILS,
onClick: handleEventRegistration,
'data-testid': 'app-group-build-history',
},
@@ -675,7 +677,7 @@ const EnvHeader = ({
label: 'Deployment history',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_CD_DETAILS}`,
+ to: BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.CD_DETAILS,
onClick: handleEventRegistration,
},
},
@@ -685,7 +687,7 @@ const EnvHeader = ({
tabType: 'navLink',
icon: ICSlidersVertical,
props: {
- to: `${match.url}/${CommonURLS.APP_CONFIG}`,
+ to: BASE_ROUTES.APPLICATION_MANAGEMENT.APPLICATION_GROUP.DETAIL.CONFIGURATIONS,
onClick: (event) =>
handleEventRegistration(event, ENV_APP_GROUP_GA_EVENTS.ConfigurationClicked.action),
'data-testid': 'group-configuration',
@@ -696,17 +698,15 @@ const EnvHeader = ({
return
}
- const renderBreadcrumbs = () => {
- return (
- <>
-
-
-
- {!showEmpty && }
-
- >
- )
- }
+ const renderBreadcrumbs = () => (
+ <>
+
+
+
+ {!showEmpty && }
+
+ >
+ )
return (
{
const [environmentId, setEnvironmentId] = useState(null)
const [currentEnvironmentName, setCurrentEnvironmentName] = useState('')
+ const contextValue = useMemo(
+ () => ({
+ environmentId,
+ setEnvironmentId,
+ currentEnvironmentName,
+ setCurrentEnvironmentName,
+ }),
+ [environmentId, currentEnvironmentName],
+ )
+
return (
-
-
-
-
-
+
+
+ } />
(
-
- )}
+ path={`${APP_GROUP_ROUTES.DETAIL.ROOT}/*`}
+ element={}
/>
-
-
+ } />
+
)
}
+
+export default AppGroupRoute
diff --git a/src/components/ApplicationGroup/CreateAppGroup.tsx b/src/components/ApplicationGroup/CreateAppGroup.tsx
index f02f0f4b71..7fa7f53c51 100644
--- a/src/components/ApplicationGroup/CreateAppGroup.tsx
+++ b/src/components/ApplicationGroup/CreateAppGroup.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useEffect, useRef, useState } from 'react'
+import { useEffect, useRef, useState, type JSX } from 'react'
import {
Checkbox,
CHECKBOX_VALUE,
@@ -35,9 +35,9 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'
import { useParams } from 'react-router-dom'
import Tippy from '@tippyjs/react'
-import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
-import { ReactComponent as CheckIcon } from '../../assets/icons/ic-check.svg'
-import { ReactComponent as Abort } from '../../assets/icons/ic-abort.svg'
+import Close from '../../assets/icons/ic-close.svg?react'
+import CheckIcon from '../../assets/icons/ic-check.svg?react'
+import Abort from '../../assets/icons/ic-abort.svg?react'
import Info from '../../assets/icons/ic-info-outline-grey.svg'
import { CreateGroupType, CreateTypeOfAppListType, FilterParentType } from './AppGroup.types'
import { CreateGroupTabs, CREATE_GROUP_TABS, FILTER_NAME_REGEX } from './Constants'
diff --git a/src/components/ApplicationGroup/Details/EnvCDDetails/EnvCDDetails.tsx b/src/components/ApplicationGroup/Details/EnvCDDetails/EnvCDDetails.tsx
index 97fa8ef953..660cb64ed9 100644
--- a/src/components/ApplicationGroup/Details/EnvCDDetails/EnvCDDetails.tsx
+++ b/src/components/ApplicationGroup/Details/EnvCDDetails/EnvCDDetails.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useState, useEffect } from 'react'
+import { useState, useEffect, type JSX } from 'react'
import {
Progressing,
showError,
@@ -33,8 +33,9 @@ import {
getTriggerHistory,
useScrollable,
TRIGGER_STATUS_PROGRESSING,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { useHistory, useRouteMatch, useParams, generatePath, Route } from 'react-router-dom'
+import { useParams, generatePath, useNavigate } from 'react-router-dom'
import { useAppContext } from '../../../common'
import { ModuleNameMap } from '../../../../config'
import { getModuleConfigured } from '../../../app/details/appDetails/appDetails.service'
@@ -91,8 +92,7 @@ export default function EnvCDDetails({ filteredAppIds }: AppGroupDetailDefaultTy
[pagination, appId, envId],
!!appId && !!pipelineId,
)
- const { path } = useRouteMatch()
- const { replace } = useHistory()
+ const navigate = useNavigate()
const { currentEnvironmentName } = useAppContext()
useInterval(pollHistory, 30000)
@@ -101,12 +101,13 @@ export default function EnvCDDetails({ filteredAppIds }: AppGroupDetailDefaultTy
const selectedPipelineExist = result[0]['value'].result.some((pipeline) => pipeline.id === +pipelineId)
result[0]['value'].result.sort((a, b) => sortCallback('appName', a, b))
if (!selectedPipelineExist) {
- replace(
- generatePath(path, {
+ navigate(
+ generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.CD_DETAILS}/:appId/:pipelineId`, {
envId,
appId: result[0]['value'].result[0].appId,
pipelineId: result[0]['value'].result[0].id,
}),
+ { replace: true },
)
}
setPipelineList(result[0]['value'].result)
@@ -134,13 +135,14 @@ export default function EnvCDDetails({ filteredAppIds }: AppGroupDetailDefaultTy
const triggerIdToSet = getUpdatedTriggerId(cdWorkflows[0].id, queryParam, cdWorkflows)
if (!triggerId && appId && pipelineId) {
- replace(
- generatePath(path, {
+ navigate(
+ generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.CD_DETAILS}/:appId/:pipelineId/:triggerId`, {
appId,
envId,
pipelineId,
- triggerId: triggerIdToSet,
+ triggerId: String(triggerIdToSet),
}),
+ { replace: true },
)
}
@@ -202,13 +204,14 @@ export default function EnvCDDetails({ filteredAppIds }: AppGroupDetailDefaultTy
setTriggerHistory(mapByKey(deploymentHistoryResult.result.cdWorkflows, 'id'))
}
setFetchTriggerIdData(FetchIdDataStatus.SUSPEND)
- replace(
- generatePath(path, {
+ navigate(
+ generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.CD_DETAILS}/:appId/:pipelineId/:triggerId`, {
appId,
envId,
pipelineId,
- triggerId: deploymentHistoryResult.result?.cdWorkflows?.[0]?.id,
+ triggerId: String(deploymentHistoryResult.result?.cdWorkflows?.[0]?.id),
}),
+ { replace: true },
)
}
@@ -238,11 +241,8 @@ export default function EnvCDDetails({ filteredAppIds }: AppGroupDetailDefaultTy
(pipeline) => pipeline.id === Number(pipelineId),
)?.deploymentAppType
return (
-
+ appId &&
+ pipelineId && (
-
+ )
)
}
if (!appId) {
@@ -299,6 +300,7 @@ export default function EnvCDDetails({ filteredAppIds }: AppGroupDetailDefaultTy
fetchIdData={fetchTriggerIdData}
handleViewAllHistory={handleViewAllHistory}
renderRunSource={renderRunSource}
+ path={`${ROUTER_URLS.APP_GROUP_DETAILS.CD_DETAILS}/:appId?/:pipelineId?/:triggerId?`}
/>
)}
diff --git a/src/components/ApplicationGroup/Details/EnvCDDetails/__test__/EnvCDDetails.test.tsx b/src/components/ApplicationGroup/Details/EnvCDDetails/__test__/EnvCDDetails.test.tsx
deleted file mode 100644
index e095b99dc0..0000000000
--- a/src/components/ApplicationGroup/Details/EnvCDDetails/__test__/EnvCDDetails.test.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import '@testing-library/jest-dom'
-import { act } from '@testing-library/react'
-import { Route } from 'react-router-dom'
-import { renderWithRouter } from '../../EnvironmentConfig/__test__/ApplicationRoutes.test'
-import EnvCDDetails from '../EnvCDDetails'
-import * as apiData from '../../../AppGroup.service'
-import * as appdetailsData from '../../../../app/details/appDetails/appDetails.service'
-import { mockCDList, mockCDModuleConfig } from '../__mock__/EnvCDDetails.mock'
-import { filteredData } from '../../EnvCIDetails/__mock__/EnvCIDetails.mock'
-
-describe('EnvCDDetails', () => {
- it('EnvCDDetails render without error', () => {
- const { container } = renderWithRouter(
-
-
- ,
- { route: 'application-group/1/cd-details/1/1' },
- )
- expect(container).toBeInTheDocument()
- })
-
- it('EnvCDDetails render without error async', async () => {
- let component
- jest.spyOn(apiData, 'getAppsCDConfigMin').mockImplementation(mockCDList)
- jest.spyOn(appdetailsData, 'getModuleConfigured').mockImplementation(mockCDModuleConfig)
- await act(async () => {
- component = renderWithRouter(
-
-
- ,
- { route: 'application-group/1/cd-details/1' },
- )
- })
- expect(component.container).toBeInTheDocument()
- const leftState = component.container.querySelector('.ci-details__history')
- const rightState = component.container.querySelector('.ci-details__body')
- expect(leftState).toBeInTheDocument()
- expect(rightState).toBeInTheDocument()
- })
-})
diff --git a/src/components/ApplicationGroup/Details/EnvCIDetails/EnvCIDetails.tsx b/src/components/ApplicationGroup/Details/EnvCIDetails/EnvCIDetails.tsx
index ad37b40f90..21b6013f1d 100644
--- a/src/components/ApplicationGroup/Details/EnvCIDetails/EnvCIDetails.tsx
+++ b/src/components/ApplicationGroup/Details/EnvCIDetails/EnvCIDetails.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { useEffect, useState } from 'react'
-import { generatePath, Route, useHistory, useParams, useRouteMatch } from 'react-router-dom'
+import { useEffect, useState, type JSX } from 'react'
+import { generatePath, useNavigate, useParams } from 'react-router-dom'
import {
Progressing,
showError,
@@ -34,8 +34,8 @@ import {
useScrollable,
TRIGGER_STATUS_PROGRESSING,
CiPipeline,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { URLS } from '../../../../config'
import { APP_GROUP_CI_DETAILS } from '../../../../config/constantMessaging'
import { EmptyView } from '../../../app/details/cicdHistory/History.components'
import { Details } from '../../../app/details/cIDetails/CIDetails'
@@ -71,6 +71,7 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
autoBottomScroll: triggerDetails && TRIGGER_STATUS_PROGRESSING.includes(triggerDetails.status.toLowerCase()),
})
+ const navigate = useNavigate()
useEffect(() => {
try {
@@ -89,9 +90,15 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
})
_filteredPipelines.sort((a, b) => sortCallback('appName', a, b))
if (!_filteredPipelines.length && pipelineId) {
- replace(generatePath(path, { envId }))
+ navigate(generatePath(ROUTER_URLS.APP_GROUP_DETAILS.CI_DETAILS, { envId }), { replace: true })
} else if (!selectedPipelineExist && _filteredPipelines.length) {
- replace(generatePath(path, { envId, pipelineId: _filteredPipelines[0].id }))
+ navigate(
+ generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.CI_DETAILS}/:pipelineId`, {
+ envId,
+ pipelineId: _filteredPipelines[0].id,
+ }),
+ { replace: true },
+ )
}
setPipelineList(_filteredPipelines)
}
@@ -120,8 +127,6 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
[pipelineId, pagination],
!!pipelineId,
)
- const { path } = useRouteMatch()
- const { replace } = useHistory()
useInterval(pollHistory, 30000)
useEffect(() => {
@@ -214,14 +219,23 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
setTriggerHistory(new Map(mapByKey(triggerHistoryResult.result.ciWorkflows, 'id')))
}
setFetchBuildIdData(FetchIdDataStatus.SUSPEND)
- replace(generatePath(path, { envId, pipelineId }))
+ navigate(generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.CI_DETAILS}/:pipelineId`, { envId, pipelineId }), {
+ replace: true,
+ })
}
if ((!hasMoreLoading && loading) || ciGroupLoading || (pipelineId && dependencyState[0] !== pipelineId)) {
return
}
if (!buildId && pipelineId && triggerHistory.size > 0) {
- replace(generatePath(path, { buildId: triggerHistory.entries().next().value[0], envId, pipelineId }))
+ navigate(
+ generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.CI_DETAILS}/:pipelineId/:buildId`, {
+ buildId: triggerHistory.entries().next().value[0],
+ envId,
+ pipelineId,
+ }),
+ { replace: true },
+ )
}
const pipelineOptions: CICDSidebarFilterOptionType[] = (pipelineList || []).map((item) => {
return { value: `${item.id}`, label: item.appName, pipelineId: item.id, pipelineType: item.pipelineType }
@@ -232,11 +246,8 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
const renderPipelineDetails = (): JSX.Element | null => {
if (triggerHistory.size > 0 || fetchBuildIdData) {
return (
-
+ pipelineId &&
+ buildId && (
-
+ )
)
}
if (pipeline.parentCiPipeline || pipeline.pipelineType === 'LINKED') {
@@ -260,7 +271,7 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
)
@@ -289,6 +300,7 @@ export default function EnvCIDetails({ filteredAppIds }: AppGroupDetailDefaultTy
setPagination={setPagination}
fetchIdData={fetchBuildIdData}
handleViewAllHistory={handleViewAllHistory}
+ path={`${ROUTER_URLS.APP_GROUP_DETAILS.CI_DETAILS}/:pipelineId?/:buildId?`}
/>
)}
diff --git a/src/components/ApplicationGroup/Details/EnvCIDetails/__test__/EnvCIDetails.test.tsx b/src/components/ApplicationGroup/Details/EnvCIDetails/__test__/EnvCIDetails.test.tsx
deleted file mode 100644
index 420e8acfba..0000000000
--- a/src/components/ApplicationGroup/Details/EnvCIDetails/__test__/EnvCIDetails.test.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import '@testing-library/jest-dom'
-import { act } from '@testing-library/react'
-import { Route } from 'react-router-dom'
-import EnvCIDetails from '../EnvCIDetails'
-import { renderWithRouter } from '../../EnvironmentConfig/__test__/ApplicationRoutes.test'
-import { filteredData, mockCIList, mockTrigger } from '../__mock__/EnvCIDetails.mock'
-import * as configData from '../../../AppGroup.service'
-import * as serviceData from '../../../../app/service'
-
-describe('EnvCIDetails', () => {
- it('EnvCIDetails render without error', () => {
- const { container } = renderWithRouter(
-
-
- ,
- { route: 'application-group/3/ci-details/45' },
- )
- expect(container).toBeInTheDocument()
- })
-
- it('EnvCIDetails render without error async', async () => {
- let component
- jest.spyOn(configData, 'getCIConfigList').mockImplementation(mockCIList)
- jest.spyOn(serviceData, 'getTriggerHistory').mockImplementation(mockTrigger)
- await act(async () => {
- component = renderWithRouter(
-
-
- ,
- { route: 'application-group/3/ci-details/45/363' },
- )
- })
-
- expect(component.container).toBeInTheDocument()
- const leftState = component.container.querySelector('.ci-details__history')
- const rightState = component.container.querySelector('.ci-details__body')
- expect(leftState).toBeInTheDocument()
- expect(rightState).toBeInTheDocument()
- })
-})
diff --git a/src/components/ApplicationGroup/Details/EnvironmentConfig/ApplicationRoutes.tsx b/src/components/ApplicationGroup/Details/EnvironmentConfig/ApplicationRoutes.tsx
index 11a5d2e063..99c70f233a 100644
--- a/src/components/ApplicationGroup/Details/EnvironmentConfig/ApplicationRoutes.tsx
+++ b/src/components/ApplicationGroup/Details/EnvironmentConfig/ApplicationRoutes.tsx
@@ -15,7 +15,9 @@
*/
import { Fragment } from 'react'
-import { generatePath, Route, Switch, useRouteMatch } from 'react-router-dom'
+import { generatePath, Route, Routes, useParams } from 'react-router-dom'
+
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
import { DEPLOYMENT_CONFIGURATION_RESOURCE_TYPE_ROUTE } from '@Config/constants'
import { EnvConfigurationsNav } from '@Pages/Applications/DevtronApps/Details/AppConfigurations/Navigation/EnvConfigurationsNav'
@@ -23,60 +25,71 @@ import { renderNavItem } from '@Pages/Applications/DevtronApps/Details/AppConfig
import { ApplicationRouteType } from '../../AppGroup.types'
+const path = `${ROUTER_URLS.APP_GROUP_DETAILS.CONFIGURATIONS}/:appId?`
+
const ApplicationRoute = ({
envAppList,
envConfig,
fetchEnvConfig,
appIdToAppApprovalConfigMap,
}: ApplicationRouteType) => {
- const {
- url,
- params: { envId, appId },
- path,
- } = useRouteMatch<{ envId: string; appId: string }>()
+ const { envId, appId } = useParams<{ envId: string; appId: string }>()
return (
-
+
{!!appId && (
-
-
-
+
+ }
+ />
)}
-
-
-
- APPLICATIONS
-
-
-
- {envAppList.map(({ name, id }) => (
-
- {renderNavItem({
- title: name,
- isProtectionAllowed: appIdToAppApprovalConfigMap?.[id]?.isApprovalApplicable,
- href: `${url}/${id}`,
- })}
-
- ))}
-
-
-
+
+
+
+ APPLICATIONS
+
+
+
+ {envAppList.map(({ name, id }) => (
+
+ {renderNavItem({
+ title: name,
+ isProtectionAllowed: appIdToAppApprovalConfigMap?.[id]?.isApprovalApplicable,
+ href: generatePath(path, {
+ envId,
+ appId: String(id),
+ }),
+ })}
+
+ ))}
+
+ >
+ }
+ />
+
)
}
diff --git a/src/components/ApplicationGroup/Details/EnvironmentConfig/EnvConfig.tsx b/src/components/ApplicationGroup/Details/EnvironmentConfig/EnvConfig.tsx
index 6af1588e88..be0bca1ff7 100644
--- a/src/components/ApplicationGroup/Details/EnvironmentConfig/EnvConfig.tsx
+++ b/src/components/ApplicationGroup/Details/EnvironmentConfig/EnvConfig.tsx
@@ -15,15 +15,15 @@
*/
import { useEffect, useMemo, useState } from 'react'
-import { generatePath, Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom'
+import { generatePath, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
import {
BASE_CONFIGURATION_ENV_ID,
- EnvResourceType,
GenericEmptyState,
noop,
Progressing,
ResourceIdToResourceApprovalPolicyConfigMapType,
+ ROUTER_URLS,
useAsync,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -32,7 +32,7 @@ import { DEPLOYMENT_CONFIGURATION_RESOURCE_TYPE_ROUTE } from '@Config/constants'
import { URLS } from '@Config/routes'
import { ENV_CONFIG_PATH_REG } from '@Pages/Applications/DevtronApps/Details/AppConfigurations/AppConfig.constants'
import { EnvConfigType } from '@Pages/Applications/DevtronApps/Details/AppConfigurations/AppConfig.types'
-import { DeploymentConfigCompare } from '@Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentConfigCompare'
+import { DeploymentConfigCompareWrapper } from '@Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentConfigCompare'
import { getEnvConfig } from '@Pages/Applications/DevtronApps/service'
import EnvironmentOverride from '@Pages/Shared/EnvironmentOverride/EnvironmentOverride'
@@ -45,10 +45,9 @@ const getApprovalPolicyConfigForEnv: (envId: number) => Promise {
// HOOKS
- const { path, params } = useRouteMatch<{ envId: string; appId: string }>()
- const { appId, envId } = params
+ const { appId, envId } = useParams<{ envId: string; appId: string }>()
const { pathname } = useLocation()
- const { replace } = useHistory()
+ const navigate = useNavigate()
// ASYNC CALLS
const [loading, initDataResults] = useAsync(
@@ -131,7 +130,7 @@ const EnvConfig = ({ filteredAppIds, envName }: AppGroupDetailDefaultType) => {
useEffect(() => {
// If the app is unavailable in the current environment, redirect to the app selection page
if (isAppNotPresentInEnv) {
- replace(generatePath(path, { ...params, appId: null }))
+ navigate(generatePath(ROUTER_URLS.APP_GROUP_DETAILS.CONFIGURATIONS, { envId }), { replace: true })
}
}, [isAppNotPresentInEnv])
@@ -144,70 +143,65 @@ const EnvConfig = ({ filteredAppIds, envName }: AppGroupDetailDefaultType) => {
}
return (
-
-
- {({ match, location }) => {
- const basePath = generatePath(path, match.params)
- // Used in cm/cs
- const resourceNamePath = match.params.resourceName ? `/${match.params.resourceName}` : ''
- const goBackURL =
- match.params.resourceType === EnvResourceType.Manifest ||
- match.params.resourceType === EnvResourceType.PipelineStrategy
- ? basePath
- : `${basePath}/${match.params.resourceType}${resourceNamePath}`
-
- return (
-
+ {appId && (
+
- `${generatePath(match.path, { ...match.params, resourceType, resourceName })}${location.search}`
- }
+ routePath={`${ROUTER_URLS.APP_GROUP_DETAILS.CONFIGURATIONS}/:appId/${URLS.APP_ENV_CONFIG_COMPARE}/:compareTo/${DEPLOYMENT_CONFIGURATION_RESOURCE_TYPE_ROUTE}/:resourceName?`}
+ baseGoBackURL={generatePath(ROUTER_URLS.APP_GROUP_DETAILS.CONFIGURATIONS, { envId })}
appOrEnvIdToResourceApprovalConfigurationMap={appIdToAppApprovalConfigMap}
/>
- )
- }}
-
-
-
-
- {appId ? (
-
-
+ )}
+
+
- ) : (
-
- )}
-
-
-
+ {appId ? (
+
+
+
+ ) : (
+
+ )}
+
+ }
+ />
+
)
}
diff --git a/src/components/ApplicationGroup/Details/EnvironmentConfig/__test__/ApplicationRoutes.test.tsx b/src/components/ApplicationGroup/Details/EnvironmentConfig/__test__/ApplicationRoutes.test.tsx
deleted file mode 100644
index b71860f8c2..0000000000
--- a/src/components/ApplicationGroup/Details/EnvironmentConfig/__test__/ApplicationRoutes.test.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import '@testing-library/jest-dom'
-import { render } from '@testing-library/react'
-import { BrowserRouter, Router } from 'react-router-dom'
-import ApplicationRoutes from '../ApplicationRoutes'
-import { createMemoryHistory } from 'history'
-
-export function renderWithRouter(
- ui: any,
- { route = '/', history = createMemoryHistory({ initialEntries: [route] }) }: { route: string; history?: any },
-) {
- return {
- ...render({ui}),
- history,
- }
-}
-
-describe('ApplicationRoutes', () => {
- it('ApplicationRoutes render without error', () => {
- const { container } = render(, {
- wrapper: BrowserRouter,
- })
- expect(container).toBeInTheDocument()
- })
-
- it('ApplicationRoutes render all the contents', () => {
- const { container } = renderWithRouter(
- ,
- { route: 'application-group/28/edit/129/deployment-template' },
- )
- expect(container).toBeInTheDocument()
- const routeWrapper = container.querySelector('.env-compose__nav-item.cursor')
- expect(routeWrapper).toBeInTheDocument()
- })
-})
diff --git a/src/components/ApplicationGroup/Details/EnvironmentConfig/__test__/EnvConfig.test.tsx b/src/components/ApplicationGroup/Details/EnvironmentConfig/__test__/EnvConfig.test.tsx
deleted file mode 100644
index 554c80a684..0000000000
--- a/src/components/ApplicationGroup/Details/EnvironmentConfig/__test__/EnvConfig.test.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import '@testing-library/jest-dom'
-import { act, render } from '@testing-library/react'
-import { BrowserRouter, Route, Router } from 'react-router-dom'
-import EnvConfig from '../EnvConfig'
-import * as configData from '../../../AppGroup.service'
-import { createMemoryHistory } from 'history'
-import { filteredData, mockConfigAppList } from '../__mock__/EnvConfig.mock'
-import { AppContext } from '../../../../common/Contexts/AppContext'
-
-function renderWithRouter(ui, { route = '/', history = createMemoryHistory({ initialEntries: [route] }) } = {}) {
- return {
- ...render({ui}),
- history,
- }
-}
-
-describe('EnvConfig', () => {
- it('ApplicationRoutes render without error', () => {
- const { container } = render(, {
- wrapper: BrowserRouter,
- })
- expect(container).toBeInTheDocument()
- const loadState = container.querySelector('.loading-state')
- expect(loadState).toBeInTheDocument()
- })
-
- it('Application render with data', async () => {
- let component,
- environmentId = 0
- const setEnvironmentId = jest.fn
- jest.spyOn(configData, 'getConfigAppList').mockImplementation(mockConfigAppList)
- await act(async () => {
- component = renderWithRouter(
-
-
-
-
- ,
- { route: 'application-group/4/edit/19' },
- )
- })
- expect(component.container).toBeInTheDocument()
- expect(component.container.querySelector('.env-compose')).toBeInTheDocument()
- expect(component.getByText('testing-app')).toBeInTheDocument()
- expect(component.container.querySelector('.env-compose__main')).toBeInTheDocument()
- })
-})
diff --git a/src/components/ApplicationGroup/Details/EnvironmentOverview/EnvironmentOverview.tsx b/src/components/ApplicationGroup/Details/EnvironmentOverview/EnvironmentOverview.tsx
index a4033776f3..cced20007e 100644
--- a/src/components/ApplicationGroup/Details/EnvironmentOverview/EnvironmentOverview.tsx
+++ b/src/components/ApplicationGroup/Details/EnvironmentOverview/EnvironmentOverview.tsx
@@ -15,7 +15,7 @@
*/
import { useEffect, useRef, useState } from 'react'
-import { generatePath, Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom'
+import { generatePath, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
import moment from 'moment'
import {
@@ -33,12 +33,13 @@ import {
Icon,
MODAL_TYPE,
Progressing,
+ ROUTER_URLS,
showError,
useSearchString,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as GridIcon } from '@Icons/ic-grid-view.svg'
-import { ReactComponent as GridIconBlue } from '@Icons/ic-grid-view-blue.svg'
+import GridIcon from '@Icons/ic-grid-view.svg?react'
+import GridIconBlue from '@Icons/ic-grid-view-blue.svg?react'
import { renderCIListHeader } from '@Components/app/details/cdDetails/utils'
import { getDeploymentStatus } from '@Components/ApplicationGroup/AppGroup.service'
import {
@@ -48,11 +49,7 @@ import {
ManageAppsResponse,
StatusDrawer,
} from '@Components/ApplicationGroup/AppGroup.types'
-import {
- getAppRedirectLink,
- getDeploymentHistoryLink,
- parseAppListData,
-} from '@Components/ApplicationGroup/AppGroup.utils'
+import { parseAppListData } from '@Components/ApplicationGroup/AppGroup.utils'
import { GROUP_LIST_HEADER } from '@Components/ApplicationGroup/Constants'
import { importComponentFromFELibrary } from '@Components/common'
import { Moment12HourFormat } from '@Config/constants'
@@ -85,8 +82,6 @@ const getManageTrafficMenuButtonConfig = importComponentFromFELibrary(
'function',
)
-const ENV_OVERVIEW_PATH = `${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/:envId/${URLS.APP_OVERVIEW}`
-
const EnvironmentOverview = ({
appGroupListData,
filteredAppIds,
@@ -121,11 +116,8 @@ const EnvironmentOverview = ({
const [restartLoader, setRestartLoader] = useState(false)
// HOOKS
- const {
- path,
- params: { envId },
- } = useRouteMatch<{ envId: string }>()
- const history = useHistory()
+ const { envId } = useParams<{ envId: string }>()
+ const navigate = useNavigate()
const location = useLocation()
const { searchParams } = useSearchString()
@@ -270,7 +262,7 @@ const EnvironmentOverview = ({
...searchParams,
modal: URL_SEARCH_PARAMS.BULK_RESTART_WORKLOAD,
}
- history.push({ search: new URLSearchParams(newParams).toString() })
+ navigate({ search: new URLSearchParams(newParams).toString() })
}
const closeCommitInfoModal = () => {
@@ -296,14 +288,15 @@ const EnvironmentOverview = ({
const handleBulkSelectionWidgetClose = () => setSelectedAppDetailsList([])
- const getManageTrafficPath = () => generatePath(`${ENV_OVERVIEW_PATH}/${URLS.MANAGE_TRAFFIC}`, { envId })
+ const getManageTrafficPath = () =>
+ generatePath(`${ROUTER_URLS.APP_GROUP_DETAILS.OVERVIEW}/${URLS.MANAGE_TRAFFIC}`, { envId })
const handleOpenManageTrafficDrawer = () => {
- history.push(getManageTrafficPath())
+ navigate(getManageTrafficPath())
}
const handleCloseManageTrafficDrawer = () => {
- history.push(generatePath(ENV_OVERVIEW_PATH, { envId }))
+ navigate(generatePath(ROUTER_URLS.APP_GROUP_DETAILS.OVERVIEW, { envId }))
}
// CONFIGS
@@ -362,8 +355,11 @@ const EnvironmentOverview = ({
isChecked: selectedAppDetailsList.some(({ appId }) => appId === appInfo.appId),
onLastDeployedImageClick: openCommitInfoModal(appInfo.ciArtifactId),
onCommitClick: openCommitInfoModal(appInfo.ciArtifactId),
- deployedAtLink: getDeploymentHistoryLink(appInfo.appId, appInfo.pipelineId, envId),
- redirectLink: getAppRedirectLink(appInfo.appId, +envId),
+ deployedAtLink: `${generatePath(ROUTER_URLS.APP_GROUP_DETAILS.CD_DETAILS, { envId })}/${appInfo.appId}/${appInfo.pipelineId}`,
+ redirectLink: generatePath(ROUTER_URLS.APP_GROUP_DETAILS.APP_DETAILS, {
+ appId: String(appInfo.appId),
+ envId,
+ }),
}),
)
@@ -443,19 +439,22 @@ const EnvironmentOverview = ({
return environmentOverviewTableRows.length > 0 ? (
<>
-
- {BulkManageTrafficDrawer && (
-
-
-
- )}
-
+ {BulkManageTrafficDrawer && (
+
+
+ }
+ />
+
+ )}
-
-
>
)
}
diff --git a/src/components/ApplicationGroup/Details/EnvironmentOverview/HibernateStatusRow.tsx b/src/components/ApplicationGroup/Details/EnvironmentOverview/HibernateStatusRow.tsx
index f9605e4809..8956fa5b75 100644
--- a/src/components/ApplicationGroup/Details/EnvironmentOverview/HibernateStatusRow.tsx
+++ b/src/components/ApplicationGroup/Details/EnvironmentOverview/HibernateStatusRow.tsx
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-import React from 'react'
-import { HibernateResponseRowType, HibernateStatusRowType } from '../../AppGroup.types'
-import { ReactComponent as Error } from '../../../../assets/icons/ic-error-exclamation.svg'
-import { ReactComponent as Success } from '../../../../assets/icons/appstatus/healthy.svg'
-import { ReactComponent as UnAuthorized } from '../../../../assets/icons/ic-locked.svg'
-import { ReactComponent as Skipped } from '../../../../assets/icons/ic-info-filled.svg'
+import type { JSX } from 'react'
+import { HibernateStatusRowType } from '../../AppGroup.types'
+import Error from '../../../../assets/icons/ic-error-exclamation.svg?react'
+import Success from '../../../../assets/icons/appstatus/healthy.svg?react'
+import UnAuthorized from '../../../../assets/icons/ic-locked.svg?react'
+import Skipped from '../../../../assets/icons/ic-info-filled.svg?react'
import { ACTION_STATE, DEPLOYMENT_WINDOW_TYPE } from '@devtron-labs/devtron-fe-common-lib'
import { importComponentFromFELibrary } from '../../../common'
diff --git a/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartStatusListDrawer.tsx b/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartStatusListDrawer.tsx
index 44442a0d47..1c173ec002 100644
--- a/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartStatusListDrawer.tsx
+++ b/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartStatusListDrawer.tsx
@@ -24,10 +24,10 @@ import {
InfoBlock,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Success } from '../../../../assets/icons/appstatus/healthy.svg'
-import { ReactComponent as Failed } from '../../../../assets/icons/ic-error.svg'
-import { ReactComponent as ArrowRight } from '../../../../assets/icons/ic-expand.svg'
-import { ReactComponent as MechanicalIcon } from '../../../../assets/img/ic-mechanical-operation.svg'
+import Success from '../../../../assets/icons/appstatus/healthy.svg?react'
+import Failed from '../../../../assets/icons/ic-error.svg?react'
+import ArrowRight from '../../../../assets/icons/ic-expand.svg?react'
+import MechanicalIcon from '../../../../assets/img/ic-mechanical-operation.svg?react'
import { importComponentFromFELibrary } from '../../../common'
import { BulkRotatePodsMap, ResourcesMetaDataMap, RestartStatusListDrawerProps } from '../../AppGroup.types'
import { AllExpandableDropdown } from './AllExpandableDropdown'
diff --git a/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartWorkloadModal.tsx b/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartWorkloadModal.tsx
index ee903458fd..b87645644d 100644
--- a/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartWorkloadModal.tsx
+++ b/src/components/ApplicationGroup/Details/EnvironmentOverview/RestartWorkloadModal.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { useEffect, useRef, useState } from 'react'
-import { Prompt, useHistory, useLocation } from 'react-router-dom'
+import { type JSX, useEffect, useRef, useState } from 'react'
+import { useLocation, useNavigate } from 'react-router-dom'
import {
ApiQueuingWithBatch,
@@ -37,10 +37,10 @@ import {
useSearchString,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Retry } from '../../../../assets/icons/ic-arrow-clockwise.svg'
-import { ReactComponent as DropdownIcon } from '../../../../assets/icons/ic-arrow-left.svg'
-import { ReactComponent as RotateIcon } from '../../../../assets/icons/ic-arrows_clockwise.svg'
-import { ReactComponent as MechanicalIcon } from '../../../../assets/img/ic-mechanical-operation.svg'
+import Retry from '../../../../assets/icons/ic-arrow-clockwise.svg?react'
+import DropdownIcon from '../../../../assets/icons/ic-arrow-left.svg?react'
+import RotateIcon from '../../../../assets/icons/ic-arrows_clockwise.svg?react'
+import MechanicalIcon from '../../../../assets/img/ic-mechanical-operation.svg?react'
import { importComponentFromFELibrary } from '../../../common'
import {
AppInfoMetaDataDTO,
@@ -82,7 +82,7 @@ export const RestartWorkloadModal = ({
const [isExpandableButtonClicked, setExpandableButtonClicked] = useState(false)
const { searchParams } = useSearchString()
- const history = useHistory()
+ const navigate = useNavigate()
const [showStatusModal, setShowStatusModal] = useState(false)
const location = useLocation()
@@ -116,7 +116,7 @@ export const RestartWorkloadModal = ({
delete newParams.modal
abortControllerRef.current.abort()
- history.push({ search: new URLSearchParams(newParams).toString() })
+ navigate({ search: new URLSearchParams(newParams).toString() })
onClose?.()
}
@@ -185,7 +185,7 @@ export const RestartWorkloadModal = ({
} else {
const newParams = { ...searchParams }
delete newParams.modal
- history.push({ search: new URLSearchParams(newParams).toString() })
+ navigate({ search: new URLSearchParams(newParams).toString() })
}
}
@@ -655,28 +655,24 @@ export const RestartWorkloadModal = ({
setShowResistanceBox(false)
}
return (
- <>
-
-
-
- {renderHeaderSection()}
- {renderBodySection()}
-
- {renderFooterSection()}
+
+
+
+ {renderHeaderSection()}
+ {renderBodySection()}
- {isDeploymentBlockedViaWindow && showResistanceBox && BulkDeployResistanceTippy && (
-
- )}
-
-
-
- >
+ {renderFooterSection()}
+
+ {isDeploymentBlockedViaWindow && showResistanceBox && BulkDeployResistanceTippy && (
+
+ )}
+
)
}
diff --git a/src/components/ApplicationGroup/Details/EnvironmentOverview/__test__/EnvironmentOverview.test.tsx b/src/components/ApplicationGroup/Details/EnvironmentOverview/__test__/EnvironmentOverview.test.tsx
deleted file mode 100644
index 806bfcec63..0000000000
--- a/src/components/ApplicationGroup/Details/EnvironmentOverview/__test__/EnvironmentOverview.test.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import '@testing-library/jest-dom'
-import EnvironmentOverview from '../EnvironmentOverview'
-import { act, render } from '@testing-library/react'
-import { Route, Router } from 'react-router-dom'
-import * as data from '../../../AppGroup.service'
-import { createMemoryHistory } from 'history'
-import { appListResult, filteredData, mockStatusFetch } from '../__mock__/EnvironmentOverview.mock'
-
-function renderWithRouter(ui, { route = '/', history = createMemoryHistory({ initialEntries: [route] }) } = {}) {
- return {
- ...render(
{ui}),
- history,
- }
-}
-
-describe('EnvironmentOverview', () => {
- it('EnvironmentOverview render without error', () => {
- const { container } = renderWithRouter(
-
-
- ,
- { route: 'application-group/28/overview' },
- )
- expect(container).toBeInTheDocument()
- const loaderState = container.querySelector('.loading-state')
- expect(loaderState).toBeInTheDocument()
- })
-
- it('EnvironmentOverview render with data', async () => {
- let component
- jest.spyOn(data, 'getDeploymentStatus').mockImplementation(mockStatusFetch)
- await act(async () => {
- component = renderWithRouter(
-
-
- ,
- { route: 'application-group/4/overview' },
- )
- })
- //left side component
- expect(component.container).toBeInTheDocument()
- const leftInfoComponent = component.container.querySelector('.pt-16.pb-16.pl-20.pr-20.dc__border-right')
- expect(leftInfoComponent).toBeInTheDocument()
- const infoLeftRowComponent = leftInfoComponent.querySelector('.fs-13.fw-4.lh-20.cn-9')
- expect(infoLeftRowComponent).toBeInTheDocument()
- expect(component.getByText('prakash-1mar')).toBeInTheDocument()
- expect(component.getByText('devtron-ns')).toBeInTheDocument()
- expect(component.getByText('default_cluster')).toBeInTheDocument()
- //right side component
- const rightInfoComponent = component.container.querySelector('.dc__overflow-auto')
- expect(rightInfoComponent).toBeInTheDocument()
- const infoRightRowComponent = rightInfoComponent.querySelector('.app-deployments-info-body')
- expect(infoRightRowComponent).toBeInTheDocument()
- expect(component.getAllByText('Succeeded')[0]).toBeInTheDocument()
- expect(component.getAllByText('Healthy')[0]).toBeInTheDocument()
- })
-})
diff --git a/src/components/ApplicationGroup/Details/TriggerView/BulkSourceChange.tsx b/src/components/ApplicationGroup/Details/TriggerView/BulkSourceChange.tsx
index 032261cf7b..e297c2d01c 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/BulkSourceChange.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/BulkSourceChange.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { useEffect, useRef, useState } from 'react'
+import React, { type JSX, useEffect, useRef, useState } from 'react'
import {
Button,
diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
index 490d64f362..6e712ce59e 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import React, { useEffect, useState } from 'react'
-import { Prompt, Route, Switch, useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom'
+import { MouseEvent, useEffect, useState, type JSX } from 'react'
+import { generatePath, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
import Tippy from '@tippyjs/react'
import {
@@ -28,7 +28,6 @@ import {
CHECKBOX_VALUE,
CommonNodeAttr,
ComponentSizeType,
- DEFAULT_ROUTE_PROMPT_MESSAGE,
DeploymentNodeType,
ErrorScreenManager,
Progressing,
@@ -40,6 +39,7 @@ import {
usePrompt,
WorkflowNodeType,
WorkflowType,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import { BuildImageModal, BulkBuildImageModal } from '@Components/app/details/triggerView/BuildImageModal'
@@ -48,15 +48,15 @@ import { BulkDeployModal } from '@Components/app/details/triggerView/DeployImage
import { shouldRenderWebhookAddImageModal } from '@Components/app/details/triggerView/TriggerView.utils'
import { getExternalCIConfig } from '@Components/ciPipeline/Webhook/webhook.service'
-import { ReactComponent as Dropdown } from '../../../../assets/icons/ic-chevron-down.svg'
-import { ReactComponent as Close } from '../../../../assets/icons/ic-cross.svg'
-import { ReactComponent as DeployIcon } from '../../../../assets/icons/ic-nav-rocket.svg'
-import { ReactComponent as Pencil } from '../../../../assets/icons/ic-pencil.svg'
-import { URLS, ViewType } from '../../../../config'
+import Dropdown from '../../../../assets/icons/ic-chevron-down.svg?react'
+import Close from '../../../../assets/icons/ic-cross.svg?react'
+import DeployIcon from '../../../../assets/icons/ic-nav-rocket.svg?react'
+import Pencil from '../../../../assets/icons/ic-pencil.svg?react'
+import { ViewType } from '../../../../config'
import { LinkedCIDetail } from '../../../../Pages/Shared/LinkedCIDetailsModal'
import { AppNotConfigured } from '../../../app/details/appDetails/AppDetails'
import { TRIGGER_VIEW_PARAMS } from '../../../app/details/triggerView/Constants'
-import { CIMaterialRouterProps, MATERIAL_TYPE } from '../../../app/details/triggerView/types'
+import { MATERIAL_TYPE } from '../../../app/details/triggerView/types'
import { Workflow } from '../../../app/details/triggerView/workflow/Workflow'
import { triggerBranchChange } from '../../../app/service'
import { importComponentFromFELibrary, sortObjectArrayAlphabetically } from '../../../common'
@@ -93,10 +93,8 @@ const WebhookAddImageModal = importComponentFromFELibrary('WebhookAddImageModal'
let inprogressStatusTimer
const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultType) => {
const { envId } = useParams<{ envId: string }>()
+ const navigate = useNavigate()
const location = useLocation()
- const history = useHistory()
- const match = useRouteMatch
()
- const { url } = useRouteMatch()
const [pageViewType, setPageViewType] = useState(ViewType.LOADING)
const [isBranchChangeLoading, setIsBranchChangeLoading] = useState(false)
@@ -405,9 +403,9 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
})
}
- const closeApprovalModal = (e: React.MouseEvent): void => {
+ const closeApprovalModal = (e: MouseEvent): void => {
e.stopPropagation()
- history.push({
+ navigate({
search: '',
})
getWorkflowStatusData(workflows)
@@ -417,7 +415,7 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
setShowBulkCDModal(false)
setResponseList([])
- history.push({
+ navigate({
search: '',
})
}
@@ -562,7 +560,6 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
pipelineId={node?.id}
getModuleInfo={getModuleInfo}
ciPipelineId={node?.connectingCiPipelineId}
- history={history}
/>
)
}
@@ -677,7 +674,7 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
)
const revertToPreviousURL = () => {
- history.push(match.url)
+ navigate(generatePath(ROUTER_URLS.APP_GROUP_DETAILS.TRIGGER, { envId }))
}
const getWebhookDetails = () => getExternalCIConfig(selectedWebhookNode.appId, selectedWebhookNode.id, false)
@@ -691,7 +688,7 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
}
const openCIMaterialModal = (ciNodeId: string) => {
- history.push(`${match.url}${URLS.BUILD}/${ciNodeId}`)
+ navigate(`build/${ciNodeId}`)
}
const renderWebhookAddImageModal = () => {
@@ -725,16 +722,16 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
isSelected={workflow.isSelected ?? false}
handleSelectionChange={handleSelectionChange}
fromAppGrouping
- history={history}
location={location}
- match={match}
index={index}
handleWebhookAddImageClick={handleWebhookAddImageClick(workflow.appId)}
openCIMaterialModal={openCIMaterialModal}
reloadTriggerView={reloadTriggerView}
+ navigate={navigate}
+ params={{ envId }}
/>
))}
-
+
{renderWebhookAddImageModal()}
>
)
@@ -754,23 +751,24 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
-
-
{renderWorkflow()}
-
-
-
-
-
+
+
+ }
+ />
+
- {value}
+ {`${value}`}
)
@@ -66,7 +66,7 @@ const EnvironmentNameCellComponent = ({ row, value }: TableCellComponentProps) => {
const count = (value as number) || 0
return (
-
+
{count}
{count === 0 || count === 1 ? GROUP_LIST_HEADER.APPLICATION : GROUP_LIST_HEADER.APPLICATIONS}
@@ -79,7 +79,7 @@ const EnvironmentsListView = ({
appListLoading,
appListResponse,
}: EnvironmentsListViewType) => {
- const history = useHistory()
+ const navigate = useNavigate()
const { setCurrentEnvironmentName } = useAppContext()
// Handle row click for navigation and toast notifications (also triggered by Enter key press)
@@ -102,7 +102,7 @@ const EnvironmentsListView = ({
} else {
// Navigate to the environment detail page
setCurrentEnvironmentName(environmentName)
- history.push(`${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/${environmentId}`)
+ navigate(generatePath(ROUTER_URLS.APP_GROUP_DETAILS.ROOT, { envId: String(environmentId) }))
}
}
diff --git a/src/components/ApplicationGroup/List/EnvironmentsList.tsx b/src/components/ApplicationGroup/List/EnvironmentsList.tsx
index 9cbe45369e..d2f1cf9f14 100644
--- a/src/components/ApplicationGroup/List/EnvironmentsList.tsx
+++ b/src/components/ApplicationGroup/List/EnvironmentsList.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useMemo } from 'react'
+import { type JSX, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import {
@@ -28,6 +28,7 @@ import {
getApplicationManagementBreadcrumb,
ImageType,
PageHeader,
+ ROUTER_URLS,
SearchBar,
SelectPickerOptionType,
useAsync,
@@ -207,6 +208,7 @@ const EnvironmentsList = ({ isSuperAdmin }: AppGroupAdminType) => {
const { pathname } = useLocation()
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.APP_GROUP_LIST,
{
alias: {
...getApplicationManagementBreadcrumb(),
@@ -219,7 +221,7 @@ const EnvironmentsList = ({ isSuperAdmin }: AppGroupAdminType) => {
[pathname],
)
- const renderBreadCrumbs = () =>
+ const renderBreadCrumbs = () =>
return (
diff --git a/src/components/ApplicationGroup/List/__tests__/EnvironmentListView.test.tsx b/src/components/ApplicationGroup/List/__tests__/EnvironmentListView.test.tsx
deleted file mode 100644
index 093239ee10..0000000000
--- a/src/components/ApplicationGroup/List/__tests__/EnvironmentListView.test.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import { act, render } from '@testing-library/react'
-import '@testing-library/jest-dom'
-import { BrowserRouter, Route } from 'react-router-dom'
-import EnvironmentsListView from '../EnvironmentListView'
-import * as data from '../../AppGroup.service'
-import { mockEmptyFetch, mockFetch } from '../__mocks__/EnvironmentList.mock'
-import { renderWithRouter } from '../../Details/EnvironmentConfig/__test__/ApplicationRoutes.test'
-
-describe('EnvironmentList', () => {
- it('EnvironmentList renders without crashing', () => {
- const { container } = render(
, {
- wrapper: BrowserRouter,
- })
- expect(container).toBeInTheDocument()
-
- const appGroupLoaderContainer = container.querySelector('.flex.dc__border-top-n1')
- expect(appGroupLoaderContainer).toBeInTheDocument()
-
- const appGroupListContainer = container.querySelector('.dc__overflow-auto')
- expect(appGroupListContainer).not.toBeInTheDocument()
- })
-
- it('Environment render with list', async () => {
- let component
- jest.spyOn(data, 'getEnvAppList').mockImplementation(mockFetch)
- await act(async () => {
- component = render(
, {
- wrapper: BrowserRouter,
- })
- })
-
- expect(component.container).toBeInTheDocument()
- const appGroupListContainer = component.container.querySelector('.dc__overflow-auto')
- expect(appGroupListContainer).toBeInTheDocument()
- expect(component.getAllByText('a')[0]).toBeInTheDocument()
- expect(component.getByText('shubham')).toBeInTheDocument()
- })
-
- it('EnvironmentList renders with empty state', async () => {
- let component
- jest.spyOn(data, 'getEnvAppList').mockImplementation(mockEmptyFetch)
- await act(async () => {
- component = render(
, {
- wrapper: BrowserRouter,
- })
- })
-
- expect(component.container).toBeInTheDocument()
- const emptyWrapper = component.container.querySelector('.flex.dc__border-top-n1')
- expect(emptyWrapper).toBeInTheDocument()
- const emptyComponent = emptyWrapper.querySelector('.flex.column.empty-state')
- expect(emptyComponent).toBeInTheDocument()
- expect(component.getByText('No matching env')).toBeInTheDocument()
- })
-
- it('EnvironmentList renders with empty state when ClusterId is available', async () => {
- let component
- jest.spyOn(data, 'getEnvAppList').mockImplementation(mockEmptyFetch)
- await act(async () => {
- component = renderWithRouter(
-
-
- ,
- { route: 'application-group/list?cluster=3&offset=0' },
- )
- })
- expect(component.container).toBeInTheDocument()
- const emptyWrapper = component.container.querySelector('.flex.dc__border-top-n1')
- expect(emptyWrapper).toBeInTheDocument()
- const emptyComponent = emptyWrapper.querySelector('.flex.column.empty-state')
- expect(emptyComponent).toBeInTheDocument()
- expect(component.getByText('No app groups found')).toBeInTheDocument()
- })
-})
diff --git a/src/components/CIPipelineN/Build.tsx b/src/components/CIPipelineN/Build.tsx
index bc951302ff..63c59f0fbf 100644
--- a/src/components/CIPipelineN/Build.tsx
+++ b/src/components/CIPipelineN/Build.tsx
@@ -21,13 +21,14 @@ import {
CiPipelineSourceTypeOption,
CustomInput,
SourceTypeMap,
+ Icon,
+ useMainContext,
} from '@devtron-labs/devtron-fe-common-lib'
import { ViewType } from '../../config'
import { createWebhookConditionList } from '../ciPipeline/ciPipeline.service'
import { SourceMaterials } from '../ciPipeline/SourceMaterials'
import { ValidationRules } from '../ciPipeline/validationRules'
import { BuildType, WebhookCIProps } from '../ciPipeline/types'
-import { ReactComponent as BugScanner } from '../../assets/icons/scanner.svg'
import AdvancedConfigOptions from './AdvancedConfigOptions'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { getSelectedWebhookEvent } from '@Pages/App/Configurations'
@@ -42,6 +43,8 @@ export const Build = ({
appId,
isTemplateView,
}: BuildType) => {
+ const { isEnterprise, forceDockerfileScan } = useMainContext()
+
const { formData, setFormData, formDataErrorObj, setFormDataErrorObj } = useContext(pipelineContext)
const validationRules = new ValidationRules()
const handleSourceChange = (event, gitMaterialId: number, sourceType: string): void => {
@@ -166,6 +169,12 @@ export const Build = ({
setFormData(_formData)
}
+ const handleDockerfileScanToggle = (): void => {
+ const _formData = { ...formData }
+ _formData.dockerfileScanEnabled = !_formData.dockerfileScanEnabled
+ setFormData(_formData)
+ }
+
const renderBasicCI = () => {
const _webhookData: WebhookCIProps = {
webhookConditionList: formData.webhookConditionList,
@@ -206,45 +215,63 @@ export const Build = ({
const renderPipelineName = () => {
return (
-
+
)
}
const renderScanner = () => (
- <>
-
-
-
-
-
-
Scan for vulnerabilities
-
Perform security scan after container image is built.
-
-
+
+
+
+
+
+
Scan for vulnerabilities
+
Perform security scan after container image is built.
+
+
- >
+ {isEnterprise && (
+ <>
+
+
+
+
+
+
+
Scan for recommendations
+
+ Perform linting scan of your docker file and get recommended optimizations.
+
+
+
+
+ >
+ )}
+
)
return pageState === ViewType.LOADING.toString() ? (
@@ -252,7 +279,7 @@ export const Build = ({
) : (
-
+
{renderBasicCI()}
{!isJobView && isAdvanced && (
<>
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index 17e1da1640..97e3a72a38 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -15,7 +15,7 @@
*/
import { useState, useEffect, useMemo, useRef } from 'react'
-import { Redirect, Route, Switch, useParams, useRouteMatch, useHistory, useLocation } from 'react-router-dom'
+import { Navigate, Route, Routes, useParams, useLocation, useNavigate } from 'react-router-dom'
import {
ServerErrors,
showError,
@@ -48,6 +48,7 @@ import {
GenericModal,
Button,
handleAnalyticsEvent,
+ useMainContext,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
@@ -60,11 +61,11 @@ import { BuildStageVariable, BuildTabText, JobPipelineTabText, URLS, ViewType }
import { getInitData, getInitDataWithCIPipeline, saveCIPipeline } from '../ciPipeline/ciPipeline.service'
import { ValidationRules } from '../ciPipeline/validationRules'
import { CIBuildType, CIPipelineBuildType, CIPipelineDataType, CIPipelineType } from '../ciPipeline/types'
-import { ReactComponent as Close } from '../../assets/icons/ic-cross.svg'
+import Close from '../../assets/icons/ic-cross.svg?react'
import { PreBuild } from './PreBuild'
import { Sidebar } from './Sidebar'
import { Build } from './Build'
-import { ReactComponent as WarningTriangle } from '../../assets/icons/ic-warning.svg'
+import WarningTriangle from '../../assets/icons/ic-warning.svg?react'
import { LoadingState } from '../ciConfig/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { calculateLastStepDetailsLogic, checkUniqueness, validateTask } from '../cdPipeline/cdpipeline.util'
@@ -96,8 +97,7 @@ export default function CIPipeline({
} else if (location.pathname.indexOf('/post-build') >= 0) {
activeStageName = BuildStageVariable.PostBuild
}
- const { path } = useRouteMatch()
- const history = useHistory()
+ const navigate = useNavigate()
const [pageState, setPageState] = useState(ViewType.LOADING)
const [errorCode, setErrorCode] = useState
(null)
const saveOrUpdateButtonTitle = ciPipelineId ? 'Update Pipeline' : 'Create Pipeline'
@@ -122,12 +122,15 @@ export default function CIPipeline({
const [environments, setEnvironments] = useState([])
// NOTE: don't want to show the warning until fetch; therefore true by default
const [isBlobStorageConfigured, setIsBlobStorageConfigured] = useState(true)
+
+ const { forceDockerfileScan } = useMainContext()
const [formData, setFormData] = useState({
name: '',
args: [],
materials: [],
triggerType: window._env_.DEFAULT_CI_TRIGGER_TYPE_MANUAL ? TriggerType.Manual : TriggerType.Auto,
scanEnabled: false,
+ dockerfileScanEnabled: forceDockerfileScan || false,
gitHost: undefined,
webhookEvents: [],
ciPipelineSourceTypeOptions: [],
@@ -468,7 +471,7 @@ export default function CIPipeline({
setIsAdvanced(true)
}
} else {
- const ciPipelineResponse = await getInitData(appId, true, isJobCard, isTemplateView)
+ const ciPipelineResponse = await getInitData(appId, true, isJobCard, isTemplateView, forceDockerfileScan)
if (ciPipelineResponse) {
setFormData(ciPipelineResponse.result.form)
setSecurityModuleInstalled(ciPipelineResponse.result.isSecurityModuleInstalled)
@@ -508,7 +511,7 @@ export default function CIPipeline({
`/${URLS.APP_CI_CONFIG}/`,
`/${URLS.APP_JOB_CI_CONFIG}/`,
)
- history.push(editCIPipelineURL)
+ navigate(editCIPipelineURL)
}
}, [location.pathname, ciPipeline.pipelineType])
@@ -616,7 +619,10 @@ export default function CIPipeline({
validateStage(BuildStageVariable.Build, formData)
validateStage(BuildStageVariable.PostBuild, formData)
const scanValidation =
- isJobCard || !isSecurityModuleInstalled || formData.scanEnabled || !window._env_.FORCE_SECURITY_SCANNING
+ isJobCard ||
+ !isSecurityModuleInstalled ||
+ formData.scanEnabled ||
+ !window._env_.FORCE_SECURITY_SCANNING
if (!scanValidation) {
setApiInProgress(false)
ToastManager.showToast({
@@ -679,6 +685,7 @@ export default function CIPipeline({
...formData,
materials: _materials,
scanEnabled: !isJobCard && isSecurityModuleInstalled ? formData.scanEnabled : false,
+ dockerfileScanEnabled: formData.dockerfileScanEnabled,
},
_ciPipeline,
_materials,
@@ -857,31 +864,30 @@ export default function CIPipeline({
/>
)}
-
- {isAdvanced && (
-
-
-
- )}
+
{isAdvanced && (
-
-
-
+ <>
+ } />
+ } />
+ >
)}
-
-
-
-
-
+
+ }
+ />
+
} />
+
>
diff --git a/src/components/CIPipelineN/ConditionContainer.tsx b/src/components/CIPipelineN/ConditionContainer.tsx
index ba521dabad..5bc6cb3cdb 100644
--- a/src/components/CIPipelineN/ConditionContainer.tsx
+++ b/src/components/CIPipelineN/ConditionContainer.tsx
@@ -18,7 +18,7 @@ import { ChangeEvent, useContext, useEffect, useState } from 'react'
import { ConditionType, PluginType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Dropdown } from '../../assets/icons/ic-chevron-down.svg'
+import Dropdown from '../../assets/icons/ic-chevron-down.svg?react'
import { ConditionContainerType } from '../ciPipeline/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { ConditionDataTable } from './ConditionDataTable/ConditionDataTable.component'
diff --git a/src/components/CIPipelineN/CreatePluginModal/CreatePluginFormContent.tsx b/src/components/CIPipelineN/CreatePluginModal/CreatePluginFormContent.tsx
index 66fb049264..6ddbdd7399 100644
--- a/src/components/CIPipelineN/CreatePluginModal/CreatePluginFormContent.tsx
+++ b/src/components/CIPipelineN/CreatePluginModal/CreatePluginFormContent.tsx
@@ -34,10 +34,10 @@ import {
TippyTheme,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICHelp } from '@Icons/ic-help.svg'
-import { ReactComponent as ICLegoBlock } from '@Icons/ic-lego-block.svg'
-import { ReactComponent as ICTag } from '@Icons/ic-tag.svg'
-import { ReactComponent as ICVisibilityOn } from '@Icons/ic-visibility-on.svg'
+import ICHelp from '@Icons/ic-help.svg?react'
+import ICLegoBlock from '@Icons/ic-lego-block.svg?react'
+import ICTag from '@Icons/ic-tag.svg?react'
+import ICVisibilityOn from '@Icons/ic-visibility-on.svg?react'
import CreatePluginFormField from './CreatePluginFormField'
import CreatePluginInputVariableContainer from './CreatePluginInputVariableContainer'
diff --git a/src/components/CIPipelineN/CreatePluginModal/CreatePluginModal.component.tsx b/src/components/CIPipelineN/CreatePluginModal/CreatePluginModal.component.tsx
index 04d04520f7..5fb74308ec 100644
--- a/src/components/CIPipelineN/CreatePluginModal/CreatePluginModal.component.tsx
+++ b/src/components/CIPipelineN/CreatePluginModal/CreatePluginModal.component.tsx
@@ -47,7 +47,7 @@ import {
VisibleModal2,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICCross } from '@Icons/ic-cross.svg'
+import ICCross from '@Icons/ic-cross.svg?react'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { getConditionDetailsAndVariablesFromPlugin } from '../ciPipeline.utils'
diff --git a/src/components/CIPipelineN/CreatePluginModal/types.ts b/src/components/CIPipelineN/CreatePluginModal/types.ts
index 6e574e88ed..96617de2fa 100644
--- a/src/components/CIPipelineN/CreatePluginModal/types.ts
+++ b/src/components/CIPipelineN/CreatePluginModal/types.ts
@@ -30,7 +30,7 @@ import {
VariableType,
} from '@devtron-labs/devtron-fe-common-lib'
-export interface CreatePluginModalURLParamsType {
+export type CreatePluginModalURLParamsType = {
appId: string
}
diff --git a/src/components/CIPipelineN/CustomImageTags.tsx b/src/components/CIPipelineN/CustomImageTags.tsx
index 0fbc7c0392..296485e611 100644
--- a/src/components/CIPipelineN/CustomImageTags.tsx
+++ b/src/components/CIPipelineN/CustomImageTags.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useState } from 'react'
+import { type JSX, useState } from 'react'
import Tippy from '@tippyjs/react'
import {
Collapse,
@@ -28,9 +28,9 @@ import {
import { CustomImageTagsType } from './CustomImageTag.type'
import { ValidationRules } from '../ciPipeline/validationRules'
import { CustomErrorMessage, REQUIRED_FIELD_MSG } from '../../config/constantMessaging'
-import { ReactComponent as Edit } from '../../assets/icons/ic-pencil.svg'
-import { ReactComponent as AlertTriangle } from '../../assets/icons/ic-alert-triangle.svg'
-import { ReactComponent as GeneratedImage } from '../../assets/icons/ic-generated-image.svg'
+import Edit from '../../assets/icons/ic-pencil.svg?react'
+import AlertTriangle from '../../assets/icons/ic-alert-triangle.svg?react'
+import GeneratedImage from '../../assets/icons/ic-generated-image.svg?react'
import { getCDStageTypeSelectorValue, customTagStageTypeOptions } from './ciPipeline.utils'
import '../ciPipeline/ciPipeline.scss'
diff --git a/src/components/CIPipelineN/CustomScript.tsx b/src/components/CIPipelineN/CustomScript.tsx
index 5316f0f1b3..f8bdefb2f3 100644
--- a/src/components/CIPipelineN/CustomScript.tsx
+++ b/src/components/CIPipelineN/CustomScript.tsx
@@ -18,7 +18,7 @@ import React, { useContext, useEffect, useState } from 'react'
import { CodeEditor, MODES, ScriptType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as AlertTriangle } from '../../assets/icons/ic-alert-triangle.svg'
+import AlertTriangle from '../../assets/icons/ic-alert-triangle.svg?react'
import { TaskFieldDescription, TaskFieldLabel } from '../ciPipeline/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import TaskFieldTippyDescription from './TaskFieldTippyDescription'
diff --git a/src/components/CIPipelineN/CustomScriptCard.tsx b/src/components/CIPipelineN/CustomScriptCard.tsx
index ce6827a3f3..9dc889f8f3 100644
--- a/src/components/CIPipelineN/CustomScriptCard.tsx
+++ b/src/components/CIPipelineN/CustomScriptCard.tsx
@@ -15,7 +15,7 @@
*/
import { ComponentSizeType, DocLink, Icon, stopPropagation } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICCDStage } from '@Icons/ic-cd-stage.svg'
+import ICCDStage from '@Icons/ic-cd-stage.svg?react'
import { INLINE_PLUGIN_TEXT } from './Constants'
diff --git a/src/components/CIPipelineN/EnvironmentList.tsx b/src/components/CIPipelineN/EnvironmentList.tsx
index 7e276b9b99..ef2685f012 100644
--- a/src/components/CIPipelineN/EnvironmentList.tsx
+++ b/src/components/CIPipelineN/EnvironmentList.tsx
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
import {
ComponentSizeType,
Environment,
diff --git a/src/components/CIPipelineN/MountFromHost.tsx b/src/components/CIPipelineN/MountFromHost.tsx
index 87635a3549..61f7769ae3 100644
--- a/src/components/CIPipelineN/MountFromHost.tsx
+++ b/src/components/CIPipelineN/MountFromHost.tsx
@@ -18,9 +18,9 @@ import { useContext } from 'react'
import { CustomInput } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
-import { ReactComponent as AlertTriangle } from '../../assets/icons/ic-alert-triangle.svg'
-import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
+import Add from '../../assets/icons/ic-add.svg?react'
+import AlertTriangle from '../../assets/icons/ic-alert-triangle.svg?react'
+import Close from '../../assets/icons/ic-close.svg?react'
import { MountPathMap } from '../ciPipeline/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
diff --git a/src/components/CIPipelineN/MultiplsPort.tsx b/src/components/CIPipelineN/MultiplsPort.tsx
index b625597696..6220dcb552 100644
--- a/src/components/CIPipelineN/MultiplsPort.tsx
+++ b/src/components/CIPipelineN/MultiplsPort.tsx
@@ -16,8 +16,8 @@
import React, { useContext } from 'react'
import { PortMap, TaskFieldDescription, TaskFieldLabel } from '../ciPipeline/types'
-import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Close from '../../assets/icons/ic-close.svg?react'
+import Add from '../../assets/icons/ic-add.svg?react'
import TaskFieldTippyDescription from './TaskFieldTippyDescription'
import { pipelineContext } from '../workflowEditor/workflowEditor'
diff --git a/src/components/CIPipelineN/OutputDirectoryPath.tsx b/src/components/CIPipelineN/OutputDirectoryPath.tsx
index fd030be456..cd491d5dab 100644
--- a/src/components/CIPipelineN/OutputDirectoryPath.tsx
+++ b/src/components/CIPipelineN/OutputDirectoryPath.tsx
@@ -17,8 +17,8 @@
import { useContext } from 'react'
import { CustomInput } from '@devtron-labs/devtron-fe-common-lib'
import { TaskFieldDescription, TaskFieldLabel } from '../ciPipeline/types'
-import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Close from '../../assets/icons/ic-close.svg?react'
+import Add from '../../assets/icons/ic-add.svg?react'
import TaskFieldTippyDescription from './TaskFieldTippyDescription'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { ValidationRules } from '@Components/ciPipeline/validationRules'
diff --git a/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx b/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
index 7c1da69a18..3c25732bd9 100644
--- a/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
+++ b/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
@@ -20,7 +20,7 @@ import Tippy from '@tippyjs/react'
import { RefVariableType, ToastManager, ToastVariantType, VariableType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICSave } from '@Icons/ic-save.svg'
+import ICSave from '@Icons/ic-save.svg?react'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { CreatePluginModal } from '../CreatePluginModal'
diff --git a/src/components/CIPipelineN/PluginDetailHeader/PluginDetailHeader.component.tsx b/src/components/CIPipelineN/PluginDetailHeader/PluginDetailHeader.component.tsx
index dbcc1c6d06..78e08470cf 100644
--- a/src/components/CIPipelineN/PluginDetailHeader/PluginDetailHeader.component.tsx
+++ b/src/components/CIPipelineN/PluginDetailHeader/PluginDetailHeader.component.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useContext } from 'react'
+import { type JSX, useContext } from 'react'
import {
DOCUMENTATION,
@@ -26,9 +26,9 @@ import {
Tooltip,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICBookOpen } from '@Icons/ic-book-open.svg'
-import { ReactComponent as ICCDStage } from '@Icons/ic-cd-stage.svg'
-import { ReactComponent as ICHelp } from '@Icons/ic-help.svg'
+import ICBookOpen from '@Icons/ic-book-open.svg?react'
+import ICCDStage from '@Icons/ic-cd-stage.svg?react'
+import ICHelp from '@Icons/ic-help.svg?react'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { INLINE_PLUGIN_TEXT } from '../Constants'
diff --git a/src/components/CIPipelineN/PluginDetailHeader/PluginVersionSelect.tsx b/src/components/CIPipelineN/PluginDetailHeader/PluginVersionSelect.tsx
index 56405af0ac..4745c20bb2 100644
--- a/src/components/CIPipelineN/PluginDetailHeader/PluginVersionSelect.tsx
+++ b/src/components/CIPipelineN/PluginDetailHeader/PluginVersionSelect.tsx
@@ -23,7 +23,7 @@ import {
stopPropagation,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICUpdateAnimated } from '@Icons/ic-update-animated.svg'
+import ICUpdateAnimated from '@Icons/ic-update-animated.svg?react'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { PluginVersionSelectOptionType, PluginVersionSelectProps } from '../types'
diff --git a/src/components/CIPipelineN/PreBuild.tsx b/src/components/CIPipelineN/PreBuild.tsx
index 51ad9917bc..767e3d0985 100644
--- a/src/components/CIPipelineN/PreBuild.tsx
+++ b/src/components/CIPipelineN/PreBuild.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { useEffect, useContext } from 'react'
+import React, { type JSX, useEffect, useContext } from 'react'
import {
PluginType,
ScriptType,
@@ -32,7 +32,7 @@ import EmptyPreDeployment from '../../assets/img/pre-deployment-empty.png'
import EmptyPostDeployment from '../../assets/img/post-deployment-empty.png'
import CustomScriptCard from './CustomScriptCard'
import { BuildStageVariable, ViewType } from '../../config'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Add from '../../assets/icons/ic-add.svg?react'
import { TaskDetailComponent } from './TaskDetailComponent'
import nojobs from '../../assets/img/empty-joblist.webp'
import { pipelineContext } from '../workflowEditor/workflowEditor'
diff --git a/src/components/CIPipelineN/Sidebar.tsx b/src/components/CIPipelineN/Sidebar.tsx
index f99399cc21..d519747974 100644
--- a/src/components/CIPipelineN/Sidebar.tsx
+++ b/src/components/CIPipelineN/Sidebar.tsx
@@ -33,10 +33,10 @@ import { TaskList } from './TaskList'
import { importComponentFromFELibrary } from '../common'
import { CIPipelineSidebarType } from '../ciConfig/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
-import { ReactComponent as File } from '../../assets/icons/ic-file-code.svg'
-import { ReactComponent as Key } from '../../assets/icons/ic-key-bulb.svg'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
-import { ReactComponent as Remove } from '../../assets/icons/ic-close.svg'
+import File from '../../assets/icons/ic-file-code.svg?react'
+import Key from '../../assets/icons/ic-key-bulb.svg?react'
+import Add from '../../assets/icons/ic-add.svg?react'
+import Remove from '../../assets/icons/ic-close.svg?react'
import { GeneratedHelmPush } from '../cdPipeline/cdPipeline.types'
import { EnvironmentList } from './EnvironmentList'
diff --git a/src/components/CIPipelineN/TaskList.tsx b/src/components/CIPipelineN/TaskList.tsx
index c8d9826f6b..0a7b74e441 100644
--- a/src/components/CIPipelineN/TaskList.tsx
+++ b/src/components/CIPipelineN/TaskList.tsx
@@ -35,9 +35,9 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'
import { importComponentFromFELibrary } from '@Components/common'
import TaskTitle from './TaskTitle'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
-import { ReactComponent as Drag } from '../../assets/icons/drag.svg'
-import { ReactComponent as AlertTriangle } from '../../assets/icons/ic-alert-triangle.svg'
+import Add from '../../assets/icons/ic-add.svg?react'
+import Drag from '../../assets/icons/drag.svg?react'
+import AlertTriangle from '../../assets/icons/ic-alert-triangle.svg?react'
import { TaskListType } from '../ciConfig/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { TaskActionMenuOptionIdEnum } from './Constants'
diff --git a/src/components/CIPipelineN/TaskTitle.tsx b/src/components/CIPipelineN/TaskTitle.tsx
index 0c2f15a51d..788dec0fec 100644
--- a/src/components/CIPipelineN/TaskTitle.tsx
+++ b/src/components/CIPipelineN/TaskTitle.tsx
@@ -19,10 +19,10 @@ import Tippy from '@tippyjs/react'
import { ActivityIndicator, PluginImageContainer, PluginType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICFileEdit } from '@Icons/ic-file-edit.svg'
+import ICFileEdit from '@Icons/ic-file-edit.svg?react'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
-import { ReactComponent as ICCDStage } from '../../assets/icons/ic-cd-stage.svg'
+import ICCDStage from '../../assets/icons/ic-cd-stage.svg?react'
import { TaskTitleProps, TaskTitleTippyContentProps } from './types'
const TaskTitleTippyContent = ({ isLatest, pluginName, pluginVersion, displayName }: TaskTitleTippyContentProps) => (
diff --git a/src/components/CIPipelineN/TaskTypeDetailComponent.tsx b/src/components/CIPipelineN/TaskTypeDetailComponent.tsx
index a0b300c431..59e93cbc1a 100644
--- a/src/components/CIPipelineN/TaskTypeDetailComponent.tsx
+++ b/src/components/CIPipelineN/TaskTypeDetailComponent.tsx
@@ -35,10 +35,10 @@ import MultiplePort from './MultiplsPort'
import TaskFieldTippyDescription from './TaskFieldTippyDescription'
import MountFromHost from './MountFromHost'
import CustomScript from './CustomScript'
-import { ReactComponent as AlertTriangle } from '../../assets/icons/ic-alert-triangle.svg'
+import AlertTriangle from '../../assets/icons/ic-alert-triangle.svg?react'
import { getCustomOptionSelectionStyle } from '../v2/common/ReactSelect.utils'
import { ValidationRules } from '../ciPipeline/validationRules'
-import { ReactComponent as Info } from '../../assets/icons/ic-info-filled.svg'
+import Info from '../../assets/icons/ic-info-filled.svg?react'
import { pipelineContext } from '../workflowEditor/workflowEditor'
export const TaskTypeDetailComponent = () => {
diff --git a/src/components/CIPipelineN/VariableContainer.tsx b/src/components/CIPipelineN/VariableContainer.tsx
index 8fbd49df00..0a4b0a01a0 100644
--- a/src/components/CIPipelineN/VariableContainer.tsx
+++ b/src/components/CIPipelineN/VariableContainer.tsx
@@ -15,7 +15,7 @@
*/
import { useState, useContext, useEffect } from 'react'
-import { ReactComponent as Dropdown } from '../../assets/icons/ic-chevron-down.svg'
+import Dropdown from '../../assets/icons/ic-chevron-down.svg?react'
import { PluginVariableType } from '../ciPipeline/types'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { VariableDataTable } from './VariableDataTable'
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
index 86ed6edf59..38cd27dfb4 100644
--- a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
@@ -36,10 +36,10 @@ import {
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
-import { ReactComponent as ICChoicesDropdown } from '@Icons/ic-choices-dropdown.svg'
-import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
-import { ReactComponent as ICInfoOutlineGrey } from '@Icons/ic-info-outline-grey.svg'
+import ICAdd from '@Icons/ic-add.svg?react'
+import ICChoicesDropdown from '@Icons/ic-choices-dropdown.svg?react'
+import ICClose from '@Icons/ic-close.svg?react'
+import ICInfoOutlineGrey from '@Icons/ic-info-outline-grey.svg?react'
import { importComponentFromFELibrary } from '@Components/common'
import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, FORMAT_OPTIONS_MAP } from './constants'
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index 4b8b3dd042..72347d12fe 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -31,7 +31,7 @@ import {
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
+import ICAdd from '@Icons/ic-add.svg?react'
import { PluginVariableType } from '@Components/ciPipeline/types'
import { importComponentFromFELibrary } from '@Components/common'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
index e8b92948fd..7e04607c3b 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
@@ -24,9 +24,9 @@ import {
usePopover,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
-import { ReactComponent as ICDot } from '@Icons/ic-dot.svg'
-import { ReactComponent as ICSlidersVertical } from '@Icons/ic-sliders-vertical.svg'
+import ICClose from '@Icons/ic-close.svg?react'
+import ICDot from '@Icons/ic-dot.svg?react'
+import ICSlidersVertical from '@Icons/ic-sliders-vertical.svg?react'
import { VariableDataTablePopupMenuProps } from './types'
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index 62e092a73e..8fcf84659e 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
+
import {
DynamicDataTableCellErrorType,
DynamicDataTableRowType,
diff --git a/src/components/CIPipelineN/types.ts b/src/components/CIPipelineN/types.ts
index 2e711c5de3..b5e4281c7e 100644
--- a/src/components/CIPipelineN/types.ts
+++ b/src/components/CIPipelineN/types.ts
@@ -49,7 +49,8 @@ export interface PluginVersionSelectProps extends PluginDetailHeaderProps {}
export interface PluginVersionSelectOptionType
extends OptionType
,
Pick {}
-export interface TaskDetailComponentParamsType {
+
+export type TaskDetailComponentParamsType = {
appId: string
}
diff --git a/src/components/ClusterNodes/ClusterList/ClusterListRow.tsx b/src/components/ClusterNodes/ClusterList/ClusterListRow.tsx
index aa05dcbe80..4aa7b5717d 100644
--- a/src/components/ClusterNodes/ClusterList/ClusterListRow.tsx
+++ b/src/components/ClusterNodes/ClusterList/ClusterListRow.tsx
@@ -29,12 +29,12 @@ import {
ConditionalWrap,
Icon,
InstallationClusterStatus,
- RESOURCE_BROWSER_ROUTES,
+ ROUTER_URLS,
Tooltip,
useBulkSelection,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Error } from '@Icons/ic-error-exclamation.svg'
+import Error from '@Icons/ic-error-exclamation.svg?react'
import { importComponentFromFELibrary } from '@Components/common'
import { getClusterChangeRedirectionUrl } from '@Components/ResourceBrowser/Utils'
@@ -113,8 +113,8 @@ const ClusterListRow = ({
variant={ButtonVariantType.borderLess}
component={ButtonComponentType.link}
linkProps={{
- to: generatePath(RESOURCE_BROWSER_ROUTES.TERMINAL, {
- clusterId: clusterData.id,
+ to: generatePath(ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS.TERMINAL, {
+ clusterId: String(clusterData.id),
}),
}}
/>
diff --git a/src/components/ClusterNodes/ClusterManifest.tsx b/src/components/ClusterNodes/ClusterManifest.tsx
index 42a7e60db8..6b5dd6b158 100644
--- a/src/components/ClusterNodes/ClusterManifest.tsx
+++ b/src/components/ClusterNodes/ClusterManifest.tsx
@@ -21,9 +21,9 @@ import MessageUI, { MsgUIType } from '../v2/common/message.ui'
import { getClusterManifest } from './clusterNodes.service'
import { ManifestMessaging, MESSAGING_UI, MODES } from '../../config'
import { ClusterManifestType, ManifestPopuptype } from './types'
-import { ReactComponent as Pencil } from '../../assets/icons/ic-pencil.svg'
-import { ReactComponent as WarningIcon } from '../../assets/icons/ic-warning.svg'
-import { ReactComponent as Close } from '../../assets/icons/ic-cross.svg'
+import Pencil from '../../assets/icons/ic-pencil.svg?react'
+import WarningIcon from '../../assets/icons/ic-warning.svg?react'
+import Close from '../../assets/icons/ic-cross.svg?react'
import { defaultManifestErrorText, manifestCommentsRegex } from './constants'
import { EditModeType } from '../v2/appDetails/k8Resource/nodeDetail/NodeDetailTabs/terminal/constants'
import { getTrimmedManifestData } from '../v2/appDetails/k8Resource/nodeDetail/nodeDetail.util'
diff --git a/src/components/ClusterNodes/ClusterOverview.tsx b/src/components/ClusterNodes/ClusterOverview.tsx
index 682b1945b6..02745611d7 100644
--- a/src/components/ClusterNodes/ClusterOverview.tsx
+++ b/src/components/ClusterNodes/ClusterOverview.tsx
@@ -14,20 +14,21 @@
* limitations under the License.
*/
-import React, { useEffect, useRef, useState } from 'react'
-import { generatePath, useHistory, useParams } from 'react-router-dom'
+import React, { type JSX, useEffect, useRef, useState } from 'react'
+import { generatePath, useNavigate, useParams } from 'react-router-dom'
import {
EditableTextArea,
ErrorScreenManager,
+ GenericDescription,
getRandomColor,
getUrlWithSearchParams,
Icon,
InfoIconTippy,
InstallationClusterConfigType,
noop,
- RESOURCE_BROWSER_ROUTES,
ResourceKindType,
+ ROUTER_URLS,
showError,
StatusComponent,
StatusType,
@@ -39,14 +40,14 @@ import {
import { ClusterDetailBaseParams } from '@Components/ResourceBrowser/Types'
import { getAvailableCharts } from '@Services/service'
-import { ReactComponent as Error } from '../../assets/icons/ic-error-exclamation.svg'
+import Error from '../../assets/icons/ic-error-exclamation.svg?react'
import { MAX_LENGTH_350 } from '../../config/constantMessaging'
import { importComponentFromFELibrary } from '../common'
-import GenericDescription from '../common/Description/GenericDescription'
import { K8S_EMPTY_GROUP } from '../ResourceBrowser/Constants'
import {
getClusterOverviewClusterCapacity,
getClusterOverviewDetails,
+ patchClusterNote,
updateClusterShortDescription,
} from './clusterNodes.service'
import {
@@ -62,6 +63,8 @@ const ClusterAddOns = importComponentFromFELibrary('ClusterAddOns', null, 'funct
const MigrateClusterVersionInfoBar = importComponentFromFELibrary('MigrateClusterVersionInfoBar', null, 'function')
const getInstallationClusterConfig = importComponentFromFELibrary('getInstallationClusterConfig', null, 'function')
+const RESOURCE_BROWSER_ROUTES = ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS
+
/* TODO: move into utils */
const metricsApiTippyContent = () => (
@@ -115,7 +118,7 @@ function ClusterOverview({ selectedCluster }: ClusterOverviewProps) {
const { isSuperAdmin } = useMainContext()
- const history = useHistory()
+ const navigate = useNavigate()
const [clusterConfig, setClusterConfig] = useState(null)
const requestAbortControllerRef = useRef(new AbortController())
@@ -149,19 +152,12 @@ function ClusterOverview({ selectedCluster }: ClusterOverviewProps) {
clusterNodeDetailsResponse ?? {}
const handleUpdateClusterDescription = async (description: string): Promise => {
- const requestPayload = {
- id: Number(clusterId),
- description,
- }
try {
- const response = await updateClusterShortDescription(requestPayload)
+ const response = await updateClusterShortDescription({ id: Number(clusterId), description })
if (response.result) {
setClusterNodeDetails({
...clusterNodeDetailsResponse,
- clusterDetails: {
- ...clusterDetails,
- shortDescription: description,
- },
+ clusterDetails: { ...clusterDetails, shortDescription: description },
})
}
} catch (error) {
@@ -170,6 +166,26 @@ function ClusterOverview({ selectedCluster }: ClusterOverviewProps) {
}
}
+ const handleUpdateClusterNote = async (description: string): Promise => {
+ const response = await patchClusterNote({
+ id: descriptionData.descriptionId,
+ identifier: Number(clusterId),
+ description,
+ })
+ if (response.result) {
+ setClusterNodeDetails({
+ ...clusterNodeDetailsResponse,
+ descriptionData: {
+ ...descriptionData,
+ descriptionText: response.result.description,
+ descriptionId: response.result.id,
+ descriptionUpdatedBy: response.result.updatedBy,
+ descriptionUpdatedOn: response.result.updatedOn,
+ },
+ })
+ }
+ }
+
const [isClusterCapacityDataLoading, clusterCapacityResponse] = useAsync(
() => getClusterOverviewClusterCapacity({ clusterId, requestAbortControllerRef }),
[selectedCluster],
@@ -211,7 +227,7 @@ function ClusterOverview({ selectedCluster }: ClusterOverviewProps) {
kind: 'node',
group: K8S_EMPTY_GROUP,
})}?${queryParam}=${encodeURIComponent(filterText)}`
- history.push(newUrl)
+ navigate(newUrl)
}
const renderClusterError = (): JSX.Element => {
@@ -338,7 +354,7 @@ function ClusterOverview({ selectedCluster }: ClusterOverviewProps) {
[TARGET_K8S_VERSION_SEARCH_KEY]: selectedVersion,
})
- history.push(URL)
+ navigate(URL)
}
const creationPrefix = clusterConfig ? 'Created' : 'Added'
@@ -465,13 +481,11 @@ function ClusterOverview({ selectedCluster }: ClusterOverviewProps) {
{ClusterAddOns && }
{Catalog && }
diff --git a/src/components/ClusterNodes/ClusterTerminal.tsx b/src/components/ClusterNodes/ClusterTerminal.tsx
index d67acc4616..a0c0c22088 100644
--- a/src/components/ClusterNodes/ClusterTerminal.tsx
+++ b/src/components/ClusterNodes/ClusterTerminal.tsx
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-import { useEffect, useRef, useState } from 'react'
-import { generatePath, useHistory, useLocation } from 'react-router-dom'
+import { type JSX, useEffect, useRef, useState } from 'react'
+import { generatePath, useLocation, useNavigate } from 'react-router-dom'
+import { ILink, Terminal } from '@xterm/xterm'
import {
Checkbox,
@@ -27,8 +28,8 @@ import {
NodeTaintType,
noop,
OptionType,
- RESOURCE_BROWSER_ROUTES,
ResponseType,
+ ROUTER_URLS,
SelectPickerOptionType,
ServerErrors,
showError,
@@ -76,6 +77,8 @@ import {
} from './constants'
import { ClusterTerminalType } from './types'
+const RESOURCE_BROWSER_ROUTES = ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS
+
let clusterTimeOut
const ClusterTerminal = ({
@@ -86,15 +89,15 @@ const ClusterTerminal = ({
taints,
updateTabUrl,
}: ClusterTerminalType) => {
- const { replace } = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const isAdminTerminalVisible =
location.pathname ===
generatePath(RESOURCE_BROWSER_ROUTES.TERMINAL, {
- clusterId,
+ clusterId: String(clusterId),
})
const queryParams = new URLSearchParams(location.search)
- const terminalAccessIdRef = useRef()
+ const terminalAccessIdRef = useRef(null)
const clusterShellTypes = shellTypes.filter((types) => types.label === 'sh' || types.label === 'bash')
const imageList = convertToOptionsList(clusterImageList, IMAGE_LIST.NAME, IMAGE_LIST.IMAGE)
const defaultNamespaceList = convertToOptionsList(namespaceList)
@@ -137,7 +140,7 @@ const ClusterTerminal = ({
const [hideManagedFields, setHideManagedFields] = useState
(true)
const isShellSwitched = useRef(false)
const autoSelectNodeRef = useRef(null)
- const terminalRef = useRef(null)
+ const terminalRef = useRef(null)
const prevNodeRef = useRef('')
const currNodeRef = useRef('')
const containerRef = useRef(null)
@@ -188,10 +191,10 @@ const ClusterTerminal = ({
queryParams.set('node', selectedNodeName.value)
updateTabUrl({
id: ResourceBrowserTabsId.terminal,
- url: `${generatePath(RESOURCE_BROWSER_ROUTES.TERMINAL, { clusterId })}?${queryParams.toString()}`,
+ url: `${generatePath(RESOURCE_BROWSER_ROUTES.TERMINAL, { clusterId: String(clusterId) })}?${queryParams.toString()}`,
})
if (isAdminTerminalVisible) {
- replace({ search: queryParams.toString() })
+ navigate({ search: queryParams.toString() }, { replace: true })
}
}, [selectedNodeName.value, selectedNamespace.value, selectedImage.value, selectedTerminalType.value])
@@ -286,7 +289,7 @@ const ClusterTerminal = ({
showError(error)
}
}
- return null
+ return noop
}, [manifestData, forceDelete])
useEffect(() => {
@@ -669,14 +672,38 @@ const ClusterTerminal = ({
setHideManagedFields(!hideManagedFields)
}
- const renderRegisterLinkMatcher = (terminal) => {
- const linkMatcherRegex = new RegExp(`${POD_LINKS.POD_MANIFEST}|${POD_LINKS.POD_EVENTS}`)
- terminal.registerLinkMatcher(linkMatcherRegex, (_event, text) => {
- if (text === POD_LINKS.POD_EVENTS) {
- selectEventsTab()
- } else if (text === POD_LINKS.POD_MANIFEST) {
- selectManifestTab()
- }
+ const registerLinkMatcher = (terminal: Terminal) => {
+ const podLinkTexts = [POD_LINKS.POD_MANIFEST, POD_LINKS.POD_EVENTS]
+ terminal.registerLinkProvider({
+ provideLinks: (bufferLineNumber, callback) => {
+ const line = terminal.buffer.active.getLine(bufferLineNumber - 1)
+ if (!line) {
+ callback(undefined)
+ return
+ }
+ const lineText = line.translateToString()
+ const links: ILink[] = []
+ podLinkTexts.forEach((linkText) => {
+ const index = lineText.indexOf(linkText)
+ if (index !== -1) {
+ links.push({
+ range: {
+ start: { x: index + 1, y: bufferLineNumber },
+ end: { x: index + 1 + linkText.length, y: bufferLineNumber },
+ },
+ text: linkText,
+ activate: (_event, text) => {
+ if (text === POD_LINKS.POD_EVENTS) {
+ selectEventsTab()
+ } else if (text === POD_LINKS.POD_MANIFEST) {
+ selectManifestTab()
+ }
+ },
+ })
+ }
+ })
+ callback(links.length > 0 ? links : undefined)
+ },
})
}
@@ -716,7 +743,7 @@ const ClusterTerminal = ({
- {connectTerminal && terminalView}
+ {connectTerminal && terminalView()}
{selectedTabIndex === 1 && (
@@ -1040,7 +1067,7 @@ const ClusterTerminal = ({
socketConnection,
isTerminalTab: selectedTabIndex === 0 && isAdminTerminalVisible,
sessionId,
- registerLinkMatcher: renderRegisterLinkMatcher,
+ registerLinkMatcher,
},
},
metadata: {
diff --git a/src/components/ClusterNodes/NodeDetails.tsx b/src/components/ClusterNodes/NodeDetails.tsx
index 53d9c09d10..2133daf92e 100644
--- a/src/components/ClusterNodes/NodeDetails.tsx
+++ b/src/components/ClusterNodes/NodeDetails.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { useState, useEffect, useRef, useMemo, ReactNode } from 'react'
+import React, { type JSX, useState, useEffect, useRef, useMemo } from 'react'
import {
showError,
Progressing,
@@ -41,23 +41,23 @@ import {
Button,
ButtonVariantType,
ButtonStyleType,
- RESOURCE_BROWSER_ROUTES,
getUrlWithSearchParams,
ResourceBrowserActionMenuEnum,
UNSAVED_CHANGES_PROMPT_MESSAGE,
usePrompt,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { useParams, useLocation, useHistory, generatePath, Prompt } from 'react-router-dom'
+import { useParams, useLocation, generatePath, useNavigate, Routes, Route, Navigate } from 'react-router-dom'
import YAML from 'yaml'
import * as jsonpatch from 'fast-json-patch'
import { applyPatch } from 'fast-json-patch'
-import { ReactComponent as Info } from '@Icons/ic-info-filled.svg'
-import { ReactComponent as Error } from '@Icons/ic-error-exclamation.svg'
-import { ReactComponent as AlertTriangle } from '@Icons/ic-alert-triangle.svg'
-import { ReactComponent as Storage } from '@Icons/ic-storage.svg'
-import { ReactComponent as Edit } from '@Icons/ic-pencil.svg'
-import { ReactComponent as Dropdown } from '@Icons/ic-chevron-down.svg'
-import { ReactComponent as Success } from '@Icons/appstatus/healthy.svg'
+import Info from '@Icons/ic-info-filled.svg?react'
+import Error from '@Icons/ic-error-exclamation.svg?react'
+import AlertTriangle from '@Icons/ic-alert-triangle.svg?react'
+import Storage from '@Icons/ic-storage.svg?react'
+import Edit from '@Icons/ic-pencil.svg?react'
+import Dropdown from '@Icons/ic-chevron-down.svg?react'
+import Success from '@Icons/appstatus/healthy.svg?react'
import { getNodeCapacity, updateNodeManifest } from './clusterNodes.service'
import {
ClusterListType,
@@ -68,27 +68,26 @@ import {
UpdateNodeRequestBody,
} from './types'
import { OrderBy } from '../app/list/types'
-import { MODES, URLS } from '../../config'
+import { MODES } from '../../config'
import EditTaintsModal from './NodeActions/EditTaintsModal'
import { AUTO_SELECT, CLUSTER_NODE_ACTIONS_LABELS, NODE_DETAILS_TABS } from './constants'
import CordonNodeModal from './NodeActions/CordonNodeModal'
import DrainNodeModal from './NodeActions/DrainNodeModal'
import DeleteNodeModal from './NodeActions/DeleteNodeModal'
import { K8S_EMPTY_GROUP, SIDEBAR_KEYS } from '@Components/ResourceBrowser/Constants'
-import { AppDetailsTabs } from '../v2/appDetails/appDetails.store'
import './clusterNodes.scss'
import ResourceBrowserActionMenu from '@Components/ResourceBrowser/ResourceList/ResourceBrowserActionMenu'
import { importComponentFromFELibrary } from '@Components/common'
import { unauthorizedInfoText } from '@Components/ResourceBrowser/ResourceList/utils'
const REDFISH_NODE_UI_TABS = importComponentFromFELibrary('REDFISH_NODE_UI_TABS', [], 'function')
+const RESOURCE_BROWSER_ROUTES = ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS
const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterListType) => {
const { clusterId, name } = useParams<{ clusterId: string; nodeType: string; name: string }>()
const [loader, setLoader] = useState(true)
const [apiInProgress, setApiInProgress] = useState(false)
const [isReviewState, setIsReviewStates] = useState(false)
- const [selectedTabIndex, setSelectedTabIndex] = useState(0)
const [selectedSubTabIndex, setSelectedSubTabIndex] = useState(0)
const [nodeDetail, setNodeDetail] = useState
(null)
const [modifiedManifest, setModifiedManifest] = useState('')
@@ -110,12 +109,11 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
const [isEdit, setIsEdit] = useState(false)
const [errorResponseCode, setErrorResponseCode] = useState()
const location = useLocation()
- const queryParams = new URLSearchParams(location.search)
- const { push, replace } = useHistory()
+ const navigate = useNavigate()
const hasUnsavedChanges = (nodeDetail?.manifest ? YAMLStringify(nodeDetail.manifest) : '') !== modifiedManifest
- usePrompt({ shouldPrompt: hasUnsavedChanges })
+ usePrompt({ shouldPrompt: hasUnsavedChanges, message: UNSAVED_CHANGES_PROMPT_MESSAGE })
const getData = (_patchdata: jsonpatch.Operation[]) => {
setLoader(true)
@@ -273,20 +271,6 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
},
]
- useEffect(() => {
- const tab = queryParams.get('tab')
- const tabIndex = NODE_TABS_INFO.findIndex((tabDetails) => tabDetails.id === tab)
-
- if (tabIndex !== -1) {
- setSelectedTabIndex(tabIndex)
- } else {
- replace({
- pathname: location.pathname,
- search: `?tab=${getSanitizedNodeTabId(NODE_DETAILS_TABS.summary)}`,
- })
- }
- }, [location.search])
-
const selectedResource = useMemo((): { gvk: GVKType; namespaced: boolean } => {
// Using - as a prefix since the group is empty for pods
const resourceGroupData = lowercaseKindToResourceGroupMap[`-${Nodes.Pod.toLowerCase()}`]
@@ -300,25 +284,24 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
const _tabIndex = Number(e.currentTarget.dataset.tabIndex)
if (name !== AUTO_SELECT.value) {
const selectedTab = NODE_TABS_INFO[_tabIndex]?.id || ''
- const _searchParam = `?tab=${selectedTab}`
-
updateTabUrl({
- url: `${location.pathname}${_searchParam}`,
+ url: generatePath(`${RESOURCE_BROWSER_ROUTES.NODE_DETAIL}/${selectedTab}`, { clusterId, name }),
})
}
}
const renderNodeDetailsTabs = (): JSX.Element => {
- const tabs = NODE_TABS_INFO.map(({ renderComponent, ...tabDetails }, index) => ({
- ...tabDetails,
+ const tabs: TabProps[] = NODE_TABS_INFO.map(({ icon, label, id }, index) => ({
+ id,
+ label,
+ icon,
tabType: 'navLink',
props: {
- to: `?tab=${tabDetails.id}`,
+ to: id,
onClick: changeNodeTab,
- isActive: (_, { search }) => search === `?tab=${tabDetails.id}`,
['data-tab-index']: index,
},
- })) as TabProps[]
+ }))
return (
@@ -682,7 +665,7 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
const openDebugTerminal = () => {
const queryParams = new URLSearchParams(location.search)
queryParams.set('node', nodeDetail.name)
- push(
+ navigate(
getUrlWithSearchParams(
generatePath(RESOURCE_BROWSER_ROUTES.TERMINAL, {
clusterId,
@@ -725,7 +708,7 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
const handleResourceClick = (e) => {
const { name, tab = ResourceBrowserActionMenuEnum.manifest, namespace } = e.currentTarget.dataset
- push(
+ navigate(
getUrlWithSearchParams(
generatePath(RESOURCE_BROWSER_ROUTES.K8S_RESOURCE_DETAIL, {
clusterId,
@@ -830,9 +813,10 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
}
const renderTabControls = () => {
- const selectedTab = NODE_TABS_INFO[selectedTabIndex]
+ const nodeDetailPath = generatePath(RESOURCE_BROWSER_ROUTES.NODE_DETAIL, { clusterId, name })
+ const selectedTabId = location.pathname.replace(nodeDetailPath, '').split('/')[1]
- if (selectedTab.id === getSanitizedNodeTabId(NODE_DETAILS_TABS.yaml)) {
+ if (selectedTabId === getSanitizedNodeTabId(NODE_DETAILS_TABS.yaml)) {
if (!isEdit) {
return (
@@ -1010,14 +994,14 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
}
const renderTabContent = (): JSX.Element => {
- const selectedTab = NODE_TABS_INFO[selectedTabIndex]
- const renderNode = selectedTab?.renderComponent
-
- if (renderNode) {
- return renderNode()
- }
-
- return null
+ return (
+
+ {NODE_TABS_INFO.map(({ id, renderComponent: Component }) => (
+ } />
+ ))}
+ } />
+
+ )
}
const showCordonNodeModal = (): void => {
@@ -1079,7 +1063,6 @@ const NodeDetails = ({ lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterL
) : (
<>
-
{renderNodeDetailsTabs()}
{renderTabContent()}
{showCordonNodeDialog && (
diff --git a/src/components/ClusterNodes/__mocks__/clusterAbout.mock.tsx b/src/components/ClusterNodes/__mocks__/clusterAbout.mock.tsx
index 3a07b80994..ac2bee676a 100644
--- a/src/components/ClusterNodes/__mocks__/clusterAbout.mock.tsx
+++ b/src/components/ClusterNodes/__mocks__/clusterAbout.mock.tsx
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
+
export const clusterId = '1'
export const mockSuccessResponseWithOutNote = {
code: 200,
diff --git a/src/components/ClusterNodes/__tests__/ClusterManifest.test.tsx b/src/components/ClusterNodes/__tests__/ClusterManifest.test.tsx
deleted file mode 100644
index 4395a97907..0000000000
--- a/src/components/ClusterNodes/__tests__/ClusterManifest.test.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import '@testing-library/jest-dom'
-import { act, render } from '@testing-library/react'
-import ClusterManifest from '../ClusterManifest'
-import { ClusterManifestType } from '../types'
-import { clusterManifestResponse } from '../__mocks__/ClusterManifest.mock'
-import { BrowserRouter } from 'react-router-dom'
-import * as data from '../clusterNodes.service'
-import { EditModeType } from '../../v2/appDetails/k8Resource/nodeDetail/NodeDetailTabs/terminal/constants'
-
-describe('ClusterManifest', () => {
- const mockClusterManifest: ClusterManifestType = {
- terminalAccessId: 123,
- manifestMode: EditModeType.NON_EDIT,
- setManifestMode: jest.fn(),
- setManifestData: jest.fn(),
- errorMessage: [],
- setManifestAvailable: jest.fn(),
- }
-
- it('renders loading message while fetching manifest', async () => {
- const { getByText } = render(
)
- expect(getByText('Fetching manifest')).toBeInTheDocument()
- })
-
- it('renders default manifest when in non-edit mode', async () => {
- let component
- jest.spyOn(data, 'getClusterManifest').mockImplementation(clusterManifestResponse)
- await act(async () => {
- component = render(
, {
- wrapper: BrowserRouter,
- })
- })
- const manifestComponent = component.container.querySelector('.h-100.flexbox-col')
- })
-})
diff --git a/src/components/ClusterNodes/__tests__/NodeList.test.tsx b/src/components/ClusterNodes/__tests__/NodeList.test.tsx
deleted file mode 100644
index 46b4fde31b..0000000000
--- a/src/components/ClusterNodes/__tests__/NodeList.test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import { BrowserRouter } from 'react-router-dom'
-import '@testing-library/jest-dom'
-import { act, render } from '@testing-library/react'
-import {
- clusterId,
- mockFailedResponse,
- mockSuccessResponseWithOutNote,
- mockSuccessResponseWithNote,
- mockMarkDownEditorComponent,
-} from '../__mocks__/clusterAbout.mock'
-import ClusterAbout from '../ClusterAbout'
-import ApiMethods from '@devtron-labs/devtron-fe-common-lib'
-
-jest.mock('../../charts/discoverChartDetail/DiscoverChartDetails', () => ({
- MarkDown: jest.fn(() => mockMarkDownEditorComponent),
-}))
-
-describe('Test randerAboutCluster function', () => {
- afterEach(() => {
- jest.clearAllMocks()
- })
-
- it('should render with default cluster without notes', async () => {
- let component
- const mockJsonPromise = Promise.resolve(mockSuccessResponseWithNote)
- jest.spyOn(ApiMethods, 'get').mockImplementation((url: string) => mockJsonPromise)
- await act(async () => {
- component = render(
, { wrapper: BrowserRouter })
- })
- expect(ApiMethods.get).toHaveBeenCalledTimes(1)
- expect(ApiMethods.get).toHaveBeenCalledWith(`cluster/description?id=${clusterId}`)
- expect(component.container).toBeInTheDocument()
- expect(component.getByTestId('mark-down-test-response')).toBeVisible()
- expect(component.getByTestId('cluster-name')).toBeVisible()
- expect(component.getByTestId('cluster-name')).toHaveTextContent(mockSuccessResponseWithNote.result.clusterName)
- })
-
- it('should render with default cluster with notes', async () => {
- let component
- const mockJsonPromise = Promise.resolve(mockSuccessResponseWithOutNote)
- jest.spyOn(ApiMethods, 'get').mockImplementation((url: string) => mockJsonPromise)
- await act(async () => {
- component = render(
, { wrapper: BrowserRouter })
- })
- expect(ApiMethods.get).toHaveBeenCalledTimes(1)
- expect(ApiMethods.get).toHaveBeenCalledWith(`cluster/description?id=${clusterId}`)
- expect(component.container).toBeInTheDocument()
- expect(component.getByTestId('mark-down-test-response')).toBeVisible()
- expect(component.getByTestId('cluster-name')).toBeVisible()
- expect(component.getByTestId('cluster-name')).toHaveTextContent(
- mockSuccessResponseWithOutNote.result.clusterName,
- )
- })
-
- it('should render cluster details empty state for invalid cluster id', async () => {
- let component
- const mockJsonPromise = Promise.reject(mockFailedResponse)
- jest.spyOn(ApiMethods, 'get').mockImplementation((url: string) => mockJsonPromise)
- await act(async () => {
- component = render(
, { wrapper: BrowserRouter })
- })
- expect(ApiMethods.get).toHaveBeenCalledTimes(1)
- expect(ApiMethods.get).toHaveBeenCalledWith('cluster/description?id=10010101010')
- expect(component.container).toBeInTheDocument()
- expect(component.getByTestId('generic-empty-state')).toBeVisible()
- })
-})
diff --git a/src/components/Jobs/ExpandedRow/ExpandedRow.tsx b/src/components/Jobs/ExpandedRow/ExpandedRow.tsx
index 25eddf96d8..1ada30ee11 100644
--- a/src/components/Jobs/ExpandedRow/ExpandedRow.tsx
+++ b/src/components/Jobs/ExpandedRow/ExpandedRow.tsx
@@ -14,16 +14,14 @@
* limitations under the License.
*/
-import React from 'react'
-import { Link } from 'react-router-dom'
-import { ReactComponent as Expand } from '../../../assets/icons/ic-dropdown-filled.svg'
-import { ReactComponent as Settings } from '../../../assets/icons/ic-settings.svg'
+import { generatePath, Link } from 'react-router-dom'
+import Expand from '../../../assets/icons/ic-dropdown-filled.svg?react'
+import Settings from '../../../assets/icons/ic-settings.svg?react'
import { ExpandedRowProps, Job, JobCIPipeline } from '../Types'
import './ExpandedRow.scss'
-import { URLS } from '../../../config'
import { environmentName } from '../Utils'
import { DEFAULT_ENV } from '../../app/details/triggerView/Constants'
-import { AppStatus } from '@devtron-labs/devtron-fe-common-lib'
+import { AppStatus, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
export default function ExpandedRow(props: ExpandedRowProps) {
const handleEditJob = () => {
@@ -31,7 +29,7 @@ export default function ExpandedRow(props: ExpandedRowProps) {
}
const redirectToJobPipelineDetails = (job: Job, ciPipeline: JobCIPipeline): string => {
- return `${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${job.id}/${URLS.APP_CI_DETAILS}/${ciPipeline.ciPipelineId}`
+ return `${generatePath(ROUTER_URLS.JOB_DETAIL.CI_DETAILS, { appId: String(job.id) })}/${ciPipeline.ciPipelineId}`
}
const renderRows = () => {
diff --git a/src/components/Jobs/JobDetails/JobDetails.tsx b/src/components/Jobs/JobDetails/JobDetails.tsx
index 187f740966..3f87fea4f2 100644
--- a/src/components/Jobs/JobDetails/JobDetails.tsx
+++ b/src/components/Jobs/JobDetails/JobDetails.tsx
@@ -15,44 +15,41 @@
*/
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react'
+import { generatePath, Navigate, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
+
import {
- generatePath,
- Redirect,
- Route,
- Switch,
- useHistory,
- useLocation,
- useParams,
- useRouteMatch,
-} from 'react-router-dom'
-import { APP_TYPE, URLS } from '../../../config'
-import {
+ BASE_ROUTES,
BreadCrumb,
- Progressing,
- showError,
- useBreadcrumb,
+ BreadcrumbText,
+ DOCUMENTATION,
+ getAutomationEnablementBreadcrumb,
+ handleAnalyticsEvent,
PageHeader,
+ Progressing,
ResourceKindType,
- TabProps,
+ ROUTER_URLS,
+ showError,
TabGroup,
- URLS as CommonURLS,
- handleAnalyticsEvent,
- BreadcrumbText,
- DOCUMENTATION,
+ TabProps,
+ useBreadcrumb,
} from '@devtron-labs/devtron-fe-common-lib'
+
+import Settings from '../../../assets/icons/ic-settings.svg?react'
+import { APP_TYPE } from '../../../config'
import AppConfig from '../../../Pages/Applications/DevtronApps/Details/AppConfigurations/AppConfig'
-import Overview from '../../app/Overview/Overview'
import CIDetails from '../../app/details/cIDetails/CIDetails'
import TriggerView from '../../app/details/triggerView/TriggerView'
+import Overview from '../../app/Overview/Overview'
import { getAppMetaInfo } from '../../app/service'
import { AppMetaInfo } from '../../app/types'
-import { ErrorBoundary } from '../../common'
-import { ReactComponent as Settings } from '../../../assets/icons/ic-settings.svg'
import { AppSelector } from '../../AppSelector'
+import { ErrorBoundary } from '../../common'
+
import '../../app/details/appDetails/appDetails.scss'
-export default function JobDetails() {
- const { path } = useRouteMatch()
+const JOB_DETAIL_ROUTES = BASE_ROUTES.AUTOMATION_AND_ENABLEMENT.JOBS.DETAIL
+
+const JobDetails = () => {
const { appId } = useParams<{ appId: string }>()
const [jobName, setJobName] = useState('')
const [appMetaInfo, setAppMetaInfo] = useState
()
@@ -84,25 +81,28 @@ export default function JobDetails() {
}>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ }
+ />
+ } />
+ }
+ />
+ }
+ />
+ } />
+
@@ -111,8 +111,7 @@ export default function JobDetails() {
const JobHeader = ({ jobName }: { jobName: string }) => {
const { appId } = useParams<{ appId: string }>()
- const match = useRouteMatch()
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const currentPathname = useRef('')
@@ -133,10 +132,11 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
}, [location.pathname])
const handleAppChange = useCallback(
- ({ label, value }) => {
- const tab = currentPathname.current.replace(match.url, '').split('/')[1]
- const newUrl = generatePath(match.path, { appId: value })
- history.push(`${newUrl}/${tab}`)
+ ({ value }) => {
+ const currentUrl = generatePath(ROUTER_URLS.JOB_DETAIL.ROOT, { appId })
+ const tab = currentPathname.current.replace(currentUrl, '').split('/')[1]
+ const newUrl = generatePath(ROUTER_URLS.JOB_DETAIL.ROOT, { appId: value })
+ navigate(`${newUrl}/${tab}`)
handleAnalyticsEvent({
category: 'Job Selector',
action: 'Job Selection Changed',
@@ -146,9 +146,11 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
)
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.JOB_DETAIL.ROOT,
{
alias: {
- ':appId(\\d+)': {
+ ...getAutomationEnablementBreadcrumb(),
+ ':appId': {
component: (
),
@@ -170,10 +172,10 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
label: 'Overview',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_OVERVIEW}`,
+ to: JOB_DETAIL_ROUTES.OVERVIEW,
onClick: handleEventClick,
- ['data-testid']: 'overview-link',
- ['data-action']: 'Overview Clicked',
+ 'data-testid': 'overview-link',
+ 'data-action': 'Overview Clicked',
},
},
{
@@ -181,10 +183,10 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
label: 'Trigger Job',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_TRIGGER}`,
+ to: JOB_DETAIL_ROUTES.TRIGGER,
onClick: handleEventClick,
- ['data-testid']: 'trigger-job-link',
- ['data-action']: 'Trigger Job Clicked',
+ 'data-testid': 'trigger-job-link',
+ 'data-action': 'Trigger Job Clicked',
},
},
{
@@ -192,10 +194,10 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
label: 'Run History',
tabType: 'navLink',
props: {
- to: `${match.url}/${URLS.APP_CI_DETAILS}`,
+ to: JOB_DETAIL_ROUTES.CI_DETAILS,
onClick: handleEventClick,
- ['data-testid']: 'run-history-link',
- ['data-action']: 'Run History Clicked',
+ 'data-testid': 'run-history-link',
+ 'data-action': 'Run History Clicked',
},
},
{
@@ -204,10 +206,10 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
tabType: 'navLink',
icon: Settings,
props: {
- to: `${match.url}/${CommonURLS.APP_CONFIG}`,
+ to: JOB_DETAIL_ROUTES.CONFIGURATIONS,
onClick: handleEventClick,
- ['data-testid']: 'job-config-link',
- ['data-action']: 'Job Configurations Clicked',
+ 'data-testid': 'job-config-link',
+ 'data-action': 'Job Configurations Clicked',
},
},
]
@@ -215,9 +217,7 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
return
}
- const renderBreadcrumbs = () => {
- return
- }
+ const renderBreadcrumbs = () =>
return (
@@ -231,3 +231,5 @@ const JobHeader = ({ jobName }: { jobName: string }) => {
)
}
+
+export default JobDetails
diff --git a/src/components/Jobs/JobList/JobListContainer.tsx b/src/components/Jobs/JobList/JobListContainer.tsx
index b23991e05b..4fbd0b39c4 100644
--- a/src/components/Jobs/JobList/JobListContainer.tsx
+++ b/src/components/Jobs/JobList/JobListContainer.tsx
@@ -14,24 +14,11 @@
* limitations under the License.
*/
-import { Reducer, useEffect, useMemo, useReducer, useRef } from 'react'
-import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'
+import { generatePath, useNavigate } from 'react-router-dom'
-import { FilterChips, ServerErrors, showError } from '@devtron-labs/devtron-fe-common-lib'
+import { FilterChips, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
-import { URLS } from '@Config/routes'
-
-import { JobListViewType } from '../Constants'
-import { getJobs } from '../Service'
-import {
- JobListPayload,
- JobListProps,
- JobListState,
- JobListStateAction,
- JobListStateActionTypes,
- JobListUrlFiltersType,
-} from '../Types'
-import { getInitialJobListState, jobListModal, jobListReducer } from '../Utils'
+import { JobListProps, JobListUrlFiltersType } from '../Types'
import JobListFilters from './JobListFilters'
import JobListView from './JobListView'
@@ -41,177 +28,59 @@ const JobListContainer = ({
masterFilters,
filterConfig,
clearFilters,
- handleSorting,
handleSearch,
jobListCount,
filtersLoading,
- openJobCreateModel,
setJobCount,
updateSearchParams,
getLabelFromValue,
- changePage,
- changePageSize,
}: JobListProps) => {
- const match = useRouteMatch()
- const location = useLocation()
- const history = useHistory()
-
- const { searchKey, sortBy, sortOrder, offset, pageSize, status, environment, project } = filterConfig
-
- const payload: JobListPayload = useMemo(
- () => ({
- appNameSearch: searchKey.toLowerCase(),
- appStatuses: status,
- environments: environment.map((envId) => +envId),
- teams: project.map((projectId) => +projectId),
- offset,
- size: pageSize,
- sortBy,
- sortOrder,
- }),
- [filterConfig],
- )
-
- const [state, dispatch] = useReducer
>(
- jobListReducer,
- getInitialJobListState(payload),
- )
- const abortControllerRef = useRef(new AbortController())
+ const navigate = useNavigate()
- const getJobsList = async (request): Promise => {
- const isSearchOrFilterApplied =
- request.appNameSearch?.length ||
- request.teams?.length ||
- request.appStatuses?.length ||
- request.environments?.length
- const updatedState = { ...state }
- updatedState.view = JobListViewType.LOADING
- updatedState.sortRule = {
- key: request.sortBy,
- order: request.sortOrder,
- }
- updatedState.expandedRow = {}
- updatedState.isAllExpanded = false
- dispatch({
- type: JobListStateActionTypes.multipleOptions,
- payload: updatedState,
- })
-
- if (abortControllerRef.current) {
- abortControllerRef.current.abort()
- abortControllerRef.current = null
- }
- abortControllerRef.current = new AbortController()
-
- getJobs(request, { signal: abortControllerRef.current.signal })
- .then((response) => {
- let view = JobListViewType.LIST
- if (response.result.jobCount === 0) {
- view = isSearchOrFilterApplied ? JobListViewType.NO_RESULT : JobListViewType.EMPTY
- }
- const _jobs = jobListModal(response.result?.jobContainers)
- dispatch({
- type: JobListStateActionTypes.multipleOptions,
- payload: {
- code: response.code,
- jobs: _jobs,
- isAllExpandable: _jobs.filter((job) => job.ciPipelines.length > 1).length > 0,
- view,
- offset: request.offset,
- size: response.result.jobCount,
- pageSize: request.size,
- },
- })
- abortControllerRef.current = null
- setJobCount(response.result.jobCount)
- })
- .catch((errors: ServerErrors) => {
- if (errors.code) {
- showError(errors)
-
- dispatch({
- type: JobListStateActionTypes.multipleOptions,
- payload: { code: errors.code, view: JobListViewType.ERROR },
- })
- }
- })
- }
-
- useEffect(() => {
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- getJobsList(payload)
- }, [payload])
-
- const expandRow = (id: number): void => {
- dispatch({
- type: JobListStateActionTypes.expandedRow,
- payload: { ...state.expandedRow, [id]: true },
- })
- }
-
- const closeExpandedRow = (id: number): void => {
- dispatch({
- type: JobListStateActionTypes.expandedRow,
- payload: { ...state.expandedRow, [id]: false },
- })
- }
-
- const toggleExpandAllRow = (): void => {
- const _expandedRow = {}
- if (!state.isAllExpanded) {
- state.jobs.forEach((job) => {
- _expandedRow[job.id] = job.ciPipelines.length > 1
- })
- }
-
- dispatch({
- type: JobListStateActionTypes.multipleOptions,
- payload: { expandedRow: _expandedRow, isAllExpanded: !state.isAllExpanded },
- })
- }
+ const { searchKey, status, environment, project } = filterConfig
const handleEditJob = (jobId: number): void => {
- history.push(`${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${jobId}/edit`)
+ navigate(generatePath(ROUTER_URLS.JOB_DETAIL.CONFIGURATIONS, { appId: String(jobId) }))
}
return (
<>
- {state.view !== JobListViewType.EMPTY && state.view !== JobListViewType.ERROR && (
- <>
-
-
- filterConfig={{ status, environment, project }}
- clearFilters={clearFilters}
- onRemoveFilter={updateSearchParams}
- className="px-20"
- getFormattedValue={getLabelFromValue}
- />
- >
- )}
+ +envId),
+ teams: project.map((projectId) => +projectId),
+ offset: filterConfig.offset,
+ size: filterConfig.pageSize,
+ sortBy: filterConfig.sortBy,
+ sortOrder: filterConfig.sortOrder,
+ }}
+ filtersLoading={filtersLoading}
+ handleSearch={handleSearch}
+ updateSearchParams={updateSearchParams}
+ getLabelFromValue={getLabelFromValue}
+ />
+
+ filterConfig={{ status, environment, project }}
+ clearFilters={clearFilters}
+ onRemoveFilter={updateSearchParams}
+ className="px-20"
+ getFormattedValue={getLabelFromValue}
+ />
>
)
diff --git a/src/components/Jobs/JobList/JobListTableCellComponents.tsx b/src/components/Jobs/JobList/JobListTableCellComponents.tsx
new file mode 100644
index 0000000000..de07378a46
--- /dev/null
+++ b/src/components/Jobs/JobList/JobListTableCellComponents.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2024. Devtron Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * 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.
+ */
+
+import { generatePath, Link } from 'react-router-dom'
+
+import { AppStatus, FiltersTypeEnum, ROUTER_URLS, TableCellComponentProps } from '@devtron-labs/devtron-fe-common-lib'
+
+import { JobTableAdditionalProps, JobTableRowData } from './types'
+
+// Name Cell Component with Job Icon
+export const JobNameCellComponent = ({
+ row: { data },
+ isExpandedRow,
+}: TableCellComponentProps) => {
+ if (isExpandedRow) {
+ // For expanded rows, show pipeline name
+ return {data.pipeline?.ciPipelineName || '-'}
+ }
+
+ // For main rows, show job name with icon
+ const redirectToJobOverview = generatePath(ROUTER_URLS.JOB_DETAIL.OVERVIEW, { appId: String(data.id) })
+
+ return (
+
+ {data.name}
+
+ )
+}
+
+// Status Cell Component
+export const JobStatusCellComponent = ({
+ row: { data },
+ isExpandedRow,
+}: TableCellComponentProps) => {
+ const pipeline = isExpandedRow ? data.pipeline : data.defaultPipeline
+ return
+}
diff --git a/src/components/Jobs/JobList/JobListView.tsx b/src/components/Jobs/JobList/JobListView.tsx
index aabf61bc65..d0ddceecf7 100644
--- a/src/components/Jobs/JobList/JobListView.tsx
+++ b/src/components/Jobs/JobList/JobListView.tsx
@@ -14,212 +14,143 @@
* limitations under the License.
*/
-import React from 'react'
-import {
- AppStatus,
- ErrorScreenManager,
- Progressing,
- DEFAULT_BASE_PAGE_SIZE,
- Pagination,
- SortableTableHeaderCell,
- handleAnalyticsEvent,
-} from '@devtron-labs/devtron-fe-common-lib'
-import { Link, useHistory, useLocation } from 'react-router-dom'
-import { ReactComponent as Edit } from '../../../assets/icons/ic-settings.svg'
-import { ReactComponent as JobIcon } from '../../../assets/icons/ic-job-node.svg'
-import { ReactComponent as Arrow } from '../../../assets/icons/ic-dropdown-filled.svg'
-import { SortBy } from '../../app/list/types'
-import { Job, JobListViewProps, JobsListSortableKeys } from '../Types'
-import { JobListViewType, JOB_LIST_HEADERS } from '../Constants'
-import ExpandedRow from '../ExpandedRow/ExpandedRow'
-import JobsEmptyState from '../JobsEmptyState'
-import { URLS } from '../../../config'
-import { environmentName } from '../Utils'
-import { DEFAULT_ENV } from '../../app/details/triggerView/Constants'
+import { useCallback, useState } from 'react'
+import { generatePath, useLocation, useNavigate } from 'react-router-dom'
-export default function JobListView(props: JobListViewProps) {
- const history = useHistory()
- const location = useLocation()
+import { FiltersTypeEnum, PaginationEnum, ROUTER_URLS, Table } from '@devtron-labs/devtron-fe-common-lib'
- const handleJobNameSorting = () => props.handleSorting(JobsListSortableKeys.APP_NAME)
+import { DEFAULT_ENV } from '../../app/details/triggerView/Constants'
+import { JobListViewType } from '../Constants'
+import JobsEmptyState from '../JobsEmptyState'
+import { getJobs } from '../Service'
+import { JobListViewProps } from '../Types'
+import { environmentName, jobListModal } from '../Utils'
+import { JOB_LIST_TABLE_COLUMNS, JobRowActionsComponent } from './constants'
+import { JobTableAdditionalProps, JobTableRowData } from './types'
- const expandEnv = (event): void => {
- event.stopPropagation()
- event.preventDefault()
- props.expandRow(event.currentTarget.dataset.key)
- }
+import './styles.scss'
- const handleEditJob = (event): void => {
- event.stopPropagation()
- event.preventDefault()
- handleAnalyticsEvent({ category: 'Job List', action: event.currentTarget.dataset.action })
- props.handleEditJob(event.currentTarget.dataset.key)
- }
+const JobListView = ({
+ searchKey,
+ status,
+ project,
+ environment,
+ setJobCount,
+ handleEditJob,
+ clearFilters,
+ sortBy,
+}: JobListViewProps) => {
+ const navigate = useNavigate()
+ const location = useLocation()
+ const [noJobs, setNoJobs] = useState(false)
- const closeExpandedRow = (event): void => {
- props.closeExpandedRow(event.currentTarget.dataset.key)
+ const createJobHandler = () => {
+ navigate({
+ pathname: ROUTER_URLS.CREATE_JOB,
+ search: location.search,
+ })
}
- const toggleAllExpandRow = () => {
- if (props.isAllExpandable) {
- props.toggleExpandAllRow()
- }
- }
+ const isSearchOrFilterApplied = !!(searchKey || status?.length || project?.length || environment?.length)
- const arrowIcon = (): string => {
- if (props.isAllExpandable) {
- return props.isAllExpanded ? 'fcn-7' : 'fcn-7 dc__flip-270'
- }
- return 'cursor-not-allowed dc__flip-270'
- }
+ // Use getRows to fetch data with pagination
+ const getRows = useCallback(
+ async ({ offset, pageSize, sortBy: getRowsSortBy, sortOrder }, signal: AbortSignal) => {
+ const request = {
+ appNameSearch: searchKey.toLowerCase(),
+ appStatuses: status,
+ environments: environment.map((envId) => +envId),
+ teams: project.map((projectId) => +projectId),
+ offset,
+ size: pageSize,
+ sortBy: getRowsSortBy,
+ sortOrder,
+ }
- const redirectToJobOverview = (job: Job): string => {
- return `${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${job.id}/${URLS.APP_OVERVIEW}`
- }
+ const response = await getJobs(request, { signal })
+ const jobs = jobListModal(response.result?.jobContainers)
+ const totalCount = response.result.jobCount
+ setJobCount(totalCount)
+ setNoJobs(totalCount === 0 && !isSearchOrFilterApplied)
- const renderJobPipelines = () => {
- return props.jobs.map((job) => {
- const len = job.ciPipelines.length > 1
- return (
-
- {!props.expandedRow[job.id] && (
-
-
-
-
-
- {len && (
-
- )}
-
-
-
-
-
- {environmentName(job.defaultPipeline)}
- {environmentName(job.defaultPipeline) === DEFAULT_ENV && (
- (Default)
- )}
-
-
-
-
{job.defaultPipeline.lastRunAt}
-
-
-
{job.defaultPipeline.lastSuccessAt}
-
-
-
-
-
- )}
- {props.expandedRow[job.id] && (
-
- )}
-
- )
- })
- }
+ return {
+ rows: jobs.map((job) => {
+ const envName = environmentName(job.defaultPipeline)
- const renderJobList = () => {
- if (props.jobs.length <= 0) {
- return null
- }
+ const jobDetails = {
+ ...job,
+ 'defaultPipeline.environmentName': `${envName || '-'}${envName === DEFAULT_ENV ? ' (default)' : ''}`,
+ 'defaultPipeline.lastRunAt': job.defaultPipeline?.lastRunAt || '-',
+ 'defaultPipeline.lastSuccessAt': job.defaultPipeline?.lastSuccessAt || '-',
+ }
- return (
-
-
- {renderJobPipelines()}
-
- )
- }
+ return {
+ id: String(job.id),
+ data: jobDetails,
+ expandableRows:
+ job.ciPipelines.length > 1
+ ? job.ciPipelines.map((pipeline) => ({
+ id: `expanded-row-${job.id}-${pipeline.ciPipelineId}` as const,
+ data: {
+ ...jobDetails,
+ pipeline,
+ },
+ }))
+ : undefined,
+ }
+ }),
+ totalRows: totalCount,
+ }
+ },
+ [searchKey, status, environment, project, setJobCount, isSearchOrFilterApplied],
+ )
- const renderPagination = () => {
- if (props.size > DEFAULT_BASE_PAGE_SIZE) {
- return (
-
- )
+ const onRowClick = useCallback(({ data }, isExpandedRow) => {
+ if (isExpandedRow) {
+ return
}
- }
- const createJobHandler = () => {
- history.push(`${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${URLS.APP_LIST}/${URLS.CREATE_JOB}${location.search}`)
- }
+ navigate(generatePath(ROUTER_URLS.JOB_DETAIL.OVERVIEW, { appId: String(data.id) }))
+ }, [])
- if (props.view === JobListViewType.LOADING) {
- return (
-
- )
- }
- if (props.view === JobListViewType.EMPTY || props.view === JobListViewType.NO_RESULT) {
- return (
- <>
-
- >
- )
- }
- if (props.view === JobListViewType.ERROR) {
- return (
-
-
-
- )
+ // Show empty state if no jobs exist
+ if (noJobs) {
+ return
}
+
return (
- <>
- {renderJobList()}
- {renderPagination()}
- >
+
+ id="table__job-list"
+ columns={JOB_LIST_TABLE_COLUMNS}
+ getRows={getRows}
+ filtersVariant={FiltersTypeEnum.URL}
+ filter={null}
+ paginationVariant={PaginationEnum.PAGINATED}
+ emptyStateConfig={{
+ noRowsConfig: null, // Empty state is handled externally
+ noRowsForFilterConfig: {
+ title: 'No jobs found',
+ subTitle: 'Try adjusting your search or filters',
+ clearFilters,
+ },
+ }}
+ rowActionOnHoverConfig={{
+ Component: JobRowActionsComponent,
+ width: 64,
+ }}
+ rowStartIconConfig={{ name: 'ic-devtron-job', color: null, size: 24 }}
+ clearFilters={clearFilters}
+ areFiltersApplied={isSearchOrFilterApplied}
+ additionalFilterProps={{
+ initialSortKey: sortBy,
+ }}
+ additionalProps={{
+ handleEditJob,
+ }}
+ data-testid="job-list-container"
+ onRowClick={onRowClick}
+ />
)
}
+
+export default JobListView
diff --git a/src/components/Jobs/JobList/JobRowActionsComponent.tsx b/src/components/Jobs/JobList/JobRowActionsComponent.tsx
new file mode 100644
index 0000000000..99b4708b34
--- /dev/null
+++ b/src/components/Jobs/JobList/JobRowActionsComponent.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024. Devtron Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * 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.
+ */
+
+import { MouseEvent } from 'react'
+
+import {
+ Button,
+ ButtonStyleType,
+ ButtonVariantType,
+ ComponentSizeType,
+ Icon,
+ TableRowActionsOnHoverComponentProps,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { JobTableAdditionalProps, JobTableRowData } from './types'
+
+// Row actions hover component
+export const JobRowActionsComponent = ({
+ row,
+ handleEditJob,
+}: TableRowActionsOnHoverComponentProps & JobTableAdditionalProps) => {
+ const handleEdit = (event: MouseEvent) => {
+ event.stopPropagation()
+ handleEditJob(row.data.id)
+ }
+
+ return (
+
+ }
+ dataTestId="edit-job-button"
+ onClick={handleEdit}
+ ariaLabel="Open job configurations"
+ showAriaLabelInTippy={false}
+ />
+
+ )
+}
diff --git a/src/components/Jobs/JobList/JobsList.tsx b/src/components/Jobs/JobList/JobsList.tsx
index 00fa78b29d..6405e79bd6 100644
--- a/src/components/Jobs/JobList/JobsList.tsx
+++ b/src/components/Jobs/JobList/JobsList.tsx
@@ -15,12 +15,14 @@
*/
import { useEffect, useMemo, useState } from 'react'
-import { Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom'
+import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
import {
+ BASE_ROUTES,
DevtronProgressing,
ErrorScreenManager,
HeaderWithCreateButton,
+ ROUTER_URLS,
ServerErrors,
showError,
useUrlFilters,
@@ -28,7 +30,6 @@ import {
import { CreateAppModal } from '@Pages/App/CreateAppModal'
-import { URLS } from '../../../config'
import { INITIAL_EMPTY_MASTER_FILTERS, JobListViewType } from '../Constants'
import { getJobsInitFilters } from '../Service'
import {
@@ -44,8 +45,7 @@ import JobListContainer from './JobListContainer'
import '../../app/list/list.scss'
const JobsList = () => {
- const { path } = useRouteMatch()
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const [dataStateType, setDataStateType] = useState(JobListViewType.LOADING)
const [filtersLoading, setFiltersLoading] = useState(false)
@@ -128,20 +128,20 @@ const JobsList = () => {
}
}
- const openJobCreateModel = () => {
- history.push(`${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${URLS.APP_LIST}/${URLS.CREATE_JOB}${location.search}`)
- }
-
const closeJobCreateModal = () => {
- history.push(`${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${URLS.APP_LIST}`)
+ navigate({
+ pathname: ROUTER_URLS.JOBS_LIST,
+ search: location.search,
+ })
}
const renderCreateJobRouter = () => (
-
-
-
-
-
+
+ }
+ />
+
)
if (dataStateType === JobListViewType.ERROR) {
@@ -165,7 +165,6 @@ const JobsList = () => {
jobListCount={jobCount}
filtersLoading={filtersLoading}
setJobCount={setJobCount}
- openJobCreateModel={openJobCreateModel}
handleSearch={handleSearch}
updateSearchParams={updateSearchParams}
getLabelFromValue={getLabelFromValue}
diff --git a/src/components/Jobs/JobList/constants.ts b/src/components/Jobs/JobList/constants.ts
index 6e8de97401..cfa30b8010 100644
--- a/src/components/Jobs/JobList/constants.ts
+++ b/src/components/Jobs/JobList/constants.ts
@@ -1,6 +1,9 @@
-import { ExportToCsvProps } from '@devtron-labs/devtron-fe-common-lib'
+import { ExportToCsvProps, FiltersTypeEnum, TableProps } from '@devtron-labs/devtron-fe-common-lib'
-import { ExportJobDataType } from './types'
+import { JobNameCellComponent, JobStatusCellComponent } from './JobListTableCellComponents'
+import { ExportJobDataType, JobTableAdditionalProps, JobTableRowData } from './types'
+
+export { JobRowActionsComponent } from './JobRowActionsComponent'
export const JOB_LIST_EXPORT_HEADERS: ExportToCsvProps['headers'] = [
{ label: 'Job Name', key: 'jobName' },
@@ -12,3 +15,42 @@ export const JOB_LIST_EXPORT_HEADERS: ExportToCsvProps[
{ label: 'Last Run At', key: 'lastRunAt' },
{ label: 'Last Success At', key: 'lastSuccessAt' },
]
+
+export const JOB_LIST_TABLE_COLUMNS: TableProps<
+ JobTableRowData,
+ FiltersTypeEnum.URL,
+ JobTableAdditionalProps
+>['columns'] = [
+ {
+ label: 'NAME',
+ field: 'name',
+ isSortable: true,
+ size: { fixed: 300 },
+ CellComponent: JobNameCellComponent,
+ },
+ {
+ label: 'LAST RUN STATUS',
+ field: 'defaultPipeline.status',
+ isSortable: false,
+ size: { fixed: 150 },
+ CellComponent: JobStatusCellComponent,
+ },
+ {
+ label: 'RUN IN ENVIRONMENT',
+ field: 'defaultPipeline.environmentName',
+ isSortable: false,
+ size: { fixed: 200 },
+ },
+ {
+ label: 'LAST RUN AT',
+ field: 'defaultPipeline.lastRunAt',
+ isSortable: false,
+ size: { fixed: 200 },
+ },
+ {
+ label: 'LAST SUCCESS AT',
+ field: 'defaultPipeline.lastSuccessAt',
+ isSortable: false,
+ size: { fixed: 200 },
+ },
+]
diff --git a/src/components/Jobs/JobList/styles.scss b/src/components/Jobs/JobList/styles.scss
new file mode 100644
index 0000000000..cdafb26f31
--- /dev/null
+++ b/src/components/Jobs/JobList/styles.scss
@@ -0,0 +1,23 @@
+[id$="table__job-list"] {
+ .generic-table__row {
+ padding-block: 8px;
+ }
+
+ .generic-table__row--expanded-row {
+ padding-block: 0px;
+
+ & > div:first-child {
+ height: 45px;
+ }
+ }
+
+ .generic-table__row--is-expanded {
+ // hide 4th div onwards (inclusive) when this row is expanded
+ & > div:nth-child(n+4) > * {
+ display: none;
+ }
+
+ height: 45px;
+ flex-shrink: 0;
+ }
+}
diff --git a/src/components/Jobs/JobList/types.ts b/src/components/Jobs/JobList/types.ts
index 3eb55b55e2..2513867b96 100644
--- a/src/components/Jobs/JobList/types.ts
+++ b/src/components/Jobs/JobList/types.ts
@@ -1,3 +1,5 @@
+import { Job, JobCIPipeline } from '../Types'
+
export interface ExportJobDataType {
jobName: string
jobId: string
@@ -8,3 +10,13 @@ export interface ExportJobDataType {
lastRunAt: string
lastSuccessAt: string
}
+
+export interface JobTableRowData extends Job {
+ // For expandable rows
+ isExpandedRow?: boolean
+ pipeline?: JobCIPipeline
+}
+
+export interface JobTableAdditionalProps {
+ handleEditJob: (jobId: number) => void
+}
diff --git a/src/components/Jobs/Jobs.tsx b/src/components/Jobs/Jobs.tsx
index c37f5b85ff..f0998d4f65 100644
--- a/src/components/Jobs/Jobs.tsx
+++ b/src/components/Jobs/Jobs.tsx
@@ -14,26 +14,24 @@
* limitations under the License.
*/
-import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'
-import { URLS } from '../../config'
+import { Navigate, Route, Routes } from 'react-router-dom'
+
+import { BASE_ROUTES } from '@devtron-labs/devtron-fe-common-lib'
+
import { ErrorBoundary } from '../common'
import JobDetails from './JobDetails/JobDetails'
import JobsList from './JobList/JobsList'
-export default function Jobs() {
- const { path } = useRouteMatch()
+const JOB_ROUTES = BASE_ROUTES.AUTOMATION_AND_ENABLEMENT.JOBS
+
+const Jobs = () => (
+
+
+ } />
+ } />
+ } />
+
+
+)
- return (
-
-
-
-
-
-
-
-
-
-
-
- )
-}
+export default Jobs
diff --git a/src/components/Jobs/JobsEmptyState.tsx b/src/components/Jobs/JobsEmptyState.tsx
index 579c366835..17d1aa575a 100644
--- a/src/components/Jobs/JobsEmptyState.tsx
+++ b/src/components/Jobs/JobsEmptyState.tsx
@@ -18,7 +18,7 @@ import { GenericEmptyState, GenericFilterEmptyState } from '@devtron-labs/devtro
import { JobListViewType, JOBLIST_EMPTY_STATE_MESSAGING } from './Constants'
import { JobsEmptyProps } from './Types'
import nojobs from '../../assets/img/empty-joblist.webp'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Add from '../../assets/icons/ic-add.svg?react'
export default function JobsEmptyState(props: JobsEmptyProps) {
const renderNoJobsView = () => {
diff --git a/src/components/Jobs/Types.ts b/src/components/Jobs/Types.ts
index e9f188bd80..e667f7978e 100644
--- a/src/components/Jobs/Types.ts
+++ b/src/components/Jobs/Types.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { RouteComponentProps } from 'react-router-dom'
-
import {
ResponseType,
SelectPickerOptionType,
@@ -36,9 +34,6 @@ export enum JobListStateActionTypes {
showCommandBar = 'showCommandBar',
offset = 'offset',
pageSize = 'pageSize',
- expandedRow = 'expandedRow',
- isAllExpanded = 'isAllExpanded',
- isAllExpandable = 'isAllExpandable',
multipleOptions = 'multipleOptions',
}
@@ -79,9 +74,6 @@ export interface JobListState {
size: number
offset: number
pageSize: number
- expandedRow: Record
- isAllExpanded: boolean
- isAllExpandable: boolean
}
export interface ExpandedRowProps {
@@ -120,24 +112,17 @@ export interface JobListProps
filterConfig: JobListFilterConfig
filtersLoading: boolean
jobListCount: number
- openJobCreateModel: (event) => void
setJobCount: React.Dispatch>
getLabelFromValue: (filterKey: JobListUrlFilters, filterValue: string) => string
}
-export interface JobListViewProps
- extends JobListState,
- RouteComponentProps<{}>,
- Pick<
- UseUrlFiltersReturnType,
- 'changePage' | 'changePageSize' | 'clearFilters' | 'handleSorting'
- > {
- expandRow: (id: number | null) => void
- closeExpandedRow: (id: number | null) => void
+export interface JobListViewProps extends Pick {
+ status: string[]
+ environment: string[]
+ project: string[]
handleEditJob: (jobId: number) => void
- jobListCount: number
- openJobCreateModel: (e) => void
- toggleExpandAllRow: () => void
+ clearFilters: () => void
+ setJobCount: React.Dispatch>
}
export interface JobSelectorType {
diff --git a/src/components/Jobs/Utils.ts b/src/components/Jobs/Utils.ts
index 4bda08893f..7f098057a3 100644
--- a/src/components/Jobs/Utils.ts
+++ b/src/components/Jobs/Utils.ts
@@ -45,9 +45,6 @@ export const getInitialJobListState = (payloadParsedFromUrl): JobListState => {
showCommandBar: false,
offset: payloadParsedFromUrl.offset ?? 0,
pageSize: payloadParsedFromUrl.size ?? 20,
- expandedRow: null,
- isAllExpanded: false,
- isAllExpandable: false,
}
}
@@ -71,12 +68,6 @@ export const jobListReducer = (state: JobListState, action: JobListStateAction)
return { ...state, offset: action.payload }
case JobListStateActionTypes.pageSize:
return { ...state, pageSize: action.payload }
- case JobListStateActionTypes.expandedRow:
- return { ...state, expandedRow: action.payload }
- case JobListStateActionTypes.isAllExpanded:
- return { ...state, isAllExpanded: action.payload }
- case JobListStateActionTypes.isAllExpandable:
- return { ...state, isAllExpandable: action.payload }
case JobListStateActionTypes.multipleOptions:
return { ...state, ...action.payload }
default:
diff --git a/src/components/Navigation/NavGroup.tsx b/src/components/Navigation/NavGroup.tsx
index 96d6fb9bb8..38d7574ace 100644
--- a/src/components/Navigation/NavGroup.tsx
+++ b/src/components/Navigation/NavGroup.tsx
@@ -46,8 +46,7 @@ export const NavGroup = ({
{shouldRenderNavLink ? (
`${className} ${isActive ? 'is-selected' : ''}`}
aria-disabled={disabled}
onClick={handleNavLinkClick}
>
diff --git a/src/components/Navigation/NavItem.tsx b/src/components/Navigation/NavItem.tsx
index 7b1a62286f..74f1da50f9 100644
--- a/src/components/Navigation/NavItem.tsx
+++ b/src/components/Navigation/NavItem.tsx
@@ -88,8 +88,9 @@ export const NavItem = ({ hasSearchText, ...navItem }: NavItemProps) => {
+ `nav-item flex left dc__gap-8 px-8 py-6 br-4 ${disabled ? 'dc__disabled' : ''} ${isActive ? 'is-selected fw-6' : ''}`
+ }
aria-disabled={disabled}
onClick={disabled ? preventDefault : handleNavItemClick}
>
diff --git a/src/components/Navigation/Navigation.tsx b/src/components/Navigation/Navigation.tsx
index 37bf26c331..1b5a7c9f8e 100644
--- a/src/components/Navigation/Navigation.tsx
+++ b/src/components/Navigation/Navigation.tsx
@@ -26,8 +26,8 @@ import {
ModuleStatus,
motion,
NavigationGroupType,
+ ROUTER_URLS,
SearchBar,
- URLS,
useMainContext,
useQuery,
UseRegisterShortcutProvider,
@@ -263,7 +263,7 @@ export const Navigation = ({
{
className="flex"
to={
serverMode === SERVER_MODE.EA_ONLY
- ? URLS.INFRASTRUCTURE_MANAGEMENT_APP
- : URLS.APPLICATION_MANAGEMENT_APP
+ ? ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_LIST.HELM
+ : ROUTER_URLS.DEVTRON_APP_LIST
}
>
{logoContent}
diff --git a/src/components/Navigation/constants.ts b/src/components/Navigation/constants.ts
index 9a23094a32..f4e890c9be 100644
--- a/src/components/Navigation/constants.ts
+++ b/src/components/Navigation/constants.ts
@@ -1,13 +1,6 @@
-import {
- NavigationGroupType,
- NavigationItemType,
- SERVER_MODE,
- URLS as COMMON_URLS,
-} from '@devtron-labs/devtron-fe-common-lib'
+import { NavigationGroupType, NavigationItemType, ROUTER_URLS, SERVER_MODE } from '@devtron-labs/devtron-fe-common-lib'
import { importComponentFromFELibrary } from '@Components/common'
-import { Routes } from '@Config/constants'
-import { URLS } from '@Config/routes'
import { filterNavGroupAndItem } from './utils'
@@ -49,40 +42,46 @@ const COST_VISIBILITY_NAV_GROUP: NavigationGroupType = importComponentFromFELibr
const SDH_NAV_GROUP: NavigationGroupType = importComponentFromFELibrary('SDH_NAV_GROUP', null, 'function')
+const GLOBAL_CONFIG_AUTH_USER_GROUPS_NAV_ITEM: NavigationItemType['subItems'][number] = importComponentFromFELibrary(
+ 'GLOBAL_CONFIG_AUTH_USER_GROUPS_NAV_ITEM',
+ null,
+ 'function',
+)
+
export const APPLICATION_MANAGEMENT_CONFIGURATIONS: NavigationItemType['subItems'] = [
{
title: 'GitOps',
dataTestId: 'click-on-configurations-gitops',
id: 'application-management-configurations-gitops',
- href: URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS_GITOPS,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS.GITOPS,
keywords: ['config'],
},
{
title: 'Git Accounts',
dataTestId: 'click-on-configurations-git-accounts',
id: 'application-management-configurations-git-accounts',
- href: URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS_GIT_ACCOUNTS,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS.GIT_ACCOUNTS,
keywords: ['config'],
},
{
title: 'Deployment Charts',
dataTestId: 'click-on-configurations-deployment-charts',
id: 'application-management-configurations-deployment-charts',
- href: COMMON_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS_DEPLOYMENT_CHARTS,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS.DEPLOYMENT_CHARTS,
keywords: ['config'],
},
{
title: 'Notifications',
dataTestId: 'click-on-configurations-notifications',
id: 'application-management-configurations-notifications',
- href: URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS_NOTIFICATIONS,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS.NOTIFICATIONS,
keywords: ['config'],
},
{
title: 'Scoped Variables',
dataTestId: 'click-on-configurations-scoped-variables',
id: 'application-management-configurations-scoped-variables',
- href: COMMON_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS_SCOPED_VARIABLES,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS.SCOPED_VARIABLES.ROOT,
keywords: ['config'],
forceHideEnvKey: 'ENABLE_SCOPED_VARIABLES',
},
@@ -90,7 +89,7 @@ export const APPLICATION_MANAGEMENT_CONFIGURATIONS: NavigationItemType['subItems
title: 'Build Infra',
dataTestId: 'click-on-configurations-build-infra',
id: 'application-management-configurations-build-infra',
- href: COMMON_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS_BUILD_INFRA,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_CONFIGURATIONS.BUILD_INFRA.ROOT,
keywords: ['config'],
},
]
@@ -100,21 +99,22 @@ const GLOBAL_CONFIGURATION_AUTHORIZATION: NavigationItemType['subItems'] = [
title: 'User Permissions',
dataTestId: 'user-permissions',
id: 'global-configuration-authorization-user-permissions',
- href: URLS.GLOBAL_CONFIG_AUTH_USER_PERMISSION,
+ href: ROUTER_URLS.GLOBAL_CONFIG_AUTH.USERS,
isAvailableInEA: true,
},
{
title: 'Permission Groups',
dataTestId: 'permission-groups',
id: 'global-configuration-authorization-permission-groups',
- href: URLS.GLOBAL_CONFIG_AUTH_PERMISSION_GROUPS,
+ href: ROUTER_URLS.GLOBAL_CONFIG_AUTH.GROUPS,
isAvailableInEA: true,
},
+ ...(GLOBAL_CONFIG_AUTH_USER_GROUPS_NAV_ITEM ? [GLOBAL_CONFIG_AUTH_USER_GROUPS_NAV_ITEM] : []),
{
title: 'API Tokens',
dataTestId: 'authorization-api-tokens',
id: 'global-configuration-authorization-api-tokens',
- href: `${URLS.GLOBAL_CONFIG_AUTH}/${Routes.API_TOKEN}/list`,
+ href: ROUTER_URLS.GLOBAL_CONFIG_AUTH.API_TOKEN,
isAvailableInEA: true,
},
]
@@ -130,28 +130,28 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'application-management-overview',
id: 'application-management-overview',
icon: 'ic-chart-line-up',
- href: COMMON_URLS.APPLICATION_MANAGEMENT_OVERVIEW,
+ href: ROUTER_URLS.APPLICATION_MANAGEMENT_OVERVIEW,
},
{
title: 'Devtron Applications',
dataTestId: 'click-on-devtron-application',
id: 'application-management-devtron-applications',
icon: 'ic-application',
- href: COMMON_URLS.APPLICATION_MANAGEMENT_APP,
+ href: ROUTER_URLS.DEVTRON_APP,
},
{
title: 'Application Groups',
dataTestId: 'click-on-application-groups',
id: 'application-management-application-groups',
icon: 'ic-application-group',
- href: COMMON_URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP,
+ href: ROUTER_URLS.APP_GROUP,
},
{
title: 'Bulk Edit',
dataTestId: 'click-on-bulk-edit',
id: 'application-management-bulk-edit',
icon: 'ic-code',
- href: URLS.APPLICATION_MANAGEMENT_BULK_EDIT,
+ href: ROUTER_URLS.BULK_EDIT,
},
...(APPLICATION_MANAGEMENT_TEMPLATES_NAV_ITEM ? [APPLICATION_MANAGEMENT_TEMPLATES_NAV_ITEM] : []),
{
@@ -174,7 +174,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'infrastructure-management-overview',
id: 'infrastructure-management-overview',
icon: 'ic-chart-line-up',
- href: COMMON_URLS.INFRASTRUCTURE_MANAGEMENT_OVERVIEW,
+ href: ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_OVERVIEW,
isAvailableInEA: true,
},
{
@@ -182,7 +182,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'click-on-application',
id: 'infrastructure-management-applications',
icon: 'ic-grid-view',
- href: COMMON_URLS.INFRASTRUCTURE_MANAGEMENT_APP,
+ href: ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APPS,
isAvailableInEA: true,
},
{
@@ -190,7 +190,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'click-on-chart-store',
id: 'infrastructure-management-chart-store',
icon: 'ic-helm',
- href: COMMON_URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE,
+ href: ROUTER_URLS.CHART_STORE,
isAvailableInEA: true,
},
{
@@ -198,7 +198,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'resource-browser',
id: 'infrastructure-management-resource-browser',
icon: 'ic-resource-browser',
- href: COMMON_URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER,
+ href: ROUTER_URLS.RESOURCE_BROWSER.ROOT,
isAvailableInEA: true,
},
...(RESOURCE_WATCHER_NAV_ITEM ? [RESOURCE_WATCHER_NAV_ITEM] : []),
@@ -216,14 +216,14 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
title: 'Overview',
dataTestId: 'security-center-overview',
id: 'security-center-overview',
- href: COMMON_URLS.SECURITY_CENTER_OVERVIEW,
+ href: ROUTER_URLS.SECURITY_CENTER_OVERVIEW,
icon: 'ic-chart-line-up',
},
{
title: 'Vulnerabilities',
dataTestId: 'security-vulnerabilities',
id: 'security-center-security-vulnerabilities',
- href: COMMON_URLS.SECURITY_CENTER_VULNERABILITIES,
+ href: ROUTER_URLS.SECURITY_CENTER_VULNERABILITIES,
icon: 'ic-bug',
},
...(SECURITY_ENABLEMENT_NAV_ITEM ? [SECURITY_ENABLEMENT_NAV_ITEM] : []),
@@ -231,7 +231,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
title: 'Security Policy',
dataTestId: 'security-policy',
id: 'security-center-security-policy',
- href: COMMON_URLS.SECURITY_CENTER_POLICIES,
+ href: ROUTER_URLS.SECURITY_CENTER_POLICIES,
icon: 'ic-security-policy',
},
],
@@ -246,7 +246,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'jobs',
id: 'automation-and-enablement-jobs',
icon: 'ic-k8s-job',
- href: URLS.AUTOMATION_AND_ENABLEMENT_JOB,
+ href: ROUTER_URLS.JOBS,
},
],
},
@@ -261,7 +261,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'sso-login-services',
id: 'global-configuration-sso-login-services',
icon: 'ic-key',
- href: `${URLS.GLOBAL_CONFIG_AUTH}/${Routes.SSO_LOGIN_SERVICES}`,
+ href: ROUTER_URLS.GLOBAL_CONFIG_AUTH.LOGIN_SERVICE,
isAvailableInEA: true,
},
{
@@ -269,13 +269,13 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'host-url',
id: 'global-configuration-host-urls',
icon: 'ic-link',
- href: URLS.GLOBAL_CONFIG_HOST_URL,
+ href: ROUTER_URLS.GLOBAL_CONFIG_HOST_URL,
},
{
title: 'External Links',
dataTestId: 'click-on-configurations-external-links',
id: 'global-configuration-external-links',
- href: URLS.GLOBAL_CONFIG_EXTERNAL_LINKS,
+ href: ROUTER_URLS.GLOBAL_CONFIG_EXTERNAL_LINKS,
icon: 'ic-external-link',
isAvailableInEA: true,
},
@@ -283,7 +283,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
title: 'Chart Repository',
dataTestId: 'click-on-configurations-chart-repository',
id: 'global-configuration-chart-repository',
- href: URLS.GLOBAL_CONFIG_CHART_REPO,
+ href: ROUTER_URLS.GLOBAL_CONFIG_CHART_REPOSITORIES,
icon: 'ic-chart-repo',
isAvailableInEA: true,
},
@@ -292,7 +292,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'cluster-and-environments',
id: 'global-configuration-cluster-and-environments',
icon: 'ic-cluster',
- href: URLS.GLOBAL_CONFIG_CLUSTER,
+ href: ROUTER_URLS.GLOBAL_CONFIG_CLUSTER_ENV,
isAvailableInEA: true,
},
{
@@ -300,7 +300,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'container-oci-registry',
id: 'global-configuration-container-oci-registry',
icon: 'ic-folder',
- href: URLS.GLOBAL_CONFIG_DOCKER,
+ href: ROUTER_URLS.GLOBAL_CONFIG_DOCKER,
isAvailableInEA: true,
},
{
@@ -308,7 +308,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [
dataTestId: 'click-on-projects',
id: 'global-configuration-projects',
icon: 'ic-folder',
- href: COMMON_URLS.GLOBAL_CONFIG_PROJECTS,
+ href: ROUTER_URLS.GLOBAL_CONFIG_PROJECTS,
isAvailableInEA: true,
},
{
diff --git a/src/components/ResourceBrowser/ClusterInstallationStatus.tsx b/src/components/ResourceBrowser/ClusterInstallationStatus.tsx
index 2c0c01e52f..4712e7c435 100644
--- a/src/components/ResourceBrowser/ClusterInstallationStatus.tsx
+++ b/src/components/ResourceBrowser/ClusterInstallationStatus.tsx
@@ -15,11 +15,12 @@
*/
import { useMemo } from 'react'
-import { useHistory, useParams } from 'react-router-dom'
+import { useNavigate, useParams } from 'react-router-dom'
import {
BreadcrumbText,
getInfrastructureManagementBreadcrumb,
+ ROUTER_URLS,
useAsync,
useBreadcrumb,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -40,7 +41,7 @@ const ClusterInstallationStatusDialog = importComponentFromFELibrary(
)
const ClusterInstallationStatus = () => {
- const { replace } = useHistory()
+ const navigate = useNavigate()
const { installationId } = useParams<{ installationId: string }>()
const [isClusterListLoading, clusterList] = useAsync(() => getClusterListing(true))
@@ -57,12 +58,13 @@ const ClusterInstallationStatus = () => {
isClusterInCreationPhase ? String(clusterInstallationId) : value,
)
- replace({
- pathname: path,
+ navigate(path, {
+ replace: true,
})
}
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.RESOURCE_BROWSER.INSTALLATION_CLUSTER,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
@@ -90,7 +92,10 @@ const ClusterInstallationStatus = () => {
return (
-
+
diff --git a/src/components/ResourceBrowser/Constants.ts b/src/components/ResourceBrowser/Constants.ts
index d0ba845df4..87210468b9 100644
--- a/src/components/ResourceBrowser/Constants.ts
+++ b/src/components/ResourceBrowser/Constants.ts
@@ -18,11 +18,10 @@ import {
K8S_EMPTY_GROUP as K8S_EMPTY_GROUP_FROM_COMMON,
NO_MATCHING_RESULT,
Nodes,
- RESOURCE_BROWSER_ROUTES,
- URLS,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import ICArrowUpCircle from '@Icons/ic-arrow-up-circle.svg'
+import ICArrowUpCircle from '@Icons/ic-arrow-up-circle.svg?react'
import { AggregationKeys, AggregationKeysType } from '../app/types'
import { multiSelectStyles } from '../v2/common/ReactSelectCustomization'
@@ -374,14 +373,19 @@ export const ResourceBrowserGAEvent = {
RB_SWITCH_CLUSTER_SEARCHED_ITEM_CLICKED: 'RB_SWITCH_CLUSTER_SEARCHED_ITEM_CLICKED',
}
+const CLUSTER_DETAILS_ROUTES = ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS
+
export const ResourceBrowserRouteToTabIdMap: Partial<
- Record<(typeof RESOURCE_BROWSER_ROUTES)[keyof typeof RESOURCE_BROWSER_ROUTES], ResourceBrowserTabsId | string>
+ Record<
+ (typeof ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS)[keyof typeof ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS],
+ ResourceBrowserTabsId | string
+ >
> = {
- [`${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/:kind/:group`]: ResourceBrowserTabsId.k8s_Resources,
- [`${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/overview`]: ResourceBrowserTabsId.cluster_overview,
- [`${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/monitoring-dashboard`]: MONITORING_DASHBOARD_TAB_ID,
- [`${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/terminal`]: ResourceBrowserTabsId.terminal,
- [`${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/resource-recommender`]: RESOURCE_RECOMMENDER_TAB_ID,
+ [CLUSTER_DETAILS_ROUTES.K8S_RESOURCE_LIST]: ResourceBrowserTabsId.k8s_Resources,
+ [CLUSTER_DETAILS_ROUTES.OVERVIEW]: ResourceBrowserTabsId.cluster_overview,
+ [CLUSTER_DETAILS_ROUTES.MONITORING_DASHBOARD]: MONITORING_DASHBOARD_TAB_ID,
+ [CLUSTER_DETAILS_ROUTES.TERMINAL]: ResourceBrowserTabsId.terminal,
+ [CLUSTER_DETAILS_ROUTES.RESOURCE_RECOMMENDER]: RESOURCE_RECOMMENDER_TAB_ID,
}
export const KUBERNETES_RESOURCE_BROWSER_DESCRIPTION =
diff --git a/src/components/ResourceBrowser/PageHeader.buttons.tsx b/src/components/ResourceBrowser/PageHeader.buttons.tsx
index 357f5ed609..3528728b9e 100644
--- a/src/components/ResourceBrowser/PageHeader.buttons.tsx
+++ b/src/components/ResourceBrowser/PageHeader.buttons.tsx
@@ -21,7 +21,7 @@ import { Button, ComponentSizeType, handleAnalyticsEvent, useMainContext } from
import { CreateClusterProps } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/CreateCluster/types'
import { AddClusterButton } from '@Pages/Shared/AddEditCluster'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Add from '../../assets/icons/ic-add.svg?react'
import { CreateResource } from './ResourceList/CreateResource'
import { CreateResourceButtonType, CreateResourceType } from './Types'
diff --git a/src/components/ResourceBrowser/ResourceBrowser.scss b/src/components/ResourceBrowser/ResourceBrowser.scss
index 4c33f32e25..6a7ac25ce2 100644
--- a/src/components/ResourceBrowser/ResourceBrowser.scss
+++ b/src/components/ResourceBrowser/ResourceBrowser.scss
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import '../v2/appDetails/k8Resource/k8resources.scss';
+@use '../v2/appDetails/k8Resource/k8resources.scss';
.resource-browser-container {
.k8s-object-kind-search {
diff --git a/src/components/ResourceBrowser/ResourceBrowser.tsx b/src/components/ResourceBrowser/ResourceBrowser.tsx
index 8efa1381b0..26ccbed4ac 100644
--- a/src/components/ResourceBrowser/ResourceBrowser.tsx
+++ b/src/components/ResourceBrowser/ResourceBrowser.tsx
@@ -26,6 +26,7 @@ import {
ErrorScreenManager,
getInfrastructureManagementBreadcrumb,
PageHeader,
+ ROUTER_URLS,
useAsync,
useBreadcrumb,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -51,6 +52,7 @@ const ResourceBrowser: React.FC = () => {
const { pathname } = useLocation()
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.RESOURCE_BROWSER.ROOT,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
@@ -105,7 +107,7 @@ const ResourceBrowser: React.FC = () => {
return
}
- const renderBreadcrumb = () =>
+ const renderBreadcrumb = () =>
return (
diff --git a/src/components/ResourceBrowser/ResourceBrowserRouter.tsx b/src/components/ResourceBrowser/ResourceBrowserRouter.tsx
index 374c4b9bc3..9cac2e8ffe 100644
--- a/src/components/ResourceBrowser/ResourceBrowserRouter.tsx
+++ b/src/components/ResourceBrowser/ResourceBrowserRouter.tsx
@@ -15,12 +15,11 @@
*/
import React from 'react'
-import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'
+import { Navigate, Route, Routes } from 'react-router-dom'
-import { URLS as COMMON_URLS } from '@devtron-labs/devtron-fe-common-lib'
+import { BASE_ROUTES } from '@devtron-labs/devtron-fe-common-lib'
import { importComponentFromFELibrary } from '@Components/common'
-import { URLS } from '@Config/routes'
import ClusterInstallationStatus from './ClusterInstallationStatus'
import ResourceBrowser from './ResourceBrowser'
@@ -31,37 +30,24 @@ import './ResourceBrowser.scss'
const CompareClusterViewWrapper = importComponentFromFELibrary('CompareClusterViewWrapper', null, 'function')
const isFeLibAvailable = !!CompareClusterViewWrapper
-const ResourceBrowserRouter: React.FC = () => {
- const { path } = useRouteMatch()
+const RESOURCE_BROWSER_ROUTES = BASE_ROUTES.INFRASTRUCTURE_MANAGEMENT.RESOURCE_BROWSER
- return (
-
- {isFeLibAvailable && (
-
-
-
- )}
+const ResourceBrowserRouter: React.FC = () => (
+
+ {isFeLibAvailable && (
+ } />
+ )}
- {isFeLibAvailable && window._env_.FEATURE_RB_SYNC_CLUSTER_ENABLE && (
-
-
-
- )}
+ {isFeLibAvailable && window._env_.FEATURE_RB_SYNC_CLUSTER_ENABLE && (
+ } />
+ )}
-
-
-
+ } />
-
-
-
+ } />
-
-
- )
-}
+
} />
+
+)
export default ResourceBrowserRouter
diff --git a/src/components/ResourceBrowser/ResourceList/ClusterSelector.tsx b/src/components/ResourceBrowser/ResourceList/ClusterSelector.tsx
index fef022031d..ea85361d8d 100644
--- a/src/components/ResourceBrowser/ResourceList/ClusterSelector.tsx
+++ b/src/components/ResourceBrowser/ResourceList/ClusterSelector.tsx
@@ -15,7 +15,7 @@
*/
import React, { useState } from 'react'
-import { useHistory } from 'react-router-dom'
+import { useNavigate } from 'react-router-dom'
import {
ActionMenu,
@@ -30,10 +30,10 @@ import {
Icon,
RecentlyVisitedOptions,
ResourceKindType,
+ ROUTER_URLS,
SelectPickerProps,
ToastManager,
ToastVariantType,
- URLS,
useUserPreferences,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -57,7 +57,7 @@ const ClusterSelector: React.FC
= ({
isInstallationStatusView,
isClusterListLoading,
}) => {
- const { replace } = useHistory()
+ const navigate = useNavigate()
const [openDeleteClusterModal, setOpenDeleteClusterModal] = useState(false)
const [showEditPodSpreadModal, setShowEditPodSpreadModal] = useState(false)
@@ -138,7 +138,9 @@ const ClusterSelector: React.FC = ({
}
const handleRedirectToClusterList = () => {
- replace(URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER)
+ navigate(ROUTER_URLS.RESOURCE_BROWSER.ROOT, {
+ replace: true,
+ })
}
const handleActionMenuClick: ActionMenuProps['onClick'] = (
diff --git a/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfo.tsx b/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfo.tsx
index ef28c3ba16..f9b5ec2493 100644
--- a/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfo.tsx
+++ b/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfo.tsx
@@ -15,7 +15,7 @@
*/
import { useCallback, useMemo } from 'react'
-import { useHistory, useLocation, useParams } from 'react-router-dom'
+import { useLocation, useNavigate, useParams } from 'react-router-dom'
import {
CollapsibleList,
@@ -27,15 +27,15 @@ import {
LARGE_PAGE_SIZE_OPTIONS,
PaginationEnum,
Progressing,
+ ROUTER_URLS,
Table,
TARGET_K8S_VERSION_SEARCH_KEY,
URL_FILTER_KEYS,
- URLS,
useSearchString,
} from '@devtron-labs/devtron-fe-common-lib'
import emptyCustomChart from '@Images/empty-noresult@2x.png'
-import { ReactComponent as NoOffendingPipeline } from '@Images/no-offending-pipeline.svg'
+import NoOffendingPipeline from '@Images/no-offending-pipeline.svg?react'
import { importComponentFromFELibrary } from '@Components/common'
import { ClusterDetailBaseParams } from '../Types'
@@ -61,7 +61,7 @@ const ClusterUpgradeCompatibilityInfo = ({
}: ClusterUpgradeCompatibilityInfoProps) => {
const { clusterId } = useParams()
const targetK8sVersion = useSearchString().queryParams.get(TARGET_K8S_VERSION_SEARCH_KEY)
- const { push } = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const {
@@ -138,7 +138,7 @@ const ClusterUpgradeCompatibilityInfo = ({
)
}
@@ -161,7 +161,7 @@ const ClusterUpgradeCompatibilityInfo = ({
const clearFilters = () => {
const searchParams = new URLSearchParams(location.search)
searchParams.delete(URL_FILTER_KEYS.SEARCH_KEY)
- push({ search: searchParams.toString() })
+ navigate({ search: searchParams.toString() })
}
return (
diff --git a/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfoTableCellComponent.tsx b/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfoTableCellComponent.tsx
index 5bbb662a35..b91cc813af 100644
--- a/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfoTableCellComponent.tsx
+++ b/src/components/ResourceBrowser/ResourceList/ClusterUpgradeCompatibilityInfoTableCellComponent.tsx
@@ -15,7 +15,7 @@
*/
import { useEffect, useRef } from 'react'
-import { generatePath, useHistory, useParams } from 'react-router-dom'
+import { generatePath, useNavigate, useParams } from 'react-router-dom'
import DOMPurify from 'dompurify'
import {
@@ -25,7 +25,7 @@ import {
highlightSearchText,
Nodes,
noop,
- RESOURCE_BROWSER_ROUTES,
+ ROUTER_URLS,
TableSignalEnum,
Tooltip,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -45,7 +45,7 @@ const ClusterUpgradeCompatibilityInfoTableCellComponent = ({
reloadResourceListData,
lowercaseKindToResourceGroupMap,
}: ClusterUpgradeCompatibilityInfoTableCellComponentProps) => {
- const { push } = useHistory()
+ const navigate = useNavigate()
const { clusterId } = useParams()
const nameButtonRef = useRef(null)
const contextMenuRef = useRef(null)
@@ -57,7 +57,7 @@ const ClusterUpgradeCompatibilityInfoTableCellComponent = ({
getFirstResourceFromKindResourceMap(lowercaseKindToResourceGroupMap, kind.toLowerCase())?.gvk?.Group ||
K8S_EMPTY_GROUP
- const url = generatePath(RESOURCE_BROWSER_ROUTES.K8S_RESOURCE_DETAIL, {
+ const url = generatePath(ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS.K8S_RESOURCE_DETAIL, {
clusterId,
namespace,
name,
@@ -65,7 +65,7 @@ const ClusterUpgradeCompatibilityInfoTableCellComponent = ({
group,
})
- push(`${url}/${tab}`)
+ navigate(`${url}/${tab}`)
}
useEffect(() => {
@@ -120,7 +120,7 @@ const ClusterUpgradeCompatibilityInfoTableCellComponent = ({
data-testid="created-resource-name"
>
-
+
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
diff --git a/src/components/ResourceBrowser/ResourceList/K8sResourceListTableCellComponent.tsx b/src/components/ResourceBrowser/ResourceList/K8sResourceListTableCellComponent.tsx
index f1b654b6b3..88d53e23c4 100644
--- a/src/components/ResourceBrowser/ResourceList/K8sResourceListTableCellComponent.tsx
+++ b/src/components/ResourceBrowser/ResourceList/K8sResourceListTableCellComponent.tsx
@@ -15,7 +15,7 @@
*/
import { MouseEvent, useEffect, useMemo, useRef, useState } from 'react'
-import { generatePath, useHistory, useParams } from 'react-router-dom'
+import { generatePath, useNavigate, useParams } from 'react-router-dom'
import DOMPurify from 'dompurify'
import {
@@ -31,14 +31,14 @@ import {
MainContext,
Nodes,
noop,
- RESOURCE_BROWSER_ROUTES,
ResourceBrowserActionMenuEnum,
+ ROUTER_URLS,
TableSignalEnum,
Tooltip,
useMainContext,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg'
+import ICErrorExclamation from '@Icons/ic-error-exclamation.svg?react'
import { importComponentFromFELibrary } from '@Components/common'
import { AddEnvironmentFormPrefilledInfoType } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/cluster.type'
import { ClusterEnvironmentDrawer } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer'
@@ -56,6 +56,8 @@ import { getClassNameForColumn, getFirstResourceFromKindResourceMap, getShowAIBu
const ExplainWithAIButton = importComponentFromFELibrary('ExplainWithAIButton', null, 'function')
const PodRestartIcon = importComponentFromFELibrary('PodRestartIcon')
+const RESOURCE_BROWSER_ROUTES = ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS
+
const K8sResourceListTableCellComponent = ({
field: columnName,
row: { id, data: resourceData },
@@ -67,7 +69,7 @@ const K8sResourceListTableCellComponent = ({
isEventListing,
lowercaseKindToResourceGroupMap,
}: K8sResourceListTableCellComponentProps) => {
- const { push } = useHistory()
+ const navigate = useNavigate()
const { clusterId } = useParams()
const isNodeListing = selectedResource?.gvk.Kind === Nodes.Node
const isNodeListingAndNodeHasErrors = isNodeListing && !!resourceData[NODE_LIST_HEADERS_TO_KEY_MAP.errors]
@@ -109,14 +111,14 @@ const K8sResourceListTableCellComponent = ({
group: _group || K8S_EMPTY_GROUP,
})
- push(`${url}/${tab}`)
+ navigate(`${url}/${tab}`)
}
const handleNodeClick = async (e: MouseEvent) => {
const { name } = e.currentTarget.dataset
const _url = generatePath(RESOURCE_BROWSER_ROUTES.NODE_DETAIL, { clusterId, name })
await addTab({ idPrefix: K8S_EMPTY_GROUP, kind: 'node', name, url: _url })
- push(_url)
+ navigate(_url)
}
useEffect(() => {
@@ -163,7 +165,7 @@ const K8sResourceListTableCellComponent = ({
const group =
getFirstResourceFromKindResourceMap(lowercaseKindToResourceGroupMap, kind)?.gvk?.Group || K8S_EMPTY_GROUP
- push(
+ navigate(
generatePath(RESOURCE_BROWSER_ROUTES.K8S_RESOURCE_DETAIL, {
clusterId,
namespace: resourceData.namespace as string,
@@ -286,7 +288,7 @@ const K8sResourceListTableCellComponent = ({
data-testid="created-resource-name"
>
-
+
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
-
diff --git a/src/components/app/details/appDetails/HibernateModal.tsx b/src/components/app/details/appDetails/HibernateModal.tsx
index bb089171af..382260015a 100644
--- a/src/components/app/details/appDetails/HibernateModal.tsx
+++ b/src/components/app/details/appDetails/HibernateModal.tsx
@@ -16,8 +16,8 @@
import { ConfirmationModal, ConfirmationModalVariantType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICHibernate } from '@Icons/ic-medium-hibernate.svg'
-import { ReactComponent as ICUnhibernate } from '@Icons/ic-medium-unhibernate.svg'
+import ICHibernate from '@Icons/ic-medium-hibernate.svg?react'
+import ICUnhibernate from '@Icons/ic-medium-unhibernate.svg?react'
import { importComponentFromFELibrary } from '@Components/common'
import { HibernationModalTypes } from './appDetails.type'
diff --git a/src/components/app/details/appDetails/IssuesCard.tsx b/src/components/app/details/appDetails/IssuesCard.tsx
index 5768597c5a..cbfb32d2aa 100644
--- a/src/components/app/details/appDetails/IssuesCard.tsx
+++ b/src/components/app/details/appDetails/IssuesCard.tsx
@@ -29,8 +29,8 @@ import {
LoadingCard,
stopPropagation,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICHelpOutline } from '../../../../assets/icons/ic-help-outline.svg'
-import { ReactComponent as ErrorIcon } from '../../../../assets/icons/ic-warning.svg'
+import ICHelpOutline from '../../../../assets/icons/ic-help-outline.svg?react'
+import ErrorIcon from '../../../../assets/icons/ic-warning.svg?react'
import { deleteArgoCDAppWithNonCascade, getClusterConnectionStatus } from './appDetails.service'
import { ClusterConnectionResponse, ErrorItem, IssuesCardType } from './appDetails.type'
import { TOAST_INFO } from '../../../../config/constantMessaging'
diff --git a/src/components/app/details/appDetails/IssuesListingModal.tsx b/src/components/app/details/appDetails/IssuesListingModal.tsx
index 5f6c12157a..2efe8426db 100644
--- a/src/components/app/details/appDetails/IssuesListingModal.tsx
+++ b/src/components/app/details/appDetails/IssuesListingModal.tsx
@@ -25,7 +25,7 @@ import {
Icon,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Error } from '../../../../assets/icons/ic-warning.svg'
+import Error from '../../../../assets/icons/ic-warning.svg?react'
import { IssuesListingModalType } from './appDetails.type'
const IssuesListingModal = ({ errorsList, closeIssuesListingModal }: IssuesListingModalType) => {
diff --git a/src/components/app/details/appDetails/LastUpdatedCard.tsx b/src/components/app/details/appDetails/LastUpdatedCard.tsx
index a2cbbb49a3..6b1685289d 100644
--- a/src/components/app/details/appDetails/LastUpdatedCard.tsx
+++ b/src/components/app/details/appDetails/LastUpdatedCard.tsx
@@ -15,21 +15,22 @@
*/
import React from 'react'
-import { useHistory, useRouteMatch } from 'react-router-dom'
+import { generatePath, useNavigate } from 'react-router-dom'
import { LoadingCard } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Timer } from '../../../../assets/icons/ic-clock-counterclockwise.svg'
+import Timer from '@Icons/ic-clock-counterclockwise.svg?react'
+
import { URLS } from '../../../../config'
import { LastUpdatedCardType } from './appDetails.type'
import { validateMomentDate } from './utils'
const LastUpdatedCard = ({ deploymentTriggerTime, triggeredBy, cardLoading }: LastUpdatedCardType) => {
- const history = useHistory()
- const match = useRouteMatch()
+ const navigate = useNavigate()
const goToDeploymentHistory = () => {
- history.push(`${match.url.split(URLS.APP_DETAILS)[0]}${URLS.APP_DEPLOYMNENT_HISTORY}`)
+ const url = generatePath(`../${URLS.APP_DEPLOYMNENT_HISTORY}`)
+ navigate(url)
}
if (cardLoading) {
diff --git a/src/components/app/details/appDetails/SecurityVulnerabilityCard.tsx b/src/components/app/details/appDetails/SecurityVulnerabilityCard.tsx
index 90877fd13e..4fe32062a0 100644
--- a/src/components/app/details/appDetails/SecurityVulnerabilityCard.tsx
+++ b/src/components/app/details/appDetails/SecurityVulnerabilityCard.tsx
@@ -28,9 +28,9 @@ import {
SeveritiesDTO,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Question } from '../../../../assets/icons/ic-help-outline.svg'
-import { ReactComponent as Bug } from '../../../../assets/icons/ic-nav-bug.svg'
-import { ReactComponent as Secure } from '../../../../assets/icons/ic-secure.svg'
+import Question from '../../../../assets/icons/ic-help-outline.svg?react'
+import Bug from '../../../../assets/icons/ic-nav-bug.svg?react'
+import Secure from '../../../../assets/icons/ic-secure.svg?react'
import { importComponentFromFELibrary } from '../../../common'
import { SecurityVulnerabilityCardType } from './appDetails.type'
import { useGetAppSecurityDetails } from './AppSecurity'
diff --git a/src/components/app/details/appDetails/SourceInfo.tsx b/src/components/app/details/appDetails/SourceInfo.tsx
index dbd0271e00..df47d9ebad 100644
--- a/src/components/app/details/appDetails/SourceInfo.tsx
+++ b/src/components/app/details/appDetails/SourceInfo.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useMemo, useState } from 'react'
+import { useMemo, useState, type JSX } from 'react'
import ReactGA from 'react-ga4'
import { Link, useParams } from 'react-router-dom'
import moment from 'moment'
@@ -35,15 +35,16 @@ import {
showError,
Tooltip,
URLS as CommonURLS,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICCamera } from '@Icons/ic-camera.svg'
-import { ReactComponent as Trash } from '@Icons/ic-delete-dots.svg'
-import { ReactComponent as LinkIcon } from '@Icons/ic-link.svg'
-import { ReactComponent as ICRollback } from '@Icons/ic-rollback.svg'
+import ICCamera from '@Icons/ic-camera.svg?react'
+import Trash from '@Icons/ic-delete-dots.svg?react'
+import LinkIcon from '@Icons/ic-link.svg?react'
+import ICRollback from '@Icons/ic-rollback.svg?react'
import HelmAppConfigApplyStatusCard from '@Components/v2/appDetails/sourceInfo/environmentStatus/HelmAppConfigApplyStatusCard'
-import { APP_COMPOSE_STAGE, getAppComposeURL, URLS } from '../../../../config'
+import { APP_COMPOSE_STAGE, getAppComposeURL } from '../../../../config'
import DeploymentTypeIcon from '../../../common/DeploymentTypeIcon/DeploymentTypeIcon'
import { importComponentFromFELibrary } from '../../../common/helpers/Helpers'
import { Nodes, SourceInfoType } from '../../types'
@@ -390,7 +391,7 @@ export const SourceInfo = ({
linkProps={{
to: isAppView
? `${getAppComposeURL(params.appId, APP_COMPOSE_STAGE.ENV_OVERRIDE, false, false)}/${params.envId}`
- : `${URLS.APPLICATION_MANAGEMENT_APPLICATION_GROUP}/${envId}/${CommonURLS.APP_CONFIG}/${appDetails?.appId}`,
+ : `${ROUTER_URLS.APP_GROUP}/${envId}/${CommonURLS.APP_CONFIG}/${appDetails?.appId}`,
}}
/>
@@ -511,7 +512,7 @@ export const SourceInfo = ({
Chart:
{appDetails.appStoreChartName}/{appDetails.appStoreAppName}(
{appDetails.appStoreAppVersion})
diff --git a/src/components/app/details/appDetails/__tests__/GenericInfo.test.tsx b/src/components/app/details/appDetails/__tests__/GenericInfo.test.tsx
deleted file mode 100644
index 65a1b05dcb..0000000000
--- a/src/components/app/details/appDetails/__tests__/GenericInfo.test.tsx
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-//@ts-nocheck
-import React from 'react'
-import ResourceTreeNodes, {
- GenericInfo,
- NestedTable,
- GenericRow,
- Name,
- NodeGroup,
- StatusFilterButton,
- AllPods,
-} from '../../../ResourceTreeNodes'
-import { BrowserRouter, Route, Router } from 'react-router-dom'
-import { nodes, podMetadata } from '../__mocks__/appDetails.mock'
-import { Nodes, AggregationKeys } from '../../../types'
-import { createMemoryHistory } from 'history'
-import '@testing-library/jest-dom'
-import { render, fireEvent, screen } from '@testing-library/react'
-import { aggregateNodes } from '@devtron-labs/devtron-fe-common-lib'
-
-describe('generic info testsuite', () => {
- let div, aggregatedNodes
-
- beforeAll(() => {
- div = document.createElement('div')
- aggregatedNodes = aggregateNodes(nodes, podMetadata)
- })
- it('genericinfo renders without crashing', () => {
- const Data = new Map()
- Data.set(
- 'colorful-pod-logs-amit-dev-698bcdb789',
- aggregatedNodes.nodes.ReplicaSet.get('colorful-pod-logs-amit-dev-698bcdb789'),
- )
- render(
- ,
- { wrapper: BrowserRouter },
- )
- })
-
- it('nested table renders without crashing', () => {
- const Data = new Map()
- Data.set(
- 'colorful-pod-logs-amit-dev-698bcdb789',
- aggregatedNodes.nodes.ReplicaSet.get('colorful-pod-logs-amit-dev-698bcdb789'),
- )
- render(
- ,
- { wrapper: BrowserRouter },
- )
- })
-
- it('nested table data row changes collpase state after click on collapsed', async () => {
- const Data = new Map()
- Data.set(
- 'colorful-pod-logs-amit-dev-698bcdb789',
- aggregatedNodes.nodes.ReplicaSet.get('colorful-pod-logs-amit-dev-698bcdb789'),
- )
- const { getByTestId } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
- fireEvent.click(getByTestId('collapse-icon'))
- expect(screen.getByText('colorful-pod-logs-amit-dev-698bcdb789-86ssw')).toBeInTheDocument() // check for pod
- })
-
- it('clicking on manifest opens manifest tab', async () => {
- let selectedNode = ''
- const table = document.createElement('table')
- const tBody = table.appendChild(document.createElement('tbody'))
- const tRow = tBody.appendChild(document.createElement('tr'))
- const { getByTestId, getAllByTestId } = render(
- {
- selectedNode = args
- }}
- />,
- {
- container: document.body.appendChild(tRow),
- wrapper: BrowserRouter,
- },
- )
- fireEvent.mouseEnter(getByTestId('colorful-pod-logs-amit-dev-698bcdb789-hover-trigger'))
- fireEvent.click(getAllByTestId('colorful-pod-logs-amit-dev-698bcdb789-manifest')[0])
- expect(location.search).toEqual('?kind=ReplicaSet')
- expect(selectedNode).toEqual('colorful-pod-logs-amit-dev-698bcdb789')
- })
-
- it('clicking on workflows uncollpases', async () => {
- const { getByTestId } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- fireEvent.click(getByTestId('Workloads'))
- expect(screen.getByTestId('Pod-anchor')).toBeInTheDocument()
- })
-
- it('clicking on status filter sets correct kind in URL and clicking on All resets ', async () => {
- const { getByTestId } = render(
- <>
-
-
- >,
- {
- wrapper: BrowserRouter,
- },
- )
-
- fireEvent.click(getByTestId('Running-filter-button'))
- expect(location.search.includes('status=Running'))
- fireEvent.click(getByTestId('All-filter-button'))
- expect(!location.pathname.includes('status=Running'))
- })
-})
-
-describe('genericrow test', () => {
- let div, aggregatedNodes
-
- beforeAll(() => {
- div = document.createElement('div')
- aggregatedNodes = aggregateNodes(nodes, podMetadata)
- })
-
- it('Clicking on Events, Manifest and Logs causes correct URL change and correct callback to parent for set state', async () => {
- let nodeName, containerName
- const table = document.createElement('table')
- const tBody = table.appendChild(document.createElement('tbody'))
-
- const { getByTestId } = render(
- {
- nodeName = node
- containerName = container
- }}
- level={1}
- podLevelExternalLinks={[]}
- containerLevelExternalLinks={[]}
- />,
- {
- container: document.body.appendChild(tBody),
- wrapper: BrowserRouter,
- },
- )
-
- fireEvent.mouseEnter(getByTestId('colorful-pod-logs-amit-dev-698bcdb789-86ssw-hover-trigger'))
-
- fireEvent.click(getByTestId('colorful-pod-logs-amit-dev-698bcdb789-86ssw-manifest'))
- expect(location.search.includes('kind=Pod'))
- expect(nodeName).toEqual('colorful-pod-logs-amit-dev-698bcdb789-86ssw')
-
- nodeName = ''
- fireEvent.click(getByTestId('colorful-pod-logs-amit-dev-698bcdb789-86ssw-events'))
- expect(nodeName).toEqual('colorful-pod-logs-amit-dev-698bcdb789-86ssw')
-
- nodeName = ''
- fireEvent.click(getByTestId('colorful-pod-logs-amit-dev-698bcdb789-86ssw-logs'))
- expect(nodeName).toEqual('colorful-pod-logs-amit-dev-698bcdb789-86ssw')
-
- nodeName = ''
- fireEvent.click(getByTestId('collapse-icon'))
- fireEvent.click(getByTestId('colorful-pod-logs-logs'))
- expect(nodeName).toEqual('colorful-pod-logs-amit-dev-698bcdb789-86ssw')
- expect(containerName).toEqual('colorful-pod-logs')
- })
-})
-
-function renderWithRouter(ui, { route = '/', history = createMemoryHistory({ initialEntries: [route] }) } = {}) {
- return {
- ...render({ui}),
- history,
- }
-}
-
-describe('resource tree nodes', () => {
- let div, aggregatedNodes
-
- beforeAll(() => {
- div = document.createElement('div')
- aggregatedNodes = aggregateNodes(nodes, podMetadata)
- })
-
- it('automatically redirects to Pod when kind is not defined and pod is present', async () => {
- const { history } = renderWithRouter(
-
-
- ,
- { route: '/', history: createMemoryHistory({ initialEntries: ['app/3/details/3'] }) },
- )
- expect(history.location.pathname).toContain('Pod')
- })
-})
-
-describe('all pods renders', () => {
- let div, aggregatedNodes
-
- beforeAll(() => {
- div = document.createElement('div')
- aggregatedNodes = aggregateNodes(nodes, podMetadata)
- })
-
- it('all pods renders without breaking', async () => {
- const { getByTestId } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
- expect(getByTestId('new-pod-status-running')).toHaveTextContent('1 running')
- })
-
- it('No pods is rendered when pods not available', async () => {
- const { getByTestId } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
- fireEvent.click(getByTestId('all-pods-old'))
- expect(getByTestId('no-pod')).toBeInTheDocument()
- })
-})
diff --git a/src/components/app/details/appDetails/appDetails.type.ts b/src/components/app/details/appDetails/appDetails.type.ts
index 7fec8fdde8..71547ba236 100644
--- a/src/components/app/details/appDetails/appDetails.type.ts
+++ b/src/components/app/details/appDetails/appDetails.type.ts
@@ -24,6 +24,7 @@ import {
EnvAppsMetaDTO,
OptionType,
ResponseType,
+ ScanRecommendationsDTO,
ScanResultDTO,
SelectPickerProps,
ServerErrors,
@@ -50,17 +51,11 @@ export enum StatusType {
Throughput = 'Throughput',
}
-export enum CalendarFocusInput {
- StartDate = 'startDate',
- EndDate = 'endDate',
-}
-
export type AppMetricsTabType = 'aggregate' | 'pod'
export type ChartTypes = 'cpu' | 'ram' | 'status' | 'latency'
export type StatusTypes = '5xx' | '4xx' | '2xx' | 'Throughput'
-export type CalendarFocusInputType = 'startDate' | 'endDate'
-export interface AppDetailsPathParams {
+export type AppDetailsPathParams = {
appId: string
envId?: string
}
@@ -182,6 +177,7 @@ export interface UseGetAppSecurityDetailsProps {
envId?: number
installedAppId?: number
artifactId?: number
+ buildId?: number
}
export interface UseGetAppSecurityDetailsReturnType {
scanResultLoading: boolean
@@ -189,6 +185,12 @@ export interface UseGetAppSecurityDetailsReturnType {
scanResultError: ServerErrors
reloadScanResult: () => void
}
+export interface UseSecurityRecommendationReturnType {
+ scanRecommendationsResultLoading: boolean
+ scanRecommendationsResultResponse: ResponseType
+ scanRecommendationsResultError: ServerErrors
+ reloadScanRecommendationsResult: () => void
+}
export enum HibernationModalTypes {
HIBERNATE = 'hibernate',
diff --git a/src/components/app/details/appDetails/constants.ts b/src/components/app/details/appDetails/constants.ts
index cde6830ca6..08613d98a2 100644
--- a/src/components/app/details/appDetails/constants.ts
+++ b/src/components/app/details/appDetails/constants.ts
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-import { UaEventOptions } from 'react-ga4/types/ga4'
+import { HandleAnalyticsEventParams } from '@devtron-labs/devtron-fe-common-lib'
export const APP_METRICS_CALENDAR_INPUT_DATE_FORMAT = 'DD-MM-YYYY hh:mm:ss'
-export const DA_APP_DETAILS_GA_EVENTS: Record> = {
+export const DA_APP_DETAILS_GA_EVENTS: Record = {
DeployButtonClicked: {
category: 'App Details',
action: 'DA_APP_DETAIL_DEPLOY',
@@ -41,7 +41,7 @@ export const DA_APP_DETAILS_GA_EVENTS: Record> = {
+export const AG_APP_DETAILS_GA_EVENTS: Record = {
DeployButtonClicked: {
category: 'App Details',
action: 'AG_APP_DETAIL_DEPLOY',
diff --git a/src/components/app/details/appDetails/types.ts b/src/components/app/details/appDetails/types.ts
index ee51a1d8fc..353de57ddc 100644
--- a/src/components/app/details/appDetails/types.ts
+++ b/src/components/app/details/appDetails/types.ts
@@ -15,6 +15,7 @@
*/
import { SyntheticEvent } from 'react'
+import moment from 'moment'
import { AppMetricsTabType, ChartTypes, HibernationModalTypes, StatusTypes } from './appDetails.type'
import { AppInfo } from './utils'
@@ -39,3 +40,8 @@ export interface HibernationModalProps {
hibernateConfirmationModal: HibernationModalTypes
handleHibernateConfirmationModalClose: (e?: SyntheticEvent) => void
}
+
+export type GrafanaPresetOptionHandlerType = (
+ startString: string,
+ momentDiff: { magnitude: number; unit: moment.unitOfTime.DurationConstructor },
+) => void
diff --git a/src/components/app/details/appDetails/utils.tsx b/src/components/app/details/appDetails/utils.tsx
index 1eb766e9f6..8d3d3fda2d 100644
--- a/src/components/app/details/appDetails/utils.tsx
+++ b/src/components/app/details/appDetails/utils.tsx
@@ -31,9 +31,9 @@ import {
AppEnvironment,
SelectPickerOptionType,
IconsProps,
- DayPickerRangeControllerPresets,
+ DateTimePickerProps,
} from '@devtron-labs/devtron-fe-common-lib'
-import { GetIFrameSrcParamsType } from './types'
+import { GetIFrameSrcParamsType, GrafanaPresetOptionHandlerType } from './types'
export function getAggregator(nodeType: NodeType, defaultAsOtherResources?: boolean): AggregationKeys {
switch (nodeType) {
@@ -140,18 +140,15 @@ export const LatencySelect = (props) => {
)
}
-export function getCalendarValue(startDateStr: string, endDateStr: string): string {
- let str: string = `${startDateStr} - ${endDateStr}`
- if (endDateStr === 'now' && startDateStr.includes('now')) {
- const range = DayPickerRangeControllerPresets.find((d) => d.endStr === startDateStr)
- if (range) {
- str = range.text
- } else {
- str = `${startDateStr} - ${endDateStr}`
- }
- }
- return str
-}
+export const getAppMetricsPresetOptions = (onClick: GrafanaPresetOptionHandlerType): DateTimePickerProps['rangeShortcutOptions'] => [
+ { label: 'Last 5 minutes', onClick: () => onClick('now-5m', { magnitude: 5, unit: 'minutes' }) },
+ { label: 'Last 30 minutes', onClick: () => onClick('now-30m', { magnitude: 30, unit: 'minutes' }) },
+ { label: 'Last 1 hour', onClick: () => onClick('now-1h', { magnitude: 1, unit: 'hours' }) },
+ { label: 'Last 24 hours', onClick: () => onClick('now-24h', { magnitude: 24, unit: 'hours' }) },
+ { label: 'Last 7 days', onClick: () => onClick('now-7d', { magnitude: 7, unit: 'days' }) },
+ { label: 'Last 1 month', onClick: () => onClick('now-1M', { magnitude: 1, unit: 'months' }) },
+ { label: 'Last 6 months', onClick: () => onClick('now-6M', { magnitude: 6, unit: 'months' }) },
+]
export function isK8sVersionValid(k8sVersion: string): boolean {
if (!k8sVersion) {
diff --git a/src/components/app/details/cIDetails/CIDetails.tsx b/src/components/app/details/cIDetails/CIDetails.tsx
index 4bb7fd4f71..da04aaf11d 100644
--- a/src/components/app/details/cIDetails/CIDetails.tsx
+++ b/src/components/app/details/cIDetails/CIDetails.tsx
@@ -14,58 +14,56 @@
* limitations under the License.
*/
-import { useState, useEffect, useMemo } from 'react'
+import { useEffect, useMemo, useState } from 'react'
+import { generatePath, Navigate, Route, Routes, useNavigate, useParams } from 'react-router-dom'
+
import {
- showError,
- Progressing,
- Reload,
- GenericEmptyState,
- useAsync,
- PipelineType,
- Sidebar,
- LogResizeButton,
+ Artifacts,
+ asyncWrap,
CICDSidebarFilterOptionType,
- History,
- HistoryComponentType,
+ EMPTY_STATE_STATUS,
+ ErrorScreenManager,
FetchIdDataStatus,
- Scroller,
+ GenericEmptyState,
GitChanges,
- TriggerDetails,
- useScrollable,
- useInterval,
- mapByKey,
- asyncWrap,
- Artifacts,
+ History,
+ HistoryComponentType,
+ LogResizeButton,
LogsRenderer,
+ mapByKey,
ModuleNameMap,
- EMPTY_STATE_STATUS,
+ PipelineType,
+ Progressing,
+ Reload,
+ sanitizeTargetPlatforms,
+ Scroller,
+ showError,
+ Sidebar,
TabGroup,
TRIGGER_STATUS_PROGRESSING,
- ErrorScreenManager,
- SecurityDetailsCards,
- sanitizeTargetPlatforms,
+ URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { Switch, Route, Redirect, useRouteMatch, useParams, useHistory, generatePath } from 'react-router-dom'
+import './ciDetails.scss'
+import { TriggerDetails, useAsync, useInterval, useScrollable, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
+
+import { Routes as ROUTES } from '../../../../config'
+import { CIPipelineBuildType } from '../../../ciPipeline/types'
+import { getModuleInfo } from '../../../v2/devtronStackManager/DevtronStackManager.service'
+import { ModuleStatus } from '../../../v2/devtronStackManager/DevtronStackManager.type'
import {
- getCIPipelines,
+ getArtifactForJobCi,
getCIHistoricalStatus,
- getTriggerHistory,
+ getCIPipelines,
getTagDetails,
- getArtifactForJobCi,
+ getTriggerHistory,
} from '../../service'
-import { URLS, Routes } from '../../../../config'
-import { BuildDetails, CIPipeline, HistoryLogsType, SecurityTabType } from './types'
-import { ImageNotScannedView, CIRunningView } from './cIDetails.util'
-import './ciDetails.scss'
-import { getModuleInfo } from '../../../v2/devtronStackManager/DevtronStackManager.service'
-import { ModuleStatus } from '../../../v2/devtronStackManager/DevtronStackManager.type'
import { getModuleConfigured } from '../appDetails/appDetails.service'
-import { CIPipelineBuildType } from '../../../ciPipeline/types'
+import { SecurityTab } from './SecurityTab'
import { renderCIListHeader, renderDeploymentHistoryTriggerMetaText } from '../cdDetails/utils'
-import { importComponentFromFELibrary } from '@Components/common'
-import { useGetAppSecurityDetails } from '../appDetails/AppSecurity'
+import { BuildDetails, CIPipeline, HistoryLogsType, SecurityTabType } from './types'
+
+import './ciDetails.scss'
-const SecurityModalSidebar = importComponentFromFELibrary('SecurityModalSidebar', null, 'function')
const terminalStatus = new Set(['succeeded', 'failed', 'error', 'cancelled', 'nottriggered', 'notbuilt'])
const statusSet = new Set(['starting', 'running', 'pending'])
@@ -85,6 +83,8 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
const [hideImageTaggingHardDelete, setHideImageTaggingHardDelete] = useState(false)
const [fetchBuildIdData, setFetchBuildIdData] = useState(null)
+ const ciDetailsPath = isJobView ? ROUTER_URLS.JOB_DETAIL.CI_DETAILS : ROUTER_URLS.DEVTRON_APP_DETAILS.CI_DETAILS
+
const triggerDetails = triggerHistory?.get(+buildId)
// This is only meant for logsRenderer
const [scrollableParentRef, scrollToTop, scrollToBottom] = useScrollable({
@@ -106,8 +106,7 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
!!pipelineId,
)
- const { path } = useRouteMatch()
- const { push, replace } = useHistory()
+ const navigate = useNavigate()
useInterval(pollHistory, 30000)
useEffect(() => {
@@ -143,14 +142,15 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
setTriggerHistory(new Map(newTriggerHistory))
}, [triggerHistoryResult])
- useEffect(() => {
- return () => {
+ useEffect(
+ () => () => {
setTriggerHistory(new Map())
setHasMoreLoading(false)
setHasMore(false)
setFetchBuildIdData(null)
- }
- }, [pipelineId])
+ },
+ [pipelineId],
+ )
function synchroniseState(triggerId: number, triggerDetails: History, triggerDetailsError: any) {
if (triggerDetailsError) {
@@ -192,35 +192,60 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
setTriggerHistory(new Map(mapByKey(triggerHistoryResult.result.ciWorkflows, 'id')))
}
setFetchBuildIdData(FetchIdDataStatus.SUSPEND)
- replace(generatePath(path, { appId, pipelineId }))
+ navigate(
+ generatePath(`${ciDetailsPath}/:pipelineId`, {
+ appId,
+ pipelineId,
+ }),
+ { replace: true },
+ )
}
if ((!hasMoreLoading && loading) || initDataLoading || (pipelineId && dependencyState[0] !== pipelineId)) {
return
}
if (pipelineId && !buildId && triggerHistory.size > 0) {
- replace(generatePath(path, { buildId: triggerHistory.entries().next().value[0], appId, pipelineId }))
+ navigate(
+ generatePath(`${ciDetailsPath}/:pipelineId/:buildId`, {
+ buildId: triggerHistory.entries().next().value[0],
+ appId,
+ pipelineId,
+ }),
+ {
+ replace: true,
+ },
+ )
}
- const pipelines: CIPipeline[] = (initDataResults[0]?.['value']?.['result'] || [])?.filter(
+ const pipelines: CIPipeline[] = (initDataResults[0]?.['value']?.result || [])?.filter(
(pipeline) => pipeline.pipelineType !== 'EXTERNAL' && pipeline.pipelineType !== PipelineType.LINKED_CD,
) // external and LINKED_CD pipelines not visible in dropdown
const selectedPipelineExist = !pipelineId || pipelines.find((pipeline) => pipeline.id === +pipelineId)
if (!pipelines.length && pipelineId) {
// reason is un-required params like logs were leaking
- replace(generatePath(path, { appId }))
+ navigate(generatePath(ciDetailsPath, { appId }), { replace: true })
} else if ((pipelines.length === 1 && !pipelineId) || (!selectedPipelineExist && pipelines.length)) {
- replace(generatePath(path, { appId, pipelineId: pipelines[0].id }))
+ navigate(
+ generatePath(`${ciDetailsPath}/:pipelineId`, {
+ appId,
+ pipelineId: String(pipelines[0].id),
+ }),
+ { replace: true },
+ )
}
- const pipelineOptions: CICDSidebarFilterOptionType[] = (pipelines || []).map((item) => {
- return { value: `${item.id}`, label: item.name, pipelineId: item.id, pipelineType: item.pipelineType }
- })
+ const pipelineOptions: CICDSidebarFilterOptionType[] = (pipelines || []).map((item) => ({
+ value: `${item.id}`,
+ label: item.name,
+ pipelineId: item.id,
+ pipelineType: item.pipelineType,
+ }))
const pipelinesMap = mapByKey(pipelines, 'id')
const pipeline = pipelinesMap.get(+pipelineId)
const redirectToArtifactLogs = () => {
- push(`${URLS.APPLICATION_MANAGEMENT_APP}/${pipeline.parentAppId}/${URLS.APP_CI_DETAILS}/${pipeline.parentCiPipeline}/logs`)
+ navigate(generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.CI_DETAILS}/logs`, { appId: pipeline.parentAppId }))
}
+
const renderSourcePipelineButton = () => {
return (
@@ -240,6 +265,7 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
setPagination={setPagination}
fetchIdData={fetchBuildIdData}
handleViewAllHistory={handleViewAllHistory}
+ path={`${isJobView ? ROUTER_URLS.JOB_DETAIL.CI_DETAILS : ROUTER_URLS.DEVTRON_APP_DETAILS.CI_DETAILS}/:pipelineId?/:buildId?`}
/>
)}
@@ -257,21 +283,18 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
pipeline && (
<>
{triggerHistory.size > 0 || fetchBuildIdData ? (
-
+ pipelineId &&
+ buildId && (
-
+ )
) : pipeline.parentCiPipeline || pipeline.pipelineType === 'LINKED' ? (
// Empty state if there is no linked pipeline
{
- const { path } = useRouteMatch()
const { pipelineId, buildId } = useParams<{ buildId: string; pipelineId: string }>()
const [ciJobArtifact, setciJobArtifact] = useState([])
const [loading, setLoading] = useState(false)
- const downloadArtifactUrl = `${Routes.CI_CONFIG_GET}/${pipelineId}/artifacts/${buildId}`
+ const downloadArtifactUrl = `${ROUTES.CI_CONFIG_GET}/${pipelineId}/artifacts/${buildId}`
const targetPlatforms = sanitizeTargetPlatforms(triggerDetails.targetPlatforms)
useEffect(() => {
@@ -559,136 +582,105 @@ const HistoryLogs = ({
}
}
- const CiArtifactsArrayCards = Array.from({ length: ciJobArtifact?.length }, (_, index) => {
+ const CiArtifactsArrayCards = Array.from({ length: ciJobArtifact?.length }, (_, index) => (
// TargetPlatforms are not supported for Artifacts in case of JobCI
- return (
-
- )
- })
+
+ ))
return (
-
-
-
- {(scrollToTop || scrollToBottom) && (
-
- )}
-
-
-
-
-
- {loading ? (
-
- ) : (
-
- {isJobCI && CiArtifactsArrayCards}
-
+
+
-
- )}
-
- {
-
+ {(scrollToTop || scrollToBottom) && (
+
+ )}
+ >
+ }
+ />
+
+ }
+ />
+
+ ) : (
+
+ {isJobCI && CiArtifactsArrayCards}
+
+
+ )
+ }
+ />
+
-
- }
-
-
-
- )
-}
-
-const SecurityTab = ({ artifactId, status, appIdFromParent }: SecurityTabType) => {
- const { appId } = useParams<{ appId: string }>()
-
- const computedAppId = appId ?? appIdFromParent
-
- const { scanResultLoading, scanResultResponse, scanResultError, reloadScanResult } = useGetAppSecurityDetails({
- appId: +computedAppId,
- artifactId,
- })
-
- if (['starting', 'running'].includes(status.toLowerCase())) {
- return
- }
-
- if (!artifactId) {
- return (
-
- )
- }
-
- if (scanResultLoading) {
- return (
-
- )
- }
- if (scanResultError) {
- return
- }
- if (!scanResultResponse?.result.scanned) {
- return
- }
-
- return (
-
-
+
+ }
+ />
+
)
}
diff --git a/src/components/app/details/cIDetails/SecurityTab.tsx b/src/components/app/details/cIDetails/SecurityTab.tsx
new file mode 100644
index 0000000000..251a606fb7
--- /dev/null
+++ b/src/components/app/details/cIDetails/SecurityTab.tsx
@@ -0,0 +1,147 @@
+import { useParams } from 'react-router-dom'
+
+import {
+ EMPTY_STATE_STATUS,
+ ErrorScreenManager,
+ GenericEmptyState,
+ Progressing,
+ ReportTabEmptyState,
+ SecurityDetailsCards,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { importComponentFromFELibrary } from '@Components/common'
+
+import { useGetAppSecurityDetails, useGetAppSecurityDetailsRecommendations } from '../appDetails/AppSecurity'
+import { CIRunningView } from './cIDetails.util'
+import { SecurityTabType } from './types'
+
+const SecurityModalSidebar = importComponentFromFELibrary('SecurityModalSidebar', null, 'function')
+const DockerfileScansRecommendations = importComponentFromFELibrary('DockerfileScansRecommendations')
+const getSecurityScanRecommendationTitle = importComponentFromFELibrary(
+ 'getSecurityScanRecommendationTitle',
+ null,
+ 'function',
+)
+
+export const SecurityTab = ({ artifactId, status, appIdFromParent }: SecurityTabType) => {
+ const { appId, buildId } = useParams<{ appId: string; buildId: string }>()
+
+ const computedAppId = appId ?? appIdFromParent
+
+ const { scanResultLoading, scanResultResponse, scanResultError, reloadScanResult } = useGetAppSecurityDetails({
+ appId: +computedAppId,
+ artifactId,
+ })
+
+ const {
+ scanRecommendationsResultLoading,
+ scanRecommendationsResultResponse,
+ scanRecommendationsResultError,
+ reloadScanRecommendationsResult,
+ } = useGetAppSecurityDetailsRecommendations({
+ appId: +computedAppId,
+ buildId: +buildId,
+ })
+
+ const renderDockerfileScannerContent = () => {
+ const scanResult = scanRecommendationsResultResponse?.result
+ if (scanRecommendationsResultError) {
+ return (
+
+
+
+ )
+ }
+
+ if (scanResult?.status === 3) {
+ return (
+
+ )
+ }
+
+ if ((scanRecommendationsResultLoading || scanResult?.scanEnabled) && !scanResult?.results) {
+ return (
+
+ )
+ }
+ if (!scanResult?.scanEnabled && !scanResult?.results) {
+ return
+ }
+
+ return
+ }
+
+ const renderDockerfileScanRecommendation = () => (
+
+ {!!getSecurityScanRecommendationTitle && getSecurityScanRecommendationTitle()}
+
{renderDockerfileScannerContent()}
+
+ )
+
+ const renderHeader = () => (
+
+ Security Scan
+
+ )
+
+ const renderSecurityScanContent = () => {
+ const normalizedStatus = status?.toLowerCase()
+
+ if (scanResultLoading) {
+ return (
+
+ )
+ }
+
+ if (['starting', 'running'].includes(normalizedStatus)) {
+ return
+ }
+
+ if (!artifactId) {
+ return (
+
+
+
+ )
+ }
+
+ if (scanResultError) {
+ return (
+
+
+
+ )
+ }
+ return
+ }
+
+ const renderSecurityDetailsCards = () => (
+
+ {!scanResultResponse?.result && renderHeader()}
+ {renderSecurityScanContent()}
+
+ )
+
+ return (
+
+
+ {DockerfileScansRecommendations && renderDockerfileScanRecommendation()}
+ {renderSecurityDetailsCards()}
+
+
+ )
+}
diff --git a/src/components/app/details/cIDetails/cIDetails.util.tsx b/src/components/app/details/cIDetails/cIDetails.util.tsx
index 9a54a8ebdc..8e2a1d7b84 100644
--- a/src/components/app/details/cIDetails/cIDetails.util.tsx
+++ b/src/components/app/details/cIDetails/cIDetails.util.tsx
@@ -15,25 +15,17 @@
*/
import { GenericEmptyState, EMPTY_STATE_STATUS } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ScannedDisabled } from '@Images/ic-empty-scanner-disabled.svg'
-import { ReactComponent as MechanicalOperation } from '@Images/ic-mechanical-operation.svg'
+import MechanicalOperation from '@Images/ic-mechanical-operation.svg?react'
-export const ImageNotScannedView = () => {
- return (
-
- )
-}
export const CIRunningView = (props) => {
return (
+
+
)
-}
+}
\ No newline at end of file
diff --git a/src/components/app/details/cdDetails/CDDetails.tsx b/src/components/app/details/cdDetails/CDDetails.tsx
index 849508a2ca..cf12d8ce9b 100644
--- a/src/components/app/details/cdDetails/CDDetails.tsx
+++ b/src/components/app/details/cdDetails/CDDetails.tsx
@@ -14,36 +14,39 @@
* limitations under the License.
*/
-import { useState, useEffect } from 'react'
+import { useEffect, useState } from 'react'
+import { generatePath, useLocation, useNavigate, useParams } from 'react-router-dom'
+
import {
- showError,
- Progressing,
- GenericEmptyState,
- DeploymentAppTypes,
- useAsync,
- Sidebar,
- TriggerOutput,
- ModuleNameMap,
- useInterval,
- mapByKey,
asyncWrap,
CICDSidebarFilterOptionType,
+ DeploymentAppTypes,
+ EMPTY_STATE_STATUS,
+ FetchIdDataStatus,
+ GenericEmptyState,
+ getTriggerHistory,
History,
HistoryComponentType,
- FetchIdDataStatus,
LogResizeButton,
- getTriggerHistory,
- useScrollable,
+ mapByKey,
+ ModuleNameMap,
+ Progressing,
+ showError,
+ Sidebar,
TRIGGER_STATUS_PROGRESSING,
AppEnvironment,
+ TriggerOutput,
+ useAsync,
+ useInterval,
+ useScrollable,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { useHistory, useRouteMatch, useParams, generatePath, useLocation, Route } from 'react-router-dom'
+
import { useAppContext } from '@Components/common'
+
import { getAppOtherEnvironmentMin, getCDConfig as getCDPipelines } from '../../../../services/service'
import { AppNotConfigured } from '../appDetails/AppDetails'
-import './cdDetail.scss'
import { getModuleConfigured } from '../appDetails/appDetails.service'
-import { EMPTY_STATE_STATUS } from '../../../../config/constantMessaging'
import {
getUpdatedTriggerId,
processVirtualEnvironmentDeploymentData,
@@ -55,6 +58,8 @@ import {
renderVirtualHistoryArtifacts,
} from './utils'
+import './cdDetail.scss'
+
export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }) {
const location = useLocation()
const { appId, envId, triggerId, pipelineId } = useParams<{
@@ -89,8 +94,7 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
const [envOptions, setEnvOptions] = useState([])
const [selectedEnv, setSelectedEnv] = useState(null)
const [deploymentAppType, setDeploymentAppType] = useState(null)
- const { path } = useRouteMatch()
- const { replace } = useHistory()
+ const navigate = useNavigate()
useInterval(pollHistory, 30000)
const [fetchTriggerIdData, setFetchTriggerIdData] = useState(null)
@@ -135,13 +139,16 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
setHideImageTaggingHardDelete(deploymentHistoryResult?.result?.hideImageTaggingHardDelete || false)
if (!triggerId && envId && pipelineId && deploymentHistoryResult.result?.cdWorkflows?.length) {
- replace(
- generatePath(path, {
+ navigate(
+ generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS}/:envId/:pipelineId/:triggerId`, {
appId,
envId,
pipelineId,
- triggerId: _triggerId,
+ triggerId: String(_triggerId),
}),
+ {
+ replace: true,
+ },
)
}
@@ -157,7 +164,13 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
//Result Typing to be fixed here
useEffect(() => {
if (!envId && environmentId) {
- replace(generatePath(path, { envId: environmentId, appId }))
+ navigate(
+ generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS}/:envId`, {
+ envId: String(environmentId),
+ appId,
+ }),
+ { replace: true },
+ )
return
}
if (result) {
@@ -170,12 +183,8 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
if (result[0]) {
const filteredEnvMap = filteredEnvIds?.split(',').reduce((agg, curr) => agg.set(curr, true), new Map())
const _selectedEnvironment = (result[0]['value']?.result || [])
- .filter((env) => {
- return !filteredEnvMap || filteredEnvMap.get(env.environmentId)
- })
- .find((envData) => {
- return +envId === envData.environmentId
- })
+ .filter((env) => !filteredEnvMap || filteredEnvMap.get(env.environmentId))
+ .find((envData) => +envId === envData.environmentId)
setSelectedEnv(_selectedEnvironment)
}
setEnvironmentId(+envId)
@@ -201,9 +210,7 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
let isEnvDeleted = false
const filteredEnvMap = filteredEnvIds?.split(',').reduce((agg, curr) => agg.set(+curr, true), new Map())
const envOptions: CICDSidebarFilterOptionType[] = (result[0]['value']?.result || [])
- .filter((env) => {
- return !filteredEnvMap || filteredEnvMap.get(env.environmentId)
- })
+ .filter((env) => !filteredEnvMap || filteredEnvMap.get(env.environmentId))
.map((envData) => {
if (envData.environmentId === +envId) {
_selectedEnvironment = envData
@@ -224,16 +231,24 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
(envId && envOptions.length && !_selectedEnvironment)
) {
setEnvironmentId(+envOptions[0].value)
- replace(generatePath(path, { appId, envId: envOptions[0].value, pipelineId: envOptions[0].pipelineId }))
+ navigate(
+ generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS}/:envId/:pipelineId`, {
+ appId,
+ envId: envOptions[0].value,
+ pipelineId: String(envOptions[0].pipelineId),
+ }),
+ { replace: true },
+ )
} else if (envId && !pipelineId && _selectedEnvironment) {
// Update the pipeline id when the selected environment is available and pipeline id is not available
setEnvironmentId(_selectedEnvironment.environmentId)
- replace(
- generatePath(path, {
+ navigate(
+ generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS}/:envId/:pipelineId`, {
appId,
envId: _selectedEnvironment.environmentId,
pipelineId: cdPipelinesMap.get(_selectedEnvironment.environmentId)?.id,
}),
+ { replace: true },
)
}
setEnvOptions(envOptions)
@@ -272,13 +287,14 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
setTriggerHistory(mapByKey(deploymentHistoryResult.result.cdWorkflows, 'id'))
}
setFetchTriggerIdData(FetchIdDataStatus.SUSPEND)
- replace(
- generatePath(path, {
+ navigate(
+ generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS}/:envId/:pipelineId/:triggerId`, {
appId,
envId,
pipelineId,
- triggerId: deploymentHistoryResult.result?.cdWorkflows?.[0]?.id,
+ triggerId: String(deploymentHistoryResult.result?.cdWorkflows?.[0]?.id),
}),
+ { replace: true },
)
}
@@ -305,17 +321,15 @@ export default function CDDetails({ filteredEnvIds }: { filteredEnvIds: string }
fetchIdData={fetchTriggerIdData}
handleViewAllHistory={handleViewAllHistory}
renderRunSource={renderRunSource}
+ path={`${ROUTER_URLS.DEVTRON_APP_DETAILS.CD_DETAILS}/:envId?/:pipelineId?/:triggerId?`}
/>
)}
{triggerHistory.size > 0 || fetchTriggerIdData ? (
-
+ envId &&
+ pipelineId && (
-
+ )
) : !envId ? (
{
@@ -32,7 +30,7 @@ export const EmptyView = ({ imgSrc, title, subTitle, link, linkText, rootClassNa
}
return (
import('./cIDetails/CIDetails'))
const AppDetails = lazy(() => import('./appDetails/AppDetails'))
const CDDetails = lazy(() => import('./cdDetails/CDDetails'))
+const APP_DETAILS_ROUTES = BASE_ROUTES.APPLICATION_MANAGEMENT.DEVTRON_APP.DETAIL
+
export default function AppDetailsPage() {
- const { path } = useRouteMatch()
const { appId } = useParams<{ appId: string }>()
const { setIntelligenceConfig } = useMainContext()
const [appName, setAppName] = useState('')
@@ -382,50 +382,57 @@ export default function AppDetailsPage() {
}>
-
+
(
+ path={`${URLS.APP_DETAILS}/:envId?/*`}
+ element={
- )}
+ }
+ />
+
+ }
+ />
+ }
/>
-
-
-
}
+ path={`${APP_DETAILS_ROUTES.CI_DETAILS}/:pipelineId?/:buildId?/*`}
+ element={}
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
+ path={`${APP_DETAILS_ROUTES.DEPLOYMENT_METRICS}/:envId?`}
+ element={}
+ />
+ }
+ />
+
+ }
+ />
+ } />
+
diff --git a/src/components/app/details/metrics/BenchmarkModal.tsx b/src/components/app/details/metrics/BenchmarkModal.tsx
index 0651a7773e..2d28418da2 100644
--- a/src/components/app/details/metrics/BenchmarkModal.tsx
+++ b/src/components/app/details/metrics/BenchmarkModal.tsx
@@ -17,8 +17,8 @@
import React, { Component } from 'react'
import ReactGA from 'react-ga4'
import { VisibleModal } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Close } from '../../../../assets/icons/ic-close.svg'
-import { ReactComponent as Tip } from '../../../../assets/icons/ic-bulb.svg'
+import Close from '../../../../assets/icons/ic-close.svg?react'
+import Tip from '../../../../assets/icons/ic-bulb.svg?react'
import { renderCategoryTag, getTimeperiod } from './deploymentMetrics.util'
export interface BenchmarkModalProps {
diff --git a/src/components/app/details/metrics/DeploymentMetrics.tsx b/src/components/app/details/metrics/DeploymentMetrics.tsx
index 2c4c1c37ca..edc365c575 100644
--- a/src/components/app/details/metrics/DeploymentMetrics.tsx
+++ b/src/components/app/details/metrics/DeploymentMetrics.tsx
@@ -16,33 +16,34 @@
import { useEffect, useMemo, useState } from 'react'
import ReactGA from 'react-ga4'
-import { generatePath, useHistory, useParams, useRouteMatch } from 'react-router-dom'
+import { generatePath, useNavigate, useParams } from 'react-router-dom'
import moment from 'moment'
import {
Chart,
ChartColorKey,
ChartProps,
+ DateTimePicker,
EMPTY_STATE_STATUS,
ErrorScreenManager,
GenericEmptyState,
Progressing,
+ ROUTER_URLS,
SelectPicker,
showError,
Tooltip,
- URLS,
+ UpdateDateRangeType,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Success } from '@Icons/appstatus/healthy.svg'
-import { ReactComponent as Fail } from '@Icons/ic-error-exclamation.svg'
-import { ReactComponent as ICHelpOutline } from '@Icons/ic-help-outline.svg'
-import { ReactComponent as Deploy } from '@Icons/ic-nav-rocket.svg'
-import AppNotDeployed from '@Images/app-not-deployed.svg'
+import Success from '@Icons/appstatus/healthy.svg?react'
+import Fail from '@Icons/ic-error-exclamation.svg?react'
+import ICHelpOutline from '@Icons/ic-help-outline.svg?react'
+import Deploy from '@Icons/ic-nav-rocket.svg?react'
import SelectEnvImage from '@Images/ic-empty-dep-metrics@2x.png'
import { ViewType } from '../../../../config'
import { getAppOtherEnvironmentMin } from '../../../../services/service'
-import { DatePicker, useAppContext } from '../../../common'
+import { useAppContext } from '../../../common'
import { BenchmarkModal } from './BenchmarkModal'
import { getDeploymentMetrics } from './deploymentMetrics.service'
import {
@@ -61,6 +62,7 @@ import {
} from './deploymentMetrics.util'
import { DeploymentTable } from './DeploymentTable'
import { DeploymentTableModal } from './DeploymentTableModal'
+import { getRangeShortcutOptions } from './utils'
import './deploymentMetrics.scss'
@@ -219,7 +221,7 @@ const RecoveryAndLeadTimeGraphLegend = ({
const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps) => {
const { appId, envId } = useParams<{ appId: string; envId: string }>()
- const history = useHistory()
+ const navigate = useNavigate()
const [state, setState] = useState({
code: 0,
@@ -239,7 +241,6 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
benchmarkModalData: undefined,
startDate: moment().set({ hour: 0, minute: 0, seconds: 0 }).subtract(6, 'months'),
endDate: moment().set({ hour: 23, minute: 59, seconds: 59, milliseconds: 999 }),
- focusedInput: null,
meanLeadTimeLabel: '',
leadTimeBenchmark: undefined,
meanRecoveryTimeLabel: '',
@@ -339,8 +340,11 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
}
const handleEnvironmentChange = (selected): void => {
- const URL = `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/deployment-metrics/${selected.value}`
- history.push(URL)
+ const URL = generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.DEPLOYMENT_METRICS}/:envId`, {
+ appId,
+ envId: selected.value,
+ })
+ navigate(URL)
ReactGA.event({
category: 'Deployment Metrics',
action: 'Environment Selection Changed',
@@ -348,18 +352,14 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
})
}
- const handleDatesChange = ({ startDate: newStartDate, endDate: newEndDate }): void => {
+ const handleDatesChange: UpdateDateRangeType = ({ from: newStartDate, to: newEndDate }): void => {
setState((prev) => ({
...prev,
- startDate: newStartDate?.set({ hour: 0, minute: 0, seconds: 0 }),
- endDate: newEndDate?.set({ hour: 23, minute: 59, seconds: 59, milliseconds: 999 }),
+ startDate: moment(newStartDate)?.set({ hour: 0, minute: 0, seconds: 0 }),
+ endDate: moment(newEndDate)?.set({ hour: 23, minute: 59, seconds: 59, milliseconds: 999 }),
}))
}
- const handleFocusChange = (focusedInput): void => {
- setState((prev) => ({ ...prev, focusedInput }))
- }
-
const handleTableFilter = (event): void => {
ReactGA.event({
category: 'Deployment Metrics',
@@ -481,6 +481,8 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
handleEnvironmentChange(selected)
}
+ const isOutsideRange = (date: Date) => moment(date).isAfter(moment(), 'day')
+
const renderInputs = () => (
@@ -494,14 +496,20 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
options={state.environments}
/>
-
+
{selectedEnvironment ? (
-
) : null}
@@ -708,7 +716,6 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
{renderInputs()}
@@ -862,15 +869,22 @@ const DeploymentMetricsComponent = ({ filteredEnvIds }: DeploymentMetricsProps)
const DeploymentMetrics = (props: DeploymentMetricsProps) => {
const { appId, envId } = useParams<{ appId: string; envId: string }>()
const { environmentId, setEnvironmentId } = useAppContext()
- const { path } = useRouteMatch()
- const { replace } = useHistory()
+ const navigate = useNavigate()
useEffect(() => {
if (envId && +envId !== environmentId) {
setEnvironmentId(+envId)
}
if (!envId && environmentId) {
- replace(generatePath(path, { appId, envId: environmentId }))
+ navigate(
+ generatePath(`${ROUTER_URLS.DEVTRON_APP_DETAILS.DEPLOYMENT_METRICS}/:envId`, {
+ appId,
+ envId: String(environmentId),
+ }),
+ {
+ replace: true,
+ },
+ )
}
}, [envId])
diff --git a/src/components/app/details/metrics/DeploymentTable.tsx b/src/components/app/details/metrics/DeploymentTable.tsx
index 818e5135cd..ea630c9af4 100644
--- a/src/components/app/details/metrics/DeploymentTable.tsx
+++ b/src/components/app/details/metrics/DeploymentTable.tsx
@@ -17,12 +17,12 @@
import { Component } from 'react'
import { Progressing, DEFAULT_BASE_PAGE_SIZE, Pagination } from '@devtron-labs/devtron-fe-common-lib'
import ReactGA from 'react-ga4'
-import { ReactComponent as Sort } from '../../../../assets/icons/ic-sort.svg'
-import { ReactComponent as SortUp } from '../../../../assets/icons/ic-sort-up.svg'
-import { ReactComponent as SortDown } from '../../../../assets/icons/ic-sort-down.svg'
-import { ReactComponent as Success } from '../../../../assets/icons/appstatus/healthy.svg'
-import { ReactComponent as Help } from '../../../../assets/icons/ic-info-outline.svg'
-import { ReactComponent as Fail } from '../../../../assets/icons/ic-error-exclamation.svg'
+import Sort from '../../../../assets/icons/ic-sort.svg?react'
+import SortUp from '../../../../assets/icons/ic-sort-up.svg?react'
+import SortDown from '../../../../assets/icons/ic-sort-down.svg?react'
+import Success from '../../../../assets/icons/appstatus/healthy.svg?react'
+import Help from '../../../../assets/icons/ic-info-outline.svg?react'
+import Fail from '../../../../assets/icons/ic-error-exclamation.svg?react'
import { ViewType } from '../../../../config'
export interface DeploymentTableCellType {
diff --git a/src/components/app/details/metrics/DeploymentTableModal.tsx b/src/components/app/details/metrics/DeploymentTableModal.tsx
index 3beba7f92f..e21f973e03 100644
--- a/src/components/app/details/metrics/DeploymentTableModal.tsx
+++ b/src/components/app/details/metrics/DeploymentTableModal.tsx
@@ -17,7 +17,7 @@
import React, { Component } from 'react'
import { VisibleModal } from '@devtron-labs/devtron-fe-common-lib'
import { DeploymentTable } from './DeploymentTable'
-import { ReactComponent as Close } from '../../../../assets/icons/ic-close.svg'
+import Close from '../../../../assets/icons/ic-close.svg?react'
import { ViewType } from '../../../../config'
export interface DeploymentTableModalProps {
diff --git a/src/components/app/details/metrics/deploymentMetrics.test.ts b/src/components/app/details/metrics/deploymentMetrics.test.ts
deleted file mode 100644
index ad22cf6de1..0000000000
--- a/src/components/app/details/metrics/deploymentMetrics.test.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import {
- createTimestamp,
- getFailureRateBenchmark,
- getFrequencyBenchmark,
- getLeadTimeBenchmark,
- getRecoveryTimeBenchmark,
-} from './deploymentMetrics.service'
-
-export const defaultStats = {
- avgFrequency: 0,
- failureRate: 0,
- meanLeadTime: '',
- meanRecoveryTime: '',
- graphs: [],
-}
-
-test('Create Timestamp', () => {
- expect(createTimestamp(0 / 0)).toBe('0m')
- expect(createTimestamp(0)).toBe('0m')
- expect(createTimestamp(59)).toBe('59m')
- expect(createTimestamp(60)).toBe('1h')
- expect(createTimestamp(60)).toBe('1h')
- expect(createTimestamp(61)).toBe('1h 1m')
- expect(createTimestamp(1440)).toBe('1d')
- expect(createTimestamp(121.999)).toBe('2h 1m')
- expect(createTimestamp(1501)).toBe('1d 1h 1m')
- expect(createTimestamp(4500)).toBe('3d 3h')
-})
-
-test('Get Frequency Benchmark', () => {
- expect(getFrequencyBenchmark(0)).toStrictEqual({
- name: 'LOW',
- targetName: 'MEDIUM',
- targetValue: 0.06,
- color: 'var(--Y500)',
- })
- expect(getFrequencyBenchmark(0.06)).toStrictEqual({
- name: 'MEDIUM',
- targetName: 'HIGH',
- targetValue: 0.13,
- color: 'var(--G500)',
- })
- expect(getFrequencyBenchmark(0.13)).toStrictEqual({
- name: 'HIGH',
- targetName: 'ELITE',
- targetValue: 1,
- color: 'var(--V500)',
- })
- expect(getFrequencyBenchmark(0.14)).toStrictEqual({
- name: 'HIGH',
- targetName: 'ELITE',
- targetValue: 1,
- color: 'var(--V500)',
- })
- expect(getFrequencyBenchmark(1)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- color: '',
- })
-})
-
-test('Get Failure Benchmark', () => {
- expect(getFailureRateBenchmark(0)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- color: '',
- })
- expect(getFailureRateBenchmark(11)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- color: '',
- })
-
- expect(getFailureRateBenchmark(15)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- color: '',
- })
- expect(getFailureRateBenchmark(30)).toStrictEqual({
- name: 'HIGH',
- targetName: 'ELITE',
- targetValue: 15,
- color: 'var(--V500)',
- })
- expect(getFailureRateBenchmark(46)).toStrictEqual({
- name: 'MEDIUM',
- targetName: 'HIGH',
- targetValue: 30,
- color: 'var(--G500)',
- })
-})
-
-test('Get Lead Time Benchmark', () => {
- expect(getLeadTimeBenchmark(0)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- targetLabel: '',
- color: '',
- })
- expect(getLeadTimeBenchmark(2 * 60 * 24)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- targetLabel: '',
- color: '',
- })
- expect(getLeadTimeBenchmark(14 * 60 * 24)).toStrictEqual({
- name: 'MEDIUM',
- targetName: 'HIGH',
- targetValue: 7 * 60 * 24,
- targetLabel: '7d',
- color: 'var(--G500)',
- })
- expect(getLeadTimeBenchmark(14 * 60 * 24)).toStrictEqual({
- name: 'MEDIUM',
- targetName: 'HIGH',
- targetValue: 7 * 60 * 24,
- targetLabel: '7d',
- color: 'var(--G500)',
- })
- expect(getLeadTimeBenchmark(15 * 60 * 24)).toStrictEqual({
- name: 'LOW',
- targetName: 'MEDIUM',
- targetValue: 14 * 60 * 24,
- targetLabel: '14d',
- color: 'var(--Y500)',
- })
-})
-
-test('Get Recovery Time Benchmark', () => {
- expect(getRecoveryTimeBenchmark(0)).toStrictEqual({
- name: 'ELITE',
- targetName: '',
- targetValue: 0,
- targetLabel: '',
- color: '',
- })
- expect(getRecoveryTimeBenchmark(4 * 60)).toStrictEqual({
- name: 'HIGH',
- targetName: 'ELITE',
- targetValue: 1 * 60,
- targetLabel: '1h',
- color: 'var(--V500)',
- })
- expect(getRecoveryTimeBenchmark(8 * 60)).toStrictEqual({
- name: 'MEDIUM',
- targetName: 'HIGH',
- targetValue: 4 * 60,
- targetLabel: '4h',
- color: 'var(--G500)',
- })
- expect(getRecoveryTimeBenchmark(15 * 60)).toStrictEqual({
- name: 'LOW',
- targetName: 'MEDIUM',
- targetLabel: createTimestamp(8 * 60),
- targetValue: 8 * 60,
- color: 'var(--Y500)',
- })
-})
diff --git a/src/components/app/details/metrics/deploymentMetrics.types.ts b/src/components/app/details/metrics/deploymentMetrics.types.ts
index eaa08c69ad..0f4e6bda7b 100644
--- a/src/components/app/details/metrics/deploymentMetrics.types.ts
+++ b/src/components/app/details/metrics/deploymentMetrics.types.ts
@@ -104,7 +104,6 @@ export interface DeploymentMetricsState {
startDate: Moment
endDate: Moment
- focusedInput: any
filterBy: {
startDate: undefined | Moment
endDate: undefined | Moment
diff --git a/src/components/app/details/metrics/deploymentMetrics.util.tsx b/src/components/app/details/metrics/deploymentMetrics.util.tsx
index d6e09d44de..76e80126c3 100644
--- a/src/components/app/details/metrics/deploymentMetrics.util.tsx
+++ b/src/components/app/details/metrics/deploymentMetrics.util.tsx
@@ -15,8 +15,8 @@
*/
import Tippy from '@tippyjs/react'
-import { ReactComponent as Smiley } from '../../../../assets/icons/ic-smiley-party.svg'
-import { ReactComponent as ICHelpOutline } from '../../../../assets/icons/ic-help-outline.svg'
+import Smiley from '../../../../assets/icons/ic-smiley-party.svg?react'
+import ICHelpOutline from '../../../../assets/icons/ic-help-outline.svg?react'
import { createTimestamp } from './deploymentMetrics.service'
export function frequencyXAxisLabel(props) {
diff --git a/src/components/app/details/metrics/utils.ts b/src/components/app/details/metrics/utils.ts
new file mode 100644
index 0000000000..8c85864398
--- /dev/null
+++ b/src/components/app/details/metrics/utils.ts
@@ -0,0 +1,23 @@
+import dayjs from 'dayjs'
+
+import { DateTimePickerProps } from '@devtron-labs/devtron-fe-common-lib'
+
+const getRangeOptionBeforeMonths = (monthCount: number): DateTimePickerProps['rangeShortcutOptions'][number] => ({
+ label: `Last ${monthCount} month${monthCount > 1 ? 's' : ''}`,
+ value: {
+ from: dayjs().subtract(monthCount, 'month').toDate(),
+ to: dayjs().toDate(),
+ },
+})
+
+export const getRangeShortcutOptions = (): DateTimePickerProps['rangeShortcutOptions'] =>
+ [
+ {
+ label: 'Last 15 days',
+ value: {
+ from: dayjs().subtract(15, 'day').toDate(),
+ to: dayjs().toDate(),
+ },
+ },
+ ...[1, 2, 3, 4, 5, 6].map((month) => getRangeOptionBeforeMonths(month)),
+ ] satisfies DateTimePickerProps['rangeShortcutOptions']
diff --git a/src/components/app/details/triggerView/BranchRegexModal.tsx b/src/components/app/details/triggerView/BranchRegexModal.tsx
index cdb0be800a..b478fdafdf 100644
--- a/src/components/app/details/triggerView/BranchRegexModal.tsx
+++ b/src/components/app/details/triggerView/BranchRegexModal.tsx
@@ -32,7 +32,7 @@ import {
VisibleModal2,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Close } from '@Icons/ic-close.svg'
+import Close from '@Icons/ic-close.svg?react'
import { getGitProviderIcon } from '@Components/common'
import { REQUIRED_FIELD_MSG } from '../../../../config/constantMessaging'
diff --git a/src/components/app/details/triggerView/BuildImageModal/BuildImageModal.tsx b/src/components/app/details/triggerView/BuildImageModal/BuildImageModal.tsx
index 0836a65db8..16981f2037 100644
--- a/src/components/app/details/triggerView/BuildImageModal/BuildImageModal.tsx
+++ b/src/components/app/details/triggerView/BuildImageModal/BuildImageModal.tsx
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
-import { Prompt, useHistory, useParams } from 'react-router-dom'
+import { useNavigate, useParams } from 'react-router-dom'
import {
APIResponseHandler,
@@ -8,7 +8,6 @@ import {
CommonNodeAttr,
ComponentSizeType,
ConsequenceAction,
- DEFAULT_ROUTE_PROMPT_MESSAGE,
DocLink,
Drawer,
ErrorScreenManagerProps,
@@ -57,7 +56,7 @@ const BuildImageModal = ({
environmentLists,
reloadWorkflowStatus,
}: BuildImageModalProps) => {
- const { push } = useHistory()
+ const navigate = useNavigate()
const { ciNodeId } = useParams
>()
const materialListAbortControllerRef = useRef(new AbortController())
@@ -174,7 +173,7 @@ const BuildImageModal = ({
getCIPipelineURL(String(appId), String(workflowId), true, ciNodeId, false, ciNode.isJobCI, false)
const redirectToCIPipeline = () => {
- push(getCIPipelineURLWrapper())
+ navigate(getCIPipelineURLWrapper())
}
const handleWebhookModalBack = () => {
@@ -401,34 +400,31 @@ const BuildImageModal = ({
)
return (
- <>
-
-
-
-
-
-
{renderContent()}
-
-
- {!ciNode || ciNode.isTriggerBlocked || showWebhookModal || !!screenErrorData ? null : (
-
- {isJobView ? renderEnvironments() : renderCacheInfo()}
- {renderCTAButton()}
-
- )}
+
+
+
-
-
- >
+
+ {!ciNode || ciNode.isTriggerBlocked || showWebhookModal || !!screenErrorData ? null : (
+
+ {isJobView ? renderEnvironments() : renderCacheInfo()}
+ {renderCTAButton()}
+
+ )}
+
+
)
}
diff --git a/src/components/app/details/triggerView/BuildImageModal/BulkBuildImageModal.tsx b/src/components/app/details/triggerView/BuildImageModal/BulkBuildImageModal.tsx
index f5ff95e856..f30b15e5e3 100644
--- a/src/components/app/details/triggerView/BuildImageModal/BulkBuildImageModal.tsx
+++ b/src/components/app/details/triggerView/BuildImageModal/BulkBuildImageModal.tsx
@@ -1,12 +1,10 @@
import { Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'
-import { Prompt } from 'react-router-dom'
import {
API_STATUS_CODES,
ApiQueuingWithBatch,
Button,
ComponentSizeType,
- DEFAULT_ROUTE_PROMPT_MESSAGE,
DocLink,
Drawer,
GenericEmptyState,
@@ -477,8 +475,6 @@ const BulkBuildImageModal = ({
{!showWebhookModal && !isLoadingAppInfoMap && renderFooter()}
-
-
)
}
diff --git a/src/components/app/details/triggerView/BuildImageModal/GitInfoMaterial.tsx b/src/components/app/details/triggerView/BuildImageModal/GitInfoMaterial.tsx
index d011fdd194..fca04692ac 100644
--- a/src/components/app/details/triggerView/BuildImageModal/GitInfoMaterial.tsx
+++ b/src/components/app/details/triggerView/BuildImageModal/GitInfoMaterial.tsx
@@ -18,6 +18,7 @@ import {
Icon,
MaterialHistory,
OptionType,
+ ROUTER_URLS,
SearchBar,
showError,
SourceTypeMap,
@@ -621,7 +622,7 @@ const GitInfoMaterial = ({
imgSrc={linkedCiImg}
title={`${selectedApp.name} ${BULK_CI_MESSAGING.emptyLinkedCI.title}`}
subTitle={BULK_CI_MESSAGING.emptyLinkedCI.subTitle}
- link={`${URLS.APPLICATION_MANAGEMENT_APP}/${selectedApp.node.parentAppId}/${URLS.APP_CI_DETAILS}/${selectedApp.node.parentCiPipeline}`}
+ link={`${ROUTER_URLS.DEVTRON_APP}/${selectedApp.node.parentAppId}/${URLS.APP_CI_DETAILS}/${selectedApp.node.parentCiPipeline}`}
linkText={BULK_CI_MESSAGING.emptyLinkedCI.linkText}
rootClassName="bg__tertiary"
/>
diff --git a/src/components/app/details/triggerView/BuildImageModal/TriggerBuildSidebar.tsx b/src/components/app/details/triggerView/BuildImageModal/TriggerBuildSidebar.tsx
index ae899e6ff5..97caa88ff7 100644
--- a/src/components/app/details/triggerView/BuildImageModal/TriggerBuildSidebar.tsx
+++ b/src/components/app/details/triggerView/BuildImageModal/TriggerBuildSidebar.tsx
@@ -1,3 +1,5 @@
+import type { JSX } from 'react'
+
import {
Checkbox,
CHECKBOX_VALUE,
diff --git a/src/components/app/details/triggerView/CiWebhookDebuggingModal.tsx b/src/components/app/details/triggerView/CiWebhookDebuggingModal.tsx
index 3e7f405319..f7297e6fb0 100644
--- a/src/components/app/details/triggerView/CiWebhookDebuggingModal.tsx
+++ b/src/components/app/details/triggerView/CiWebhookDebuggingModal.tsx
@@ -36,7 +36,7 @@ import {
useAsync,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Edit } from '@Icons/ic-pencil.svg'
+import Edit from '@Icons/ic-pencil.svg?react'
import { Moment12HourFormat } from '../../../../config'
import { getCIPipelineURL } from '../../../common'
diff --git a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx
index e08388074a..0a732f6a05 100644
--- a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx
+++ b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx
@@ -1,5 +1,4 @@
import { Dispatch, SetStateAction, SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react'
-import { Prompt } from 'react-router-dom'
import {
AnimatedDeployButton,
@@ -8,7 +7,6 @@ import {
ButtonStyleType,
CDMaterialResponseType,
CDMaterialServiceEnum,
- DEFAULT_ROUTE_PROMPT_MESSAGE,
DeploymentNodeType,
DeploymentStrategyTypeWithDefault,
Drawer,
@@ -730,42 +728,36 @@ const BulkDeployModal = ({
}
return (
- <>
-
-
-
-
-
-
{renderContent()}
-
+
+
+
+
- {isLoadingAppInfoMap || showStrategyFeasibilityPage ? null : renderFooter()}
+
{renderContent()}
- {showResistanceBox && (
-
- )}
-
+ {isLoadingAppInfoMap || showStrategyFeasibilityPage ? null : renderFooter()}
+
-
- >
+ {showResistanceBox && (
+
+ )}
+
)
}
diff --git a/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx b/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx
index 5d71187ca2..c280256b51 100644
--- a/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx
+++ b/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx
@@ -1,4 +1,4 @@
-import { useHistory } from 'react-router-dom'
+import { useNavigate } from 'react-router-dom'
import {
Button,
@@ -97,7 +97,7 @@ const DeployImageContent = ({
onImageSelection = noop,
}: DeployImageContentProps) => {
// WARNING: Pls try not to create a useState in this component, it is supposed to be a dumb component.
- const history = useHistory()
+ const navigate = useNavigate()
const { isSuperAdmin } = useMainContext()
// Assumption: isExceptionUser is a global trait
@@ -243,7 +243,7 @@ const DeployImageContent = ({
const viewAllImages = () => {
if (isRedirectedFromAppDetails) {
- history.push({
+ navigate({
search: `${TRIGGER_VIEW_PARAMS.APPROVAL_NODE}=${pipelineId}&${TRIGGER_VIEW_PARAMS.APPROVAL_STATE}=${TRIGGER_VIEW_PARAMS.APPROVAL}`,
})
} else {
@@ -253,7 +253,7 @@ const DeployImageContent = ({
cdNodeId: pipelineId,
fromAppGroup: isBulkTrigger,
})
- history.push({ search })
+ navigate({ search })
}
}
diff --git a/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx b/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx
index ad3844f702..4161cb30be 100644
--- a/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx
+++ b/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx
@@ -1,5 +1,5 @@
import { Dispatch, SetStateAction, SyntheticEvent, useMemo, useState } from 'react'
-import { Prompt, useHistory, useLocation } from 'react-router-dom'
+import { useLocation, useNavigate } from 'react-router-dom'
import {
ACTION_STATE,
@@ -7,7 +7,6 @@ import {
API_STATUS_CODES,
ArtifactInfo,
ConditionalWrap,
- DEFAULT_ROUTE_PROMPT_MESSAGE,
DEPLOYMENT_CONFIG_DIFF_SORT_KEY,
DeploymentAppTypes,
DeploymentNodeType,
@@ -25,6 +24,7 @@ import {
ModuleStatus,
noop,
PipelineDeploymentStrategy,
+ ROUTER_URLS,
ServerErrors,
showError,
SortingOrder,
@@ -111,7 +111,7 @@ const DeployImageModal = ({
isTriggerBlockedDueToPlugin,
parentEnvironmentName,
}: DeployImageModalProps) => {
- const history = useHistory()
+ const navigate = useNavigate()
const { pathname } = useLocation()
const { searchParams } = useSearchString()
const { handleDownload } = useDownload()
@@ -255,7 +255,7 @@ const DeployImageModal = ({
newParams.delete('sortBy')
}
- history.push({
+ navigate({
pathname:
modeParamValue === URL_PARAM_MODE_TYPE.REVIEW_CONFIG
? // Replace consecutive trailing single slashes
@@ -352,7 +352,7 @@ const DeployImageModal = ({
)
const redirectToDeploymentStepsPage = () => {
- history.push(`${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${URLS.APP_CD_DETAILS}/${envId}/${pipelineId}`)
+ navigate(`${ROUTER_URLS.DEVTRON_APP}/${appId}/${URLS.APP_CD_DETAILS}/${envId}/${pipelineId}`)
}
const handleDeployment = ({ ciArtifactId, deploymentWithConfig, computedWfrId }: HandleDeploymentProps) => {
@@ -499,7 +499,7 @@ const DeployImageModal = ({
searchText: newSearchText,
}))
- history.push({
+ navigate({
pathname,
search: newParams.toString(),
})
@@ -766,8 +766,6 @@ const DeployImageModal = ({
envName={envName}
/>
)}
-
-
>
)
}
diff --git a/src/components/app/details/triggerView/DeployImageModal/MaterialListEmptyState.tsx b/src/components/app/details/triggerView/DeployImageModal/MaterialListEmptyState.tsx
index 80bd895303..784c572687 100644
--- a/src/components/app/details/triggerView/DeployImageModal/MaterialListEmptyState.tsx
+++ b/src/components/app/details/triggerView/DeployImageModal/MaterialListEmptyState.tsx
@@ -1,3 +1,5 @@
+import type { JSX } from 'react'
+
import {
Button,
ButtonVariantType,
diff --git a/src/components/app/details/triggerView/DeployImageModal/utils.tsx b/src/components/app/details/triggerView/DeployImageModal/utils.tsx
index 1ff64d5836..676f5c34ab 100644
--- a/src/components/app/details/triggerView/DeployImageModal/utils.tsx
+++ b/src/components/app/details/triggerView/DeployImageModal/utils.tsx
@@ -1,3 +1,5 @@
+import type { JSX } from 'react'
+
import {
ACTION_STATE,
API_STATUS_CODES,
diff --git a/src/components/app/details/triggerView/EmptyStateCIMaterial.tsx b/src/components/app/details/triggerView/EmptyStateCIMaterial.tsx
index a2f17e19ac..9777123894 100644
--- a/src/components/app/details/triggerView/EmptyStateCIMaterial.tsx
+++ b/src/components/app/details/triggerView/EmptyStateCIMaterial.tsx
@@ -14,12 +14,13 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
import { Button, ButtonVariantType, GenericEmptyState } from '@devtron-labs/devtron-fe-common-lib'
import ErrorImage from '../../../../assets/img/ic-empty-error@2x.png'
import EmptyStateImage from '../../../../assets/img/app-not-deployed.svg'
import NoEligibleCommit from '../../../../assets/gif/ic-no-eligible-commit.svg'
import NoResults from '../../../../assets/img/empty-noresult@2x.png'
-import { ReactComponent as NextIcon } from '../../../../assets/icons/ic-arrow-right.svg'
+import NextIcon from '../../../../assets/icons/ic-arrow-right.svg?react'
import { EmptyStateCIMaterialProps } from './types'
import { CI_MATERIAL_EMPTY_STATE_MESSAGING } from './Constants'
import { DOCKER_FILE_ERROR_MESSAGE, SOURCE_NOT_CONFIGURED_MESSAGE } from '../../../../config'
diff --git a/src/components/app/details/triggerView/MaterialSource.tsx b/src/components/app/details/triggerView/MaterialSource.tsx
index 5de53f05e3..5e7504a1f1 100644
--- a/src/components/app/details/triggerView/MaterialSource.tsx
+++ b/src/components/app/details/triggerView/MaterialSource.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { ReactComponent as Refresh } from '@Icons/ic-restore.svg'
-import { ReactComponent as Error } from '@Icons/ic-warning.svg'
+import Refresh from '@Icons/ic-restore.svg?react'
+import Error from '@Icons/ic-warning.svg?react'
import { MaterialSourceProps } from './types'
import { CIMaterialType, CiPipelineSourceConfig, SourceTypeMap } from '@devtron-labs/devtron-fe-common-lib'
import { getGitProviderIcon } from '@Components/common'
diff --git a/src/components/app/details/triggerView/PipelineConfigDiff/PipelineConfigDiffStatusTile.tsx b/src/components/app/details/triggerView/PipelineConfigDiff/PipelineConfigDiffStatusTile.tsx
index bd0cf26e47..bc04da8c6e 100644
--- a/src/components/app/details/triggerView/PipelineConfigDiff/PipelineConfigDiffStatusTile.tsx
+++ b/src/components/app/details/triggerView/PipelineConfigDiff/PipelineConfigDiffStatusTile.tsx
@@ -16,7 +16,7 @@
import { DeploymentConfigDiffRadioSelect, Icon, Tooltip } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg'
+import ICWarning from '@Icons/ic-warning.svg?react'
import { PipelineConfigDiffStatusTileProps } from './types'
diff --git a/src/components/app/details/triggerView/PipelineConfigDiff/types.ts b/src/components/app/details/triggerView/PipelineConfigDiff/types.ts
index 17817fe3fc..d70dfebd4a 100644
--- a/src/components/app/details/triggerView/PipelineConfigDiff/types.ts
+++ b/src/components/app/details/triggerView/PipelineConfigDiff/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Dispatch, SetStateAction } from 'react'
+import { Dispatch, type JSX, SetStateAction } from 'react'
import {
DeploymentConfigDiffProps,
diff --git a/src/components/app/details/triggerView/PipelineConfigDiff/usePipelineDeploymentConfig.ts b/src/components/app/details/triggerView/PipelineConfigDiff/usePipelineDeploymentConfig.ts
index e42ca6ae63..aafc8658a4 100644
--- a/src/components/app/details/triggerView/PipelineConfigDiff/usePipelineDeploymentConfig.ts
+++ b/src/components/app/details/triggerView/PipelineConfigDiff/usePipelineDeploymentConfig.ts
@@ -15,7 +15,7 @@
*/
import { useEffect, useMemo, useState } from 'react'
-import { useHistory, useLocation } from 'react-router-dom'
+import { useLocation, useNavigate } from 'react-router-dom'
import {
AppEnvDeploymentConfigType,
@@ -73,7 +73,7 @@ export const usePipelineDeploymentConfig = ({
}: UsePipelineDeploymentConfigProps) => {
// HOOKS
const { pathname, search } = useLocation()
- const history = useHistory()
+ const navigate = useNavigate()
const { isSuperAdmin } = useMainContext()
// STATES
@@ -374,6 +374,7 @@ export const usePipelineDeploymentConfig = ({
Awaited
>,
[string, string, typeof pipelineDeploymentConfigRes, number | undefined],
false
+ // eslint-disable-next-line @tanstack/query/exhaustive-deps
>({
queryKey: ['getManifestData', selectedTab, pipelineDeploymentConfigRes, wfrId],
queryFn: ({ signal }) => getManifestData(signal),
@@ -435,13 +436,14 @@ export const usePipelineDeploymentConfig = ({
`${pathname.split(`/${URLS.APP_DIFF_VIEW}/`)[0]}/${URLS.APP_DIFF_VIEW}/${resourceType}${resourceName ? `/${resourceName}` : ''}${search}`
const onTabChange = async (tab: typeof selectedTab) => {
- history.replace(
+ navigate(
getNavItemHref(
tab === deploymentConfigDiffTabs.MANIFEST
? EnvResourceType.Manifest
: EnvResourceType.DeploymentTemplate,
'',
),
+ { replace: true },
)
}
diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx
index 45f820082d..f499663f7c 100644
--- a/src/components/app/details/triggerView/TriggerView.tsx
+++ b/src/components/app/details/triggerView/TriggerView.tsx
@@ -15,13 +15,12 @@
*/
import React, { useState } from 'react'
-import { Route, Switch, useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom'
+import { generatePath, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
-import { DocLink, ErrorScreenManager, Progressing } from '@devtron-labs/devtron-fe-common-lib'
+import { DocLink, ErrorScreenManager, Progressing, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
import { getExternalCIConfig } from '@Components/ciPipeline/Webhook/webhook.service'
-import { URLS } from '../../../../config'
import { APP_DETAILS } from '../../../../config/constantMessaging'
import { LinkedCIDetail } from '../../../../Pages/Shared/LinkedCIDetailsModal'
import { importComponentFromFELibrary, InValidHostUrlWarningBlock, useAppContext } from '../../../common'
@@ -54,9 +53,8 @@ const JobNotConfiguredSubtitle = () => (
const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
const { appId, envId } = useParams()
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
- const match = useRouteMatch()
const { currentAppName } = useAppContext()
@@ -74,12 +72,12 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
} = useTriggerViewServices({ appId, isJobView, filteredEnvIds })
const openCIMaterialModal = (ciNodeId: string) => {
- history.push(`${match.url}${URLS.BUILD}/${ciNodeId}`)
+ navigate(`build/${ciNodeId}`)
}
const closeApprovalModal = (e: React.MouseEvent): void => {
e.stopPropagation()
- history.push({
+ navigate({
search: '',
})
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -97,7 +95,11 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
}
const revertToPreviousURL = () => {
- history.push(match.url)
+ const redirectPath = generatePath(
+ isJobView ? ROUTER_URLS.JOB_DETAIL.TRIGGER : ROUTER_URLS.DEVTRON_APP_DETAILS.TRIGGER,
+ { appId },
+ )
+ navigate(redirectPath)
}
const renderApprovalMaterial = () => {
@@ -114,7 +116,6 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
pipelineId={cdNodeId}
getModuleInfo={getModuleInfo}
ciPipelineId={node?.connectingCiPipelineId}
- history={history}
/>
)
}
@@ -145,9 +146,9 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
width={workflow.width}
nodes={workflow.nodes}
artifactPromotionMetadata={workflow.artifactPromotionMetadata}
- history={history}
+ params={{ appId, envId }}
location={location}
- match={{ params: { appId, envId }, url: match.url, path: match.path, isExact: match.isExact }}
+ navigate={navigate}
isJobView={isJobView}
index={index}
filteredCIPipelines={filteredCIPipelines}
@@ -158,7 +159,7 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
reloadTriggerView={reloadWorkflows}
/>
))}
-
+
{renderWebhookAddImageModal()}
>
)
@@ -206,20 +207,23 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => {
{renderHostErrorMessage()}
{renderWorkflow()}
-
-
-
-
-
+
+
+ }
+ />
+
{
{WorkflowActionRouter && (
{
- it('Render Trigger View without crashing', () => {
- const { container } = render(, {
- wrapper: BrowserRouter,
- })
- expect(container).toBeInTheDocument()
- })
- // it('Finish config message appearing without breaking when no workflow is configured',async () => {
- // expect(screen.findByText('Go To App Configurations')).toBeInTheDocument
- // })
- // it('Select Image button for a cd must appear without breaking',async () => {
- // expect(screen.findByText('Select Image')).toBeInTheDocument
- // })
-})
diff --git a/src/components/app/details/triggerView/__tests__/workflow.service.test.ts b/src/components/app/details/triggerView/__tests__/workflow.service.test.ts
deleted file mode 100644
index e2c8e6a5c2..0000000000
--- a/src/components/app/details/triggerView/__tests__/workflow.service.test.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import { processWorkflow } from '../workflow.service'
-import { WorkflowTrigger, WorkflowCreate } from '../config'
-import {
- ciConfigResp,
- cdConfigResp,
- cdConfigPreResp,
- cdConfigPostResp,
- cdConfigPrePostResp,
- workflow,
- workflowsCreate,
- workflowsTrigger,
- workflowsTriggerPreCDResp,
- workflowsCreatePreCDResp,
- workflowsTriggerPostCD,
- workflowsCreatePostCD,
- workflowsTriggerPrePostCD,
- workflowsCreatePrePostCD,
- cdConfigPrePostRespWithPrePostSequential,
-} from '../__mocks__/workflow.mock'
-import {
- workflowsCreatePostCDWithSequential,
- workflowsCreatePreCDRespWithSequential,
- workflowsCreatePrePostCDWithSequential,
- workflowsCreateWithSequential,
- workflowsTriggerPostCDWithSequential,
- workflowsTriggerPreCDRespWithSequential,
- workflowsTriggerPrePostCDWithSequential,
- workflowsTriggerWithSequential,
- workflowWithSequential,
- workflowsTriggerPrePostCDWithPrePostSequential,
- workflowsCreatePrePostCDWithPrePostSequential,
-} from '../__mocks__/workflow.sequential.mock'
-import { CdPipelineResult, CiPipelineResult, WorkflowResult } from '../types'
-
-describe('workflow service tests', () => {
- test('process workflows no PRECD, no POSTCD', () => {
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTrigger)
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreate)
- })
-
- test('process workflows PRECD, no POSTCD', () => {
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPreResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPreCDResp)
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPreResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePreCDResp)
- })
-
- test('process workflows no PRECD, POSTCD', () => {
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPostResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPostCD)
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPostResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePostCD)
- })
-
- test('process workflows PRECD, POSTCD', () => {
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPrePostResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPrePostCD)
- expect(
- processWorkflow(
- workflow.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPrePostResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePrePostCD)
- })
-
- test('process workflows sequential no PRECD, no POSTCD', () => {
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerWithSequential)
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreateWithSequential)
- })
-
- test('process workflows sequeqntial PRECD, no POSTCD', () => {
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPreResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPreCDRespWithSequential)
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPreResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePreCDRespWithSequential)
- })
-
- test('process workflows sequential no PRECD, POSTCD', () => {
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPostResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPostCDWithSequential)
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPostResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePostCDWithSequential)
- })
-
- test('process workflows sequential PRECD, POSTCD', () => {
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPrePostResp.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPrePostCDWithSequential)
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPrePostResp.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePrePostCDWithSequential)
- })
-
- test('process workflows pre and post sequential PRECD, POSTCD', () => {
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPrePostRespWithPrePostSequential.result as CdPipelineResult,
- [],
- WorkflowTrigger,
- WorkflowTrigger.workflow,
- ).workflows,
- ).toStrictEqual(workflowsTriggerPrePostCDWithPrePostSequential)
- expect(
- processWorkflow(
- workflowWithSequential.result as WorkflowResult,
- ciConfigResp.result as CiPipelineResult,
- cdConfigPrePostRespWithPrePostSequential.result as CdPipelineResult,
- [],
- WorkflowCreate,
- WorkflowCreate.workflow,
- ).workflows,
- ).toStrictEqual(workflowsCreatePrePostCDWithPrePostSequential)
- })
-})
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts
index c3e8752e40..6fc19942ed 100644
--- a/src/components/app/details/triggerView/types.ts
+++ b/src/components/app/details/triggerView/types.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { RouteComponentProps } from 'react-router-dom'
-
import {
AppConfigProps,
ArtifactPromotionMetadata,
@@ -33,6 +31,7 @@ import {
Material,
PipelineType,
PolicyKindType,
+ RouterV5Props,
RuntimePluginVariables,
TaskErrorObj,
WorkflowType,
@@ -93,7 +92,7 @@ export interface MaterialInfo {
type?: string
}
-export interface CIMaterialRouterProps {
+export type CIMaterialRouterProps = {
appId: string
envId: string
ciNodeId: string
@@ -123,7 +122,7 @@ interface InputMaterials {
}
export interface TriggerCDNodeProps
- extends RouteComponentProps<{ appId: string; envId: string }>,
+ extends RouterV5Props<{ appId: string; envId: string }>,
Partial>,
Pick {
x: number
@@ -165,7 +164,7 @@ export interface TriggerCDNodeState {
}
export interface TriggerPrePostCDNodeProps
- extends RouteComponentProps<{ appId: string; envId: string }>,
+ extends RouterV5Props<{ appId: string; envId: string }>,
Partial>,
Pick {
x: number
@@ -201,7 +200,7 @@ export interface TriggerEdgeType {
}
export interface WorkflowProps
- extends RouteComponentProps<{ appId: string; envId: string }>,
+ extends RouterV5Props<{ appId: string; envId: string }>,
Pick {
id: string
name: string
diff --git a/src/components/app/details/triggerView/workflow/Workflow.tsx b/src/components/app/details/triggerView/workflow/Workflow.tsx
index 6a7a52dd7e..16a7fe2c1d 100644
--- a/src/components/app/details/triggerView/workflow/Workflow.tsx
+++ b/src/components/app/details/triggerView/workflow/Workflow.tsx
@@ -44,7 +44,7 @@ export class Workflow extends Component {
goToWorkFlowEditor = (node: CommonNodeAttr) => {
if (node.branch === GIT_BRANCH_NOT_CONFIGURED) {
const ciPipelineURL = getCIPipelineURL(
- this.props.appId?.toString() ?? this.props.match.params.appId,
+ this.props.appId?.toString() ?? this.props.params.appId,
this.props.id,
true,
node.downstreams[0].split('-')[1],
@@ -59,7 +59,7 @@ export class Workflow extends Component {
'noreferrer',
)
} else {
- this.props.history.push(ciPipelineURL)
+ this.props.navigate(ciPipelineURL)
}
}
}
@@ -71,7 +71,7 @@ export class Workflow extends Component {
nodeType,
fromAppGroup: this.props.fromAppGrouping,
})
- this.props.history.push({ search })
+ this.props.navigate({ search })
}
onClickRollbackMaterial = (cdNodeId: number) => {
@@ -80,7 +80,7 @@ export class Workflow extends Component {
cdNodeId,
fromAppGroup: this.props.fromAppGrouping,
})
- this.props.history.push({ search })
+ this.props.navigate({ search })
}
onClickApprovalNode = (cdNodeId: number) => {
@@ -89,7 +89,7 @@ export class Workflow extends Component {
cdNodeId,
fromAppGroup: this.props.fromAppGrouping,
})
- this.props.history.push({ search })
+ this.props.navigate({ search })
}
renderNodes() {
@@ -191,9 +191,9 @@ export class Workflow extends Component {
isLinkedCI={node.isLinkedCI}
linkedCount={node.linkedCount}
inputMaterialsNew={[]}
- history={this.props.history}
+ params={this.props.params}
location={this.props.location}
- match={this.props.match}
+ navigate={this.props.navigate}
fromAppGrouping={this.props.fromAppGrouping}
isCITriggerBlocked={node.isTriggerBlocked}
/>
@@ -244,9 +244,9 @@ export class Workflow extends Component {
description={node.description}
linkedCount={node.linkedCount}
inputMaterialsNew={[]}
- history={this.props.history}
+ params={this.props.params}
location={this.props.location}
- match={this.props.match}
+ navigate={this.props.navigate}
branch={node.branch}
fromAppGrouping={this.props.fromAppGrouping}
isJobView={this.props.isJobView}
@@ -280,9 +280,9 @@ export class Workflow extends Component {
colourCode={node.colourCode}
rollbackMaterialList={node.rollbackMaterialList}
deploymentStrategy={node.deploymentStrategy}
- history={this.props.history}
+ params={this.props.params}
location={this.props.location}
- match={this.props.match}
+ navigate={this.props.navigate}
parentPipelineId={node.parentPipelineId}
parentPipelineType={node.parentPipelineType}
parentEnvironmentName={node.parentEnvironmentName}
@@ -320,9 +320,9 @@ export class Workflow extends Component {
title={node.title}
colourCode={node.colourCode}
rollbackMaterialList={node.rollbackMaterialList}
- history={this.props.history}
+ params={this.props.params}
location={this.props.location}
- match={this.props.match}
+ navigate={this.props.navigate}
fromAppGrouping={this.props.fromAppGrouping}
index={this.props.index}
isGitOpsRepoNotConfigured={node.isGitOpsRepoNotConfigured}
diff --git a/src/components/app/details/triggerView/workflow/nodes/TriggerExternalCINode.tsx b/src/components/app/details/triggerView/workflow/nodes/TriggerExternalCINode.tsx
index 071370e758..3484e62ae5 100644
--- a/src/components/app/details/triggerView/workflow/nodes/TriggerExternalCINode.tsx
+++ b/src/components/app/details/triggerView/workflow/nodes/TriggerExternalCINode.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { Component } from 'react'
+import { Component } from 'react'
import Tippy from '@tippyjs/react'
import link from '../../../../../../assets/icons/ic-link.svg'
import { Icon } from '@devtron-labs/devtron-fe-common-lib'
diff --git a/src/components/app/details/triggerView/workflow/nodes/TriggerLinkedCINode.tsx b/src/components/app/details/triggerView/workflow/nodes/TriggerLinkedCINode.tsx
index 5255f968b9..e6586c7caf 100644
--- a/src/components/app/details/triggerView/workflow/nodes/TriggerLinkedCINode.tsx
+++ b/src/components/app/details/triggerView/workflow/nodes/TriggerLinkedCINode.tsx
@@ -15,15 +15,15 @@
*/
import { Component } from 'react'
-import { RouteComponentProps, Link } from 'react-router-dom'
+import { generatePath, Link } from 'react-router-dom'
import Tippy from '@tippyjs/react'
-import { getWorkflowNodeStatusTitle } from '@devtron-labs/devtron-fe-common-lib'
+import { getWorkflowNodeStatusTitle, RouterV5Props, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
import link from '@Icons/ic-link.svg'
-import { ReactComponent as ICLinkedCINode } from '@Icons/ic-node-build-linked.svg'
+import ICLinkedCINode from '@Icons/ic-node-build-linked.svg?react'
import { TriggerStatus } from '../../../../config'
-import { DEFAULT_STATUS, URLS } from '../../../../../../config'
+import { DEFAULT_STATUS } from '../../../../../../config'
-export interface CINodeProps extends RouteComponentProps<{}> {
+export interface CINodeProps extends RouterV5Props<{ appId: string }> {
x: number
y: number
width: number
@@ -47,14 +47,16 @@ export interface CINodeProps extends RouteComponentProps<{}> {
export class TriggerLinkedCINode extends Component {
getCIDetailsURL(): string {
- return `${this.props.match.url.replace(URLS.APP_TRIGGER, URLS.APP_CI_DETAILS)}/${this.props.id}`
+ return `${generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.CI_DETAILS, {
+ appId: this.props.params.appId,
+ })}/${this.props.id}`
}
redirectToCIDetails() {
if (this.props.fromAppGrouping) {
return
}
- this.props.history.push(this.getCIDetailsURL())
+ this.props.navigate(this.getCIDetailsURL())
}
renderStatus() {
diff --git a/src/components/app/details/triggerView/workflow/nodes/triggerCDNode.tsx b/src/components/app/details/triggerView/workflow/nodes/triggerCDNode.tsx
index ec5cef7e4d..b4215ebe48 100644
--- a/src/components/app/details/triggerView/workflow/nodes/triggerCDNode.tsx
+++ b/src/components/app/details/triggerView/workflow/nodes/triggerCDNode.tsx
@@ -16,19 +16,19 @@
import { Component } from 'react'
import Tippy from '@tippyjs/react'
-import { Link } from 'react-router-dom'
+import { generatePath, Link } from 'react-router-dom'
import {
DEPLOYMENT_STATUS,
DeploymentAppTypes,
DeploymentNodeType,
getDeploymentStatusFromStatus,
Icon,
+ ROUTER_URLS,
statusColor,
statusIcon,
- URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import { TriggerCDNodeProps, TriggerCDNodeState } from '../../types'
-import { ReactComponent as Rollback } from '../../../../../../assets/icons/ic-rollback.svg'
+import Rollback from '../../../../../../assets/icons/ic-rollback.svg?react'
import { DEFAULT_STATUS } from '../../../../../../config'
import { envDescriptionTippy, getNodeSideHeadingAndClass } from './workflow.utils'
import NoGitOpsRepoConfiguredWarning, {
@@ -55,19 +55,21 @@ export class TriggerCDNode extends Component): void {
diff --git a/src/components/app/details/triggerView/workflow/nodes/triggerCINode.tsx b/src/components/app/details/triggerView/workflow/nodes/triggerCINode.tsx
index 0984231848..ad5f42dfc7 100644
--- a/src/components/app/details/triggerView/workflow/nodes/triggerCINode.tsx
+++ b/src/components/app/details/triggerView/workflow/nodes/triggerCINode.tsx
@@ -15,17 +15,24 @@
*/
import { Component } from 'react'
-import { RouteComponentProps, Link } from 'react-router-dom'
+import { Link } from 'react-router-dom'
import Tippy from '@tippyjs/react'
-import { CIMaterialType, ConsequenceType, Icon, getWorkflowNodeStatusTitle } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ CIMaterialType,
+ ConsequenceType,
+ Icon,
+ RouterV5Props,
+ getWorkflowNodeStatusTitle,
+} from '@devtron-labs/devtron-fe-common-lib'
import { TriggerStatus } from '../../../../config'
import { BUILD_STATUS, DEFAULT_STATUS, URLS } from '../../../../../../config'
-import { ReactComponent as IcLink } from '../../../../../../assets/icons/ic-link.svg'
+import IcLink from '../../../../../../assets/icons/ic-link.svg?react'
import { DEFAULT_ENV } from '../../Constants'
import { getLinkedCITippyContent } from '../../../../../../Pages/Shared/LinkedCIDetailsModal/utils'
import { WorkflowProps } from '../../types'
-export interface TriggerCINodeProps extends RouteComponentProps<{ appId: string }>, Pick {
+export interface TriggerCINodeProps
+ extends RouterV5Props<{ appId: string }>, Pick {
x: number
y: number
height: number
@@ -61,14 +68,14 @@ export class TriggerCINode extends Component {
}
getCIDetailsURL(): string {
- return `${this.props.match.url.replace(URLS.APP_TRIGGER, URLS.APP_CI_DETAILS)}/${this.props.id}`
+ return `${this.props.location.pathname.replace('trigger', URLS.APP_CI_DETAILS)}/${this.props.id}`
}
redirectToCIDetails() {
if (this.props.fromAppGrouping) {
return
}
- this.props.history.push(this.getCIDetailsURL())
+ this.props.navigate(this.getCIDetailsURL())
}
hideDetails(status: string = '') {
@@ -83,7 +90,7 @@ export class TriggerCINode extends Component {
handleLinkedCIWorkflowChipClick = (e) => {
// stopPropagation to stop redirection to ci-details
e.stopPropagation()
- this.props.history.push(`${this.props.match.url}/${URLS.LINKED_CI_DETAILS}/${this.props.id}`)
+ this.props.navigate(`${URLS.LINKED_CI_DETAILS}/${this.props.id}`)
}
renderStatus() {
diff --git a/src/components/app/details/triggerView/workflow/nodes/triggerPrePostCDNode.tsx b/src/components/app/details/triggerView/workflow/nodes/triggerPrePostCDNode.tsx
index 524e6df928..f8e850f182 100644
--- a/src/components/app/details/triggerView/workflow/nodes/triggerPrePostCDNode.tsx
+++ b/src/components/app/details/triggerView/workflow/nodes/triggerPrePostCDNode.tsx
@@ -15,12 +15,13 @@
*/
import { Component } from 'react'
-import { Link } from 'react-router-dom'
+import { generatePath, Link } from 'react-router-dom'
import {
DeploymentAppTypes,
stopPropagation,
getWorkflowNodeStatusTitle,
Icon,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import { TriggerPrePostCDNodeProps, TriggerPrePostCDNodeState } from '../../types'
import { TriggerStatus } from '../../../../config'
@@ -47,18 +48,18 @@ export class TriggerPrePostCDNode extends Component {
- this.props.history.push(this.getCDDetailsURL())
+ this.props.navigate(this.getCDDetailsURL())
}
renderStatus(isClickable: boolean, status: string) {
@@ -151,7 +152,7 @@ export class TriggerPrePostCDNode extends Component
diff --git a/src/components/app/details/utils.tsx b/src/components/app/details/utils.tsx
index 419a9ae967..29bb916a85 100644
--- a/src/components/app/details/utils.tsx
+++ b/src/components/app/details/utils.tsx
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
+
import {
DynamicDataTableRowDataType,
DynamicDataTableRowType,
diff --git a/src/components/app/list-new/AppList.tsx b/src/components/app/list-new/AppList.tsx
index 1223f0ae48..1dbf62ac92 100644
--- a/src/components/app/list-new/AppList.tsx
+++ b/src/components/app/list-new/AppList.tsx
@@ -15,19 +15,12 @@
*/
import { useEffect, useMemo, useRef, useState } from 'react'
-import {
- Redirect,
- RedirectProps,
- Route,
- Switch,
- useHistory,
- useLocation,
- useParams,
- useRouteMatch,
-} from 'react-router-dom'
+import { Navigate, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
import {
AppListConstants,
+ BASE_ROUTES,
+ Chip,
FilterChips,
getNamespaceListMin,
handleUTCTime,
@@ -35,9 +28,9 @@ import {
InfrastructureManagementAppListType,
ModuleNameMap,
Progressing,
+ ROUTER_URLS,
TabGroup,
TabProps,
- URLS as COMMON_URLS,
useAsync,
useMainContext,
useUrlFilters,
@@ -48,7 +41,7 @@ import { CreateAppModal } from '@Pages/App/CreateAppModal'
import { getCommonAppFilters } from '@Services/service'
import { Cluster } from '@Services/service.types'
-import { SERVER_MODE, URLS } from '../../../config'
+import { SERVER_MODE } from '../../../config'
import { getModuleInfo } from '../../v2/devtronStackManager/DevtronStackManager.service'
import DevtronAppList from '../list/DevtronAppListContainer'
import AppListFilters from './AppListFilters'
@@ -59,7 +52,13 @@ import {
AppListUrlFiltersType,
FluxCDTemplateType,
} from './AppListType'
-import { APP_LIST_LOCAL_STORAGE_KEY, DEVTRON_APP_LIST_LOCAL_STORAGE_KEY, FLUX_CD_HELM_RELEASE_LABEL } from './Constants'
+import {
+ APP_LIST_LOCAL_STORAGE_KEY,
+ DEVTRON_APP_LIST_LOCAL_STORAGE_KEY,
+ FLUX_CD_HELM_RELEASE_LABEL,
+ LABEL_OPERATOR_DISPLAY_TEXT,
+ LABEL_OPERATORS_WITHOUT_VALUE,
+} from './Constants'
import GenericAppList from './GenericAppList'
import HelmAppList from './HelmAppList'
import {
@@ -68,15 +67,15 @@ import {
getFormattedFilterLabel,
parseSearchParams,
} from './list.utils'
+import { AppListFilterLabelType } from './types'
import '../list/list.scss'
let interval
const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
- const { url } = useRouteMatch()
const params = useParams<{ appType: InfrastructureManagementAppListType }>()
const { serverMode, isSuperAdmin } = useMainContext()
@@ -121,6 +120,7 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
cluster,
project,
templateType,
+ [AppListUrlFilters.labelSelector]: labelSelectorRaw,
handleSorting,
changePage,
changePageSize,
@@ -129,6 +129,19 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
updateSearchParams,
} = urlFilters
+ const labelSelectors: AppListFilterLabelType[] = useMemo(() => {
+ if (!labelSelectorRaw) {
+ return []
+ }
+
+ try {
+ const parsed = JSON.parse(labelSelectorRaw)
+ return Array.isArray(parsed) ? parsed : []
+ } catch {
+ return []
+ }
+ }, [labelSelectorRaw])
+
const filterConfig: AppListFilterConfig = useMemo(
() => ({
offset,
@@ -142,6 +155,8 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
cluster,
namespace,
templateType,
+ [AppListUrlFilters.labelSelector]: labelSelectorRaw,
+ labelSelectors,
}),
[
offset,
@@ -155,6 +170,7 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
JSON.stringify(cluster),
JSON.stringify(namespace),
JSON.stringify(templateType),
+ labelSelectorRaw,
],
)
@@ -279,21 +295,63 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
setFetchingExternalApps(fetching)
}
- const renderAppliedFilters = () =>
- !appListFiltersLoading &&
- !appListFiltersError && (
- >
- filterConfig={getFilterChipConfig(
- { appStatus, project, environment, cluster, namespace, templateType },
- params.appType,
- )}
- onRemoveFilter={updateSearchParams}
- clearFilters={clearFilters}
- className="px-20"
- getFormattedLabel={getFormattedFilterLabel}
- getFormattedValue={getFormattedFilterValue}
- />
+ const handleApplyLabelSelectors = (selectors: AppListFilterLabelType[]): void => {
+ const filtered = selectors.filter((s) => !!s.key.trim())
+ updateSearchParams({
+ [AppListUrlFilters.labelSelector]: filtered.length ? JSON.stringify(filtered) : '',
+ })
+ }
+
+ const getRemoveLabelSelectorHandler = (selectorId: string) => () => {
+ const updatedSelectors = labelSelectors.filter((s) => s.id !== selectorId)
+ updateSearchParams({
+ [AppListUrlFilters.labelSelector]: updatedSelectors.length ? JSON.stringify(updatedSelectors) : '',
+ })
+ }
+
+ const renderAppliedFilters = () => {
+ if (appListFiltersLoading || appListFiltersError) {
+ return null
+ }
+
+ const urlChipConfig = getFilterChipConfig(
+ {
+ appStatus,
+ project,
+ environment,
+ cluster,
+ namespace,
+ templateType,
+ },
+ params.appType,
+ )
+
+ return (
+
+ {labelSelectors.map((selector) => (
+
+ ))}
+
+ {/* FilterChips renders its own "Clear All" when URL chips are present */}
+ >
+ filterConfig={urlChipConfig}
+ onRemoveFilter={updateSearchParams}
+ clearFilters={clearFilters}
+ getFormattedLabel={getFormattedFilterLabel}
+ getFormattedValue={getFormattedFilterValue}
+ />
+
)
+ }
const removePageNumber = (search: string) => {
const searchParams = new URLSearchParams(search)
@@ -308,7 +366,7 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
tabType: 'navLink',
props: {
to: {
- pathname: URLS.HELM_APP_LIST,
+ pathname: ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_LIST.HELM,
search: removePageNumber(location.search),
},
'data-testid': 'helm-app-list-button',
@@ -322,7 +380,7 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
tabType: 'navLink' as const,
props: {
to: {
- pathname: URLS.ARGO_APP_LIST,
+ pathname: ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_LIST.ARGO_CD,
search: removePageNumber(location.search),
},
'data-testid': 'argo-app-list-button',
@@ -338,7 +396,7 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
tabType: 'navLink' as const,
props: {
to: {
- pathname: URLS.FLUX_APP_LIST,
+ pathname: ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_LIST.FLUX_CD,
search: removePageNumber(location.search),
},
'data-testid': 'flux-app-list-button',
@@ -355,18 +413,20 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
)
const closeDevtronAppCreateModal = () => {
- history.push(`${url}${location.search}`)
+ navigate({
+ pathname: ROUTER_URLS.DEVTRON_APP_LIST,
+ search: location.search,
+ })
}
const renderAppCreateRouter = () => (
-
+
-
-
-
+ path={BASE_ROUTES.APPLICATION_MANAGEMENT.DEVTRON_APP.LIST.CREATE_APP}
+ key={BASE_ROUTES.APPLICATION_MANAGEMENT.DEVTRON_APP.LIST.CREATE_APP}
+ element={}
+ />
+
)
return (
@@ -395,6 +455,9 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
isDataSyncing={isDataSyncing}
lastSyncTimeString={lastDataSyncTimeString}
showExportCsvButton={isSuperAdmin && isDevtronAppList}
+ isDevtronAppList={isDevtronAppList}
+ labelSelectors={labelSelectors}
+ handleApplyLabelSelectors={handleApplyLabelSelectors}
/>
{renderAppliedFilters()}
{!isDevtronAppList && renderAppTabs()}
@@ -461,7 +524,12 @@ const AppList = ({ isDevtronAppList }: { isDevtronAppList?: boolean }) => {
/>
)}
{tabs.every((tab) => tab.id !== params.appType) && (
-
+
)}
>
)}
diff --git a/src/components/app/list-new/AppListComponents.tsx b/src/components/app/list-new/AppListComponents.tsx
index 8525becf0d..3420ff7bc7 100644
--- a/src/components/app/list-new/AppListComponents.tsx
+++ b/src/components/app/list-new/AppListComponents.tsx
@@ -16,7 +16,7 @@
import { GenericFilterEmptyState } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as InfoFill } from '@Icons/ic-info-filled.svg'
+import InfoFill from '@Icons/ic-info-filled.svg?react'
import { AskToClearFiltersProps } from './AppListType'
import { APP_LIST_EMPTY_STATE_MESSAGING } from './Constants'
diff --git a/src/components/app/list-new/AppListFilters.tsx b/src/components/app/list-new/AppListFilters.tsx
index 0174195795..f52961b603 100644
--- a/src/components/app/list-new/AppListFilters.tsx
+++ b/src/components/app/list-new/AppListFilters.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useMemo } from 'react'
+import { MutableRefObject, useMemo } from 'react'
import ReactGA from 'react-ga4'
import {
@@ -33,8 +33,9 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'
import { getDevtronAppListDataToExport } from './AppListService'
-import { AppListFiltersProps, AppListUrlFilters, AppStatuses } from './AppListType'
+import { AppListFilterKey, AppListFiltersProps, AppListUrlFilters, AppStatuses } from './AppListType'
import { APP_STATUS_FILTER_OPTIONS, APPLIST_EXPORT_HEADERS, TEMPLATE_TYPE_FILTER_OPTIONS } from './Constants'
+import LabelSelectorForm from './LabelSelectorForm'
import { getAppListFilters, getAppTabNameFromAppType, useFilterOptions } from './list.utils'
const AppListFilters = ({
@@ -58,6 +59,9 @@ const AppListFilters = ({
lastSyncTimeString,
showExportCsvButton,
isDataSyncing,
+ isDevtronAppList,
+ labelSelectors,
+ handleApplyLabelSelectors,
}: AppListFiltersProps) => {
const { appStatus, cluster, environment, namespace, project, templateType, searchKey } = filterConfig
@@ -131,7 +135,17 @@ const AppListFilters = ({
updateSearchParams({ [filterKey]: selectedOptions.map((option) => String(option.value)) })
}
- const appListFiltersSelectPickerMap: GroupedFilterSelectPickerProps['filterSelectPickerPropsMap'] =
+ const renderLabelSelectionForm = (closePopover: () => void, scrollableRef: MutableRefObject) => (
+
+
+
+ )
+
+ const appListFiltersSelectPickerMap: GroupedFilterSelectPickerProps['filterSelectPickerPropsMap'] =
{
[AppListUrlFilters.appStatus]: {
placeholder: 'App Status',
@@ -198,6 +212,11 @@ const AppListFilters = ({
optionListError: appListFiltersError,
reloadOptionList: reloadAppListFilters,
},
+ [AppListUrlFilters.labelSelector]: {
+ variant: 'popover',
+ component: renderLabelSelectionForm,
+ popoverConfig: { width: 560, position: 'bottom' },
+ },
}
const getExportToCsvApiPromise: ExportToCsvProps<(typeof APPLIST_EXPORT_HEADERS)[number]['key']>['apiPromise'] = ({
@@ -247,7 +266,7 @@ const AppListFilters = ({
keyboardShortcut="/"
/>
-
+
id="app-list-filters"
width={200}
isFilterApplied={
@@ -257,7 +276,8 @@ const AppListFilters = ({
selectedEnvironments.length ||
selectedTemplateTypes.length ||
selectedClusters.length ||
- selectedNamespaces.length
+ selectedNamespaces.length ||
+ labelSelectors.length
)
}
options={getAppListFilters({
@@ -267,6 +287,7 @@ const AppListFilters = ({
isArgoInstalled,
serverMode,
selectedEnvironments,
+ isDevtronAppList,
})}
filterSelectPickerPropsMap={appListFiltersSelectPickerMap}
/>
diff --git a/src/components/app/list-new/AppListType.ts b/src/components/app/list-new/AppListType.ts
index 42643d8a05..ce86238d14 100644
--- a/src/components/app/list-new/AppListType.ts
+++ b/src/components/app/list-new/AppListType.ts
@@ -29,6 +29,7 @@ import { Cluster } from '@Services/service.types'
import { DevtronAppListProps } from '../list/types'
import { APP_LIST_HEADERS } from './Constants'
+import { AppListFilterLabelOperatorType, AppListFilterLabelType } from './types'
export enum FluxCDTemplateType {
KUSTOMIZATION = 'Kustomization',
@@ -102,18 +103,6 @@ export interface HelmAppListResponse extends ResponseType {
result?: HelmAppsListResult
}
-export interface AppListPayloadType {
- environments: number[]
- teams: number[]
- namespaces: string[]
- appNameSearch: string
- appStatuses: string[]
- sortBy: AppListSortableKeys
- sortOrder: SortingOrder
- offset: number
- size: number
-}
-
export enum AppListSortableKeys {
APP_NAME = 'appNameSort',
LAST_DEPLOYED = 'lastDeployedSort',
@@ -126,15 +115,41 @@ export enum AppListUrlFilters {
namespace = 'namespace',
cluster = 'cluster',
templateType = 'templateType',
+ labelSelector = 'labelSelector',
+}
+
+export type AppListFilterKey = AppListUrlFilters
+
+export interface AppListUrlFiltersType
+ extends Omit, AppListUrlFilters.labelSelector> {
+ [AppListUrlFilters.labelSelector]: string
+}
+
+export interface LabelSelectorPayloadType {
+ key: string
+ operator: AppListFilterLabelOperatorType
+ value?: string
}
-export interface AppListUrlFiltersType extends Record {}
+export interface AppListPayloadType {
+ environments: number[]
+ teams: number[]
+ namespaces: string[]
+ appNameSearch: string
+ appStatuses: string[]
+ sortBy: AppListSortableKeys
+ sortOrder: SortingOrder
+ offset: number
+ size: number
+ tagFilters: LabelSelectorPayloadType[]
+}
export interface AppListFilterConfig
extends AppListUrlFiltersType,
Pick {
pageSize: number
searchKey: string
+ labelSelectors: AppListFilterLabelType[]
}
export interface HelmAppListProps
@@ -197,6 +212,9 @@ export interface AppListFiltersProps
lastSyncTimeString?: string
isDataSyncing: boolean
showExportCsvButton: boolean
+ isDevtronAppList: boolean
+ labelSelectors: AppListFilterLabelType[]
+ handleApplyLabelSelectors: (selectors: AppListFilterLabelType[]) => void
}
export interface useFilterOptionsProps
@@ -219,7 +237,7 @@ export interface AskToClearFiltersProps extends Pick['options'][number]['items'][number]
+ GroupedFilterSelectPickerProps['options'][number]['items'][number]
export interface GetAppListFiltersParams {
clusterIdsCsv: string
@@ -228,6 +246,7 @@ export interface GetAppListFiltersParams {
isArgoInstalled: boolean
serverMode: SERVER_MODE
selectedEnvironments: { label: string; value: string }[]
+ isDevtronAppList: boolean
}
export interface ExportAppListDataType {
diff --git a/src/components/app/list-new/Constants.ts b/src/components/app/list-new/Constants.ts
index a16998ed35..135bcf0419 100644
--- a/src/components/app/list-new/Constants.ts
+++ b/src/components/app/list-new/Constants.ts
@@ -16,9 +16,15 @@
import { GroupBase } from 'react-select'
-import { ExportToCsvProps, SelectPickerOptionType, UseUrlFiltersProps } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ DynamicDataTableHeaderType,
+ ExportToCsvProps,
+ SelectPickerOptionType,
+ UseUrlFiltersProps,
+} from '@devtron-labs/devtron-fe-common-lib'
import { AppStatuses, AppStatusesDTO, ExportAppListDataType, FluxCDTemplateType } from './AppListType'
+import { AppListFilterLabelOperatorType, AppListFilterLabelTableHeaderType } from './types'
export const APP_LIST_HEADERS = {
AppName: 'APP NAME',
@@ -105,3 +111,37 @@ export const APPLIST_EXPORT_HEADERS: ExportToCsvProps[] = [
+ {
+ label: 'Key',
+ key: AppListFilterLabelTableHeaderType.KEY,
+ width: '200px',
+ },
+ {
+ label: 'Operator',
+ key: AppListFilterLabelTableHeaderType.OPERATOR,
+ width: '140px',
+ },
+ {
+ label: 'Value',
+ key: AppListFilterLabelTableHeaderType.VALUE,
+ width: '1fr',
+ },
+]
+
+export const LABEL_OPERATORS_WITHOUT_VALUE: AppListFilterLabelOperatorType[] = [
+ AppListFilterLabelOperatorType.EXISTS,
+ AppListFilterLabelOperatorType.DOES_NOT_EXIST,
+]
+
+export const DEFAULT_LABEL_SELECTOR_OPERATOR = AppListFilterLabelOperatorType.EQUALS
+
+export const LABEL_OPERATOR_DISPLAY_TEXT: Record = {
+ [AppListFilterLabelOperatorType.EQUALS]: 'equals',
+ [AppListFilterLabelOperatorType.DOES_NOT_EQUAL]: 'not equals',
+ [AppListFilterLabelOperatorType.CONTAINS]: 'contains',
+ [AppListFilterLabelOperatorType.DOES_NOT_CONTAIN]: 'does not contain',
+ [AppListFilterLabelOperatorType.EXISTS]: 'exists',
+ [AppListFilterLabelOperatorType.DOES_NOT_EXIST]: 'does not exist',
+}
diff --git a/src/components/app/list-new/GenericAppList.tsx b/src/components/app/list-new/GenericAppList.tsx
index b1bf6ae14d..fb672a1661 100644
--- a/src/components/app/list-new/GenericAppList.tsx
+++ b/src/components/app/list-new/GenericAppList.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useRef, useEffect, useMemo, useState, useCallback } from 'react'
+import { useEffect, useMemo, useState, type JSX, useCallback, useRef } from 'react'
import {
TableProps,
ErrorScreenManager,
@@ -26,14 +26,14 @@ import {
ResponseType,
showError,
InfrastructureManagementAppListType,
- URLS as CommonURLS,
+ ROUTER_URLS,
Table,
PaginationEnum,
FiltersTypeEnum,
} from '@devtron-labs/devtron-fe-common-lib'
-import { Link, useHistory } from 'react-router-dom'
+import { generatePath, Link, useNavigate } from 'react-router-dom'
import { getArgoInstalledExternalApps } from './AppListService'
-import { Routes, URLS } from '../../../config'
+import { Routes } from '../../../config'
import { AppListViewType } from '../config'
import SelectClusterImage from '../../../assets/icons/ic-select-cluster.svg'
import '../list/list.scss'
@@ -65,10 +65,10 @@ const GenericAppList = ({
const [dataStateType, setDataStateType] = useState(AppListViewType.LOADING)
const [errorResponseCode, setErrorResponseCode] = useState(0)
const [appsList, setAppsList] = useState([])
- const sseConnectionRef = useRef()
+ const sseConnectionRef = useRef(null)
const { isSuperAdmin } = useMainContext()
- const { push } = useHistory()
+ const navigate = useNavigate()
const isArgoCDAppList = appType === InfrastructureManagementAppListType.ARGO_CD
const isFluxCDAppList = appType === InfrastructureManagementAppListType.FLUX_CD
@@ -194,9 +194,18 @@ const GenericAppList = ({
const buildAppDetailUrl = (app: GenericAppType): string => {
if (isArgoCDAppList) {
- return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.EXTERNAL_ARGO_APP}/${app.clusterId}/${app.appName}/${app.namespace}`
+ return generatePath(ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_DETAIL.EXTERNAL_ARGO_APP, {
+ clusterId: app.clusterId,
+ appName: app.appName,
+ namespace: app.namespace,
+ })
}
- return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.EXTERNAL_FLUX_APP}/${app.clusterId}/${app.appName}/${app.namespace}/${app.fluxAppDeploymentType}`
+ return generatePath(ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_DETAIL.EXTERNAL_FLUX_APP, {
+ clusterId: app.clusterId,
+ appName: app.appName,
+ namespace: app.namespace,
+ templateType: app.fluxAppDeploymentType,
+ })
}
const askToSelectClusterId = () => (
@@ -219,7 +228,7 @@ const GenericAppList = ({
)
const onRowClick = useCallback(({ data: app }) => {
- push(buildAppDetailUrl(app.detail))
+ navigate(buildAppDetailUrl(app.detail))
}, [])
const filter = useCallback(({ data: app }) => {
@@ -252,7 +261,7 @@ const GenericAppList = ({
isButtonAvailable: true,
renderButton: () => {
return (
-
+
{APP_LIST_EMPTY_STATE_MESSAGING.connectClusterLabel}
diff --git a/src/components/app/list-new/HelmAppList.tsx b/src/components/app/list-new/HelmAppList.tsx
index 57ec62e22b..c38b713069 100644
--- a/src/components/app/list-new/HelmAppList.tsx
+++ b/src/components/app/list-new/HelmAppList.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useEffect, useMemo, useState } from 'react'
+import { useEffect, useMemo, useState, type JSX } from 'react'
import {
AppStatus,
showError,
@@ -31,27 +31,27 @@ import {
useStickyEvent,
getClassNameForStickyHeaderWithShadow,
DocLink,
- URLS as CommonURLS,
ComponentSizeType,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { Link } from 'react-router-dom'
+import { generatePath, Link } from 'react-router-dom'
import Tippy from '@tippyjs/react'
import moment from 'moment'
import { getDevtronInstalledHelmApps } from './AppListService'
import { LazyImage } from '../../common'
-import { SERVER_MODE, URLS, checkIfDevtronOperatorHelmRelease, ModuleNameMap } from '../../../config'
+import { SERVER_MODE, checkIfDevtronOperatorHelmRelease, ModuleNameMap } from '../../../config'
import { AppListViewType } from '../config'
-import { ReactComponent as ICHelpOutline } from '../../../assets/icons/ic-help-outline.svg'
+import ICHelpOutline from '../../../assets/icons/ic-help-outline.svg?react'
import NoClusterSelectImage from '../../../assets/icons/ic-select-cluster.svg'
import defaultChartImage from '../../../assets/icons/ic-default-chart.svg'
import HelmCluster from '../../../assets/img/guided-helm-cluster.png'
import DeployCICD from '../../../assets/img/guide-onboard.png'
import { AllCheckModal } from '../../checkList/AllCheckModal'
-import { ReactComponent as InfoFillPurple } from '../../../assets/icons/ic-info-filled-purple.svg'
-import { ReactComponent as ErrorExclamationIcon } from '../../../assets/icons/ic-error-exclamation.svg'
-import { ReactComponent as CloseIcon } from '../../../assets/icons/ic-close.svg'
-import { ReactComponent as AlertTriangleIcon } from '../../../assets/icons/ic-alert-triangle.svg'
-import { ReactComponent as ArrowRight } from '../../../assets/icons/ic-arrow-right.svg'
+import InfoFillPurple from '../../../assets/icons/ic-info-filled-purple.svg?react'
+import ErrorExclamationIcon from '../../../assets/icons/ic-error-exclamation.svg?react'
+import CloseIcon from '../../../assets/icons/ic-close.svg?react'
+import AlertTriangleIcon from '../../../assets/icons/ic-alert-triangle.svg?react'
+import ArrowRight from '../../../assets/icons/ic-arrow-right.svg?react'
import noChartInClusterImage from '../../../assets/img/ic-no-chart-in-clusters@2x.png'
import ContentCard from '../../common/ContentCard/ContentCard'
import { CardContentDirection, CardLinkIconPlacement } from '../../common/ContentCard/ContentCard.types'
@@ -316,10 +316,17 @@ const HelmAppList = ({
}
function _buildAppDetailUrl(app: HelmApp) {
+ const APP_DETAIL_ROUTE = ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_DETAIL
if (app.isExternal) {
- return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.EXTERNAL_APPS}/${app.appId}/${app.appName}`
+ return generatePath(APP_DETAIL_ROUTE.EXTERNAL_HELM_APP, {
+ appId: app.appId,
+ appName: app.appName,
+ })
}
- return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.DEVTRON_CHARTS}/deployments/${app.appId}/env/${app.environmentDetail.environmentId}`
+ return generatePath(APP_DETAIL_ROUTE.DEVTRON_CHART, {
+ appId: app.appId,
+ envId: `${app.environmentDetail.environmentId}`,
+ })
}
function _removeExternalAppFetchError(e) {
@@ -490,7 +497,7 @@ const HelmAppList = ({
{showGuidedContentCards && (
(
-
+
{APP_LIST_EMPTY_STATE_MESSAGING.connectClusterLabel}
diff --git a/src/components/app/list-new/LabelSelectorForm.tsx b/src/components/app/list-new/LabelSelectorForm.tsx
new file mode 100644
index 0000000000..c37ccb18b9
--- /dev/null
+++ b/src/components/app/list-new/LabelSelectorForm.tsx
@@ -0,0 +1,148 @@
+import { useState } from 'react'
+
+import {
+ Button,
+ ButtonStyleType,
+ ButtonVariantType,
+ ComponentSizeType,
+ DynamicDataTable,
+ DynamicDataTableCellErrorType,
+ DynamicDataTableProps,
+ ToastManager,
+ ToastVariantType,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import {
+ DEFAULT_LABEL_SELECTOR_OPERATOR,
+ LABEL_OPERATORS_WITHOUT_VALUE,
+ LABEL_SELECTOR_HEADER_CONFIG,
+} from './Constants'
+import {
+ AppListFilterLabelOperatorType,
+ AppListFilterLabelTableHeaderType,
+ AppListFilterLabelType,
+ LabelSelectorFormProps,
+} from './types'
+import { getEmptyLabelSelector, getSelectorRowConfig, getSelectorsErrorState } from './utils'
+
+const LabelSelectorForm = ({ closePopover, initialLabels, onApply }: LabelSelectorFormProps) => {
+ const [selectors, setSelectors] = useState(() =>
+ initialLabels.length ? initialLabels : [getEmptyLabelSelector()],
+ )
+ const [cellError, setCellError] = useState>({})
+ const selectorRows: DynamicDataTableProps['rows'] =
+ selectors.map(getSelectorRowConfig)
+
+ const handleRowAdd: DynamicDataTableProps['onRowAdd'] = () => {
+ const updatedSelectors: typeof selectors = [getEmptyLabelSelector(), ...selectors]
+ setSelectors(updatedSelectors)
+ }
+
+ const handleRowEdit: DynamicDataTableProps['onRowEdit'] = (
+ row,
+ headerKey,
+ value,
+ ) => {
+ const updatedSelectors: typeof initialLabels = selectors.map((selector) => {
+ if (selector.id === row.id) {
+ const updatedSelector = { ...selector }
+ switch (headerKey) {
+ case AppListFilterLabelTableHeaderType.KEY:
+ updatedSelector.key = value || ''
+ break
+ case AppListFilterLabelTableHeaderType.VALUE:
+ updatedSelector.value = value || ''
+ break
+ case AppListFilterLabelTableHeaderType.OPERATOR: {
+ const operator = (value as AppListFilterLabelOperatorType) || DEFAULT_LABEL_SELECTOR_OPERATOR
+ if (LABEL_OPERATORS_WITHOUT_VALUE.includes(operator)) {
+ delete updatedSelector.value
+ }
+ updatedSelector.operator = operator
+
+ break
+ }
+ default:
+ break
+ }
+ return updatedSelector
+ }
+ return selector
+ })
+ setSelectors(updatedSelectors)
+ setCellError(getSelectorsErrorState(updatedSelectors))
+ }
+
+ const handleRowDelete: DynamicDataTableProps['onRowDelete'] = (deletedRow) => {
+ const isLastRow = selectors.length === 1
+
+ const updatedSelectors: typeof initialLabels = isLastRow
+ ? [
+ {
+ id: deletedRow.id as string,
+ key: '',
+ value: '',
+ operator: DEFAULT_LABEL_SELECTOR_OPERATOR,
+ } satisfies (typeof initialLabels)[number],
+ ]
+ : selectors.filter((selector) => selector.id !== deletedRow.id)
+
+ setSelectors(updatedSelectors)
+ setCellError(getSelectorsErrorState(updatedSelectors))
+ }
+
+ const handleApply = () => {
+ const errors = getSelectorsErrorState(selectors)
+ setCellError(errors)
+
+ if (Object.keys(errors).length) {
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: 'Please fix the errors before applying.',
+ })
+ return
+ }
+
+ onApply(selectors)
+ closePopover()
+ }
+
+ return (
+
+
+
Filter by tags
+
Multiple tag filters are applied using AND logic
+
+
+
+ headers={LABEL_SELECTOR_HEADER_CONFIG}
+ rows={selectorRows}
+ onRowAdd={handleRowAdd}
+ onRowEdit={handleRowEdit}
+ onRowDelete={handleRowDelete}
+ shouldAutoFocusOnMount={false}
+ cellError={cellError}
+ />
+
+
+
+
+
+
+
+ )
+}
+
+export default LabelSelectorForm
diff --git a/src/components/app/list-new/list.utils.ts b/src/components/app/list-new/list.utils.ts
index 172fbe6e70..c84c6ea69e 100644
--- a/src/components/app/list-new/list.utils.ts
+++ b/src/components/app/list-new/list.utils.ts
@@ -36,6 +36,7 @@ import FluxCDAppIcon from '@Icons/ic-fluxcd-app.svg'
import { Cluster } from '@Services/service.types'
import {
+ AppListFilterKey,
AppListFilterMenuItemType,
AppListSortableKeys,
AppListUrlFilters,
@@ -75,6 +76,7 @@ export const parseSearchParams = (searchParams: URLSearchParams) => ({
[AppListUrlFilters.cluster]: searchParams.getAll(AppListUrlFilters.cluster),
[AppListUrlFilters.namespace]: searchParams.getAll(AppListUrlFilters.namespace),
[AppListUrlFilters.templateType]: searchParams.getAll(AppListUrlFilters.templateType),
+ [AppListUrlFilters.labelSelector]: searchParams.get(AppListUrlFilters.labelSelector) ?? '',
})
export const getFormattedFilterLabel = (filterType: AppListUrlFilters) => {
@@ -198,7 +200,7 @@ export const useFilterOptions = ({
}
export const getFilterChipConfig = (
- filterConfig: AppListUrlFiltersType,
+ filterConfig: Omit,
appType: InfrastructureManagementAppListType,
): Partial => {
const { cluster, namespace, templateType } = filterConfig
@@ -207,8 +209,9 @@ export const getFilterChipConfig = (
return { cluster, namespace }
case InfrastructureManagementAppListType.FLUX_CD:
return { cluster, namespace, templateType }
- default:
+ default: {
return { ...filterConfig, templateType: [] }
+ }
}
}
@@ -219,7 +222,8 @@ export const getAppListFilters = ({
isArgoInstalled,
serverMode,
selectedEnvironments,
-}: GetAppListFiltersParams): GroupedFilterSelectPickerProps['options'] => [
+ isDevtronAppList,
+}: GetAppListFiltersParams): GroupedFilterSelectPickerProps['options'] => [
{
items: [
...((!(isExternalArgo || isExternalFlux)
@@ -285,6 +289,15 @@ export const getAppListFilters = ({
isDisabled: !clusterIdsCsv,
tooltipProps: { content: !clusterIdsCsv ? SELECT_CLUSTER_TIPPY : null },
},
+ ...((isDevtronAppList
+ ? [
+ {
+ id: AppListUrlFilters.labelSelector,
+ label: 'Tags',
+ startIcon: { name: 'ic-tag' },
+ },
+ ]
+ : []) as AppListFilterMenuItemType[]),
],
},
]
diff --git a/src/components/app/list-new/types.ts b/src/components/app/list-new/types.ts
new file mode 100644
index 0000000000..d1a37fa41e
--- /dev/null
+++ b/src/components/app/list-new/types.ts
@@ -0,0 +1,38 @@
+export enum AppListFilterLabelOperatorType {
+ EQUALS = 'EQUALS',
+ DOES_NOT_EQUAL = 'DOES_NOT_EQUAL',
+ CONTAINS = 'CONTAINS',
+ DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN',
+ EXISTS = 'EXISTS',
+ DOES_NOT_EXIST = 'DOES_NOT_EXIST',
+}
+
+export type AppListFilterLabelType = {
+ key: string
+ id: string
+} & (
+ | {
+ operator: AppListFilterLabelOperatorType.EXISTS | AppListFilterLabelOperatorType.DOES_NOT_EXIST
+ value?: never
+ }
+ | {
+ operator:
+ | AppListFilterLabelOperatorType.EQUALS
+ | AppListFilterLabelOperatorType.DOES_NOT_EQUAL
+ | AppListFilterLabelOperatorType.CONTAINS
+ | AppListFilterLabelOperatorType.DOES_NOT_CONTAIN
+ value: string
+ }
+)
+
+export enum AppListFilterLabelTableHeaderType {
+ KEY = 'key',
+ OPERATOR = 'operator',
+ VALUE = 'value',
+}
+
+export interface LabelSelectorFormProps {
+ closePopover: () => void
+ initialLabels: AppListFilterLabelType[]
+ onApply: (selectors: AppListFilterLabelType[]) => void
+}
diff --git a/src/components/app/list-new/utils.ts b/src/components/app/list-new/utils.ts
new file mode 100644
index 0000000000..017eea0b38
--- /dev/null
+++ b/src/components/app/list-new/utils.ts
@@ -0,0 +1,98 @@
+import {
+ DynamicDataTableCellErrorType,
+ DynamicDataTableProps,
+ DynamicDataTableRowDataType,
+ getUniqueId,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import {
+ DEFAULT_LABEL_SELECTOR_OPERATOR,
+ LABEL_OPERATOR_DISPLAY_TEXT,
+ LABEL_OPERATORS_WITHOUT_VALUE,
+} from './Constants'
+import { AppListFilterLabelOperatorType, AppListFilterLabelTableHeaderType, AppListFilterLabelType } from './types'
+
+export const getSelectorRowConfig = (
+ selector?: AppListFilterLabelType,
+): DynamicDataTableProps['rows'][number] => ({
+ data: {
+ [AppListFilterLabelTableHeaderType.KEY]: {
+ value: selector?.key || '',
+ type: DynamicDataTableRowDataType.TEXT,
+ props: {
+ placeholder: 'Key',
+ },
+ },
+ [AppListFilterLabelTableHeaderType.OPERATOR]: {
+ value: selector?.operator || DEFAULT_LABEL_SELECTOR_OPERATOR,
+ type: DynamicDataTableRowDataType.DROPDOWN,
+ props: {
+ options: Object.values(AppListFilterLabelOperatorType).map((operator) => ({
+ label: LABEL_OPERATOR_DISPLAY_TEXT[operator] || operator,
+ value: operator,
+ })),
+ placeholder: 'Select operator',
+ },
+ },
+ [AppListFilterLabelTableHeaderType.VALUE]: {
+ value: selector?.value || '',
+ type: DynamicDataTableRowDataType.TEXT,
+ disabled: LABEL_OPERATORS_WITHOUT_VALUE.includes(selector?.operator),
+ tooltip: {
+ content: LABEL_OPERATORS_WITHOUT_VALUE.includes(selector?.operator)
+ ? 'Value not required for this operator'
+ : '',
+ },
+ props: {
+ placeholder: 'Enter value',
+ },
+ },
+ },
+ id: selector?.id || getUniqueId(),
+})
+
+export const getEmptyLabelSelector = (): AppListFilterLabelType => ({
+ key: '',
+ value: '',
+ operator: DEFAULT_LABEL_SELECTOR_OPERATOR,
+ id: getUniqueId(),
+})
+
+export const validateLabelValue = (value: string, operator: AppListFilterLabelOperatorType): string | null => {
+ if (LABEL_OPERATORS_WITHOUT_VALUE.includes(operator)) {
+ return null
+ }
+ if (!value.trim()) {
+ return 'Value is required for the selected operator'
+ }
+ return null
+}
+
+export const getSelectorsErrorState = (
+ selectors: AppListFilterLabelType[],
+): DynamicDataTableCellErrorType => {
+ const cellErrors: DynamicDataTableCellErrorType = {}
+ selectors.forEach((selector) => {
+ const hasKey = !!selector.key.trim()
+ const hasValue = !!(selector.value || '').trim()
+
+ const keyError = !hasKey && hasValue ? 'Key is required' : null
+ const valueError = hasKey ? validateLabelValue(selector.value || '', selector.operator) : null
+ if (keyError || valueError) {
+ cellErrors[selector.id] = {}
+ if (keyError) {
+ cellErrors[selector.id][AppListFilterLabelTableHeaderType.KEY] = {
+ isValid: false,
+ errorMessages: [keyError],
+ }
+ }
+ if (valueError) {
+ cellErrors[selector.id][AppListFilterLabelTableHeaderType.VALUE] = {
+ isValid: false,
+ errorMessages: [valueError],
+ }
+ }
+ }
+ })
+ return cellErrors
+}
diff --git a/src/components/app/list/DevtronAppListContainer.tsx b/src/components/app/list/DevtronAppListContainer.tsx
index 9d2039dc76..78464266df 100644
--- a/src/components/app/list/DevtronAppListContainer.tsx
+++ b/src/components/app/list/DevtronAppListContainer.tsx
@@ -15,7 +15,7 @@
*/
import { MouseEvent, useCallback, useMemo, useState } from 'react'
-import { useHistory } from 'react-router-dom'
+import { generatePath, useNavigate } from 'react-router-dom'
import {
Button,
@@ -25,21 +25,21 @@ import {
FiltersTypeEnum,
Icon,
PaginationEnum,
+ ROUTER_URLS,
Table,
TableRowActionsOnHoverComponentProps,
TableViewWrapperProps,
- URLS as CommonURLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as PlayMedia } from '@Icons/ic-play-outline.svg'
+import PlayMedia from '@Icons/ic-play-outline.svg?react'
import ContentCard from '@Components/common/ContentCard/ContentCard'
import { CardLinkIconPlacement } from '@Components/common/ContentCard/ContentCard.types'
import { HELM_GUIDED_CONTENT_CARDS_TEXTS } from '@Components/onboardingGuide/OnboardingGuide.constants'
-import { ReactComponent as ArrowRight } from '../../../assets/icons/ic-arrow-right.svg'
+import ArrowRight from '../../../assets/icons/ic-arrow-right.svg?react'
import DeployCICD from '../../../assets/img/guide-onboard.png'
import NodeAppThumbnail from '../../../assets/img/node-app-thumbnail.png'
-import { DEVTRON_NODE_DEPLOY_VIDEO, Routes, URLS } from '../../../config'
+import { DEVTRON_NODE_DEPLOY_VIDEO } from '../../../config'
import { AppListSortableKeys } from '../list-new/AppListType'
import { getAppList } from '../service'
import { appListModal, getDevtronAppListPayload } from './appList.modal'
@@ -49,13 +49,13 @@ import { getTableColumns } from './utils'
import './list.scss'
const HoverComponent = ({ row: { data } }: TableRowActionsOnHoverComponentProps) => {
- const { push } = useHistory()
const app = data as App
+ const navigate = useNavigate()
const handleEditAppClick = (e: MouseEvent) => {
e.stopPropagation()
- const url = `${URLS.APPLICATION_MANAGEMENT_APP}/${app.id}/${Routes.EDIT}`
- push(url)
+ const url = generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.CONFIGURATIONS, { appId: String(app.id) })
+ navigate(url)
}
return (
@@ -91,7 +91,7 @@ const renderGuidedCards = () => (
/>
{
+ const navigate = useNavigate()
const [noRows, setNoRows] = useState(false)
- const { searchKey, appStatus, project, environment, namespace, cluster } = filterConfig
-
- const { push } = useHistory()
+ const { searchKey, appStatus, project, environment, namespace, cluster, labelSelectors } = filterConfig
const isSearchOrFilterApplied = !!(
searchKey ||
@@ -140,15 +139,20 @@ const DevtronAppList = ({
project.length ||
environment.length ||
namespace.length ||
- cluster.length
+ cluster.length ||
+ labelSelectors.length
)
const redirectToAppDetails = (app, envId: number): string => {
if (envId) {
- return `${URLS.APPLICATION_MANAGEMENT_APP}/${app.id}/details/${envId}`
+ return generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.ENV_DETAILS, {
+ appId: String(app.id),
+ envId: String(envId),
+ })
}
-
- return `${URLS.APPLICATION_MANAGEMENT_APP}/${app.id}/trigger`
+ return generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.TRIGGER, {
+ appId: String(app.id),
+ })
}
const getRows = useCallback(
@@ -201,14 +205,14 @@ const DevtronAppList = ({
if (!isExpandedRow) {
const app = data as App
- push(redirectToAppDetails(app, app.defaultEnv.id))
+ navigate(redirectToAppDetails(app, app.defaultEnv.id))
return
}
const { app, id } = data as Environment & { app: App }
- push(redirectToAppDetails(app, id))
+ navigate(redirectToAppDetails(app, id))
}, [])
const columns = useMemo(() => getTableColumns(isArgoInstalled), [isArgoInstalled])
diff --git a/src/components/app/list/TriggerUrl.tsx b/src/components/app/list/TriggerUrl.tsx
index 0835849f3c..f2104ad12a 100644
--- a/src/components/app/list/TriggerUrl.tsx
+++ b/src/components/app/list/TriggerUrl.tsx
@@ -23,7 +23,7 @@ import {
EMPTY_STATE_STATUS,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
-import { ReactComponent as Close } from '../../../assets/icons/ic-close.svg'
+import Close from '../../../assets/icons/ic-close.svg?react'
import { getIngressServiceUrls } from '../service'
import { KIND } from '../../../config/constants'
import { getManifestUrlInfo } from '../../external-apps/ExternalAppService'
diff --git a/src/components/app/list/appList.modal.ts b/src/components/app/list/appList.modal.ts
index e1aaa51984..9114b6d01b 100644
--- a/src/components/app/list/appList.modal.ts
+++ b/src/components/app/list/appList.modal.ts
@@ -130,8 +130,20 @@ export const getDevtronAppListPayload = (
environmentList: EnvListMinDTO[],
namespaceList: EnvironmentListHelmResult[],
): AppListPayloadType => {
- const { searchKey, offset, pageSize, sortBy, sortOrder, appStatus, environment, cluster, namespace, project } =
- filterConfig
+ const {
+ searchKey,
+ offset,
+ pageSize,
+ sortBy,
+ sortOrder,
+ appStatus,
+ environment,
+ cluster,
+ namespace,
+ project,
+ labelSelectors,
+ } = filterConfig
+
return {
appNameSearch: searchKey.toLowerCase(),
offset,
@@ -150,5 +162,12 @@ export const getDevtronAppListPayload = (
}),
teams: project.map((team) => +team),
namespaces: namespace,
+ tagFilters: labelSelectors
+ .filter((s) => s.key?.trim())
+ .map((s) => ({
+ key: s.key.trim(),
+ operator: s.operator,
+ ...(s.value?.trim() ? { value: s.value.trim() } : {}),
+ })),
}
}
diff --git a/src/components/app/service.ts b/src/components/app/service.ts
index e900b41f9b..9b743fa001 100644
--- a/src/components/app/service.ts
+++ b/src/components/app/service.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { useEffect } from 'react'
import moment from 'moment'
import {
@@ -85,7 +86,8 @@ export const useGetDTAppDetails = ({ appId, envId }: UseGetDTAppDetailsParams):
error: appDetailsError,
refetch: refetchAppDetails,
status: appDetailsQueryStatus,
- } = useQuery({
+ dataUpdatedAt: appDetailsDataUpdatedAt,
+ } = useQuery({
queryKey: ['dt-app-details', appId, envId],
queryFn: ({ signal }) =>
get(getUrlWithSearchParams(`${Routes.APP_DETAIL}/v2`, { 'app-id': appId, 'env-id': envId }), {
@@ -93,31 +95,37 @@ export const useGetDTAppDetails = ({ appId, envId }: UseGetDTAppDetailsParams):
}),
select: ({ result }) => result,
enabled: !!appId && !!envId,
- refetchInterval: (data, query) =>
+ refetchInterval: (query) =>
// In case query failed and no data is available previously, stop polling and show error state
- !data && query.state.status === 'error' ? false : Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL),
- onSuccess: async (data) => {
- // Publish app details to IndexStore if resource tree is not to be fetched
- if (!data?.isPipelineTriggered && data?.releaseMode === ReleaseMode.NEW_DEPLOYMENT) {
- IndexStore.publishAppDetails(
- {
- ...data,
- },
- AppType.DEVTRON_APP,
- )
- }
-
- // Refetch resource tree to get latest data after app details fetch
- await queryClient.refetchQueries({ queryKey: [resourceTreeQueryKey, appId, envId] })
- },
+ !query.state.data && query.state.status === 'error'
+ ? false
+ : Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL),
})
+ useEffect(() => {
+ if (!appDetails) {
+ return
+ }
+
+ // Publish app details to IndexStore if resource tree is not to be fetched
+ if (!appDetails.isPipelineTriggered && appDetails.releaseMode === ReleaseMode.NEW_DEPLOYMENT) {
+ IndexStore.publishAppDetails({ ...appDetails }, AppType.DEVTRON_APP)
+ }
+
+ // Refetch resource tree to get latest data after app details fetch.
+ // NOTE: dependency is appDetailsDataUpdatedAt (not appDetails) so this fires on every
+ // successful poll even when data is structurally identical.
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ queryClient.refetchQueries({ queryKey: [resourceTreeQueryKey] })
+ }, [appDetailsDataUpdatedAt])
+
const {
data: resourceTree,
isFetching: isFetchingResourceTree,
error: resourceTreeError,
refetch: refetchResourceTree,
status: resourceTreeQueryStatus,
+ dataUpdatedAt: resourceTreeDataUpdatedAt,
} = useQuery({
queryKey: [resourceTreeQueryKey, appId, envId],
queryFn: ({ signal }) =>
@@ -131,18 +139,25 @@ export const useGetDTAppDetails = ({ appId, envId }: UseGetDTAppDetailsParams):
!!envId &&
!!appDetails &&
(appDetails.isPipelineTriggered || appDetails.releaseMode === ReleaseMode.MIGRATE_EXTERNAL_APPS), // Fetch resource tree for pipelines which are not triggered only in case of migrate external apps
- onSuccess: async (data) => {
- IndexStore.publishAppDetails(
- {
- ...appDetails,
- resourceTree: data,
- },
- AppType.DEVTRON_APP,
- )
- await queryClient.refetchQueries({ queryKey: [DEPLOYMENT_STATUS_QUERY_KEY] })
- },
})
+ useEffect(() => {
+ if (!resourceTree) {
+ return
+ }
+
+ IndexStore.publishAppDetails(
+ {
+ ...appDetails,
+ resourceTree,
+ },
+ AppType.DEVTRON_APP,
+ )
+
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ queryClient.refetchQueries({ queryKey: [DEPLOYMENT_STATUS_QUERY_KEY] })
+ }, [resourceTreeDataUpdatedAt])
+
// Returning appDetails, resourceTree as null if it doesn't exist, to form loading and error states properly
const mergedAppDetails: AppDetails = appDetails
? { ...appDetails, appType: AppType.DEVTRON_APP, resourceTree }
@@ -209,7 +224,11 @@ export const useGetDTAppDeploymentStatusDetail = (appId: string, envId: string,
}
},
enabled: enabled && !!appId && !!envId,
- refetchInterval: (data) => (data?.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS ? 10000 : false),
+ refetchInterval: (query) => {
+ const wfrStatus = query.state.data?.result?.wfrStatus
+ const deploymentStatus = WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP[wfrStatus] || DEPLOYMENT_STATUS.INPROGRESS
+ return deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS ? 10_000 : false
+ },
meta: {
showToastError: false,
},
diff --git a/src/components/bulkEdits/BulkEdits.tsx b/src/components/bulkEdits/BulkEdits.tsx
index 2488b5fb23..758186fc82 100644
--- a/src/components/bulkEdits/BulkEdits.tsx
+++ b/src/components/bulkEdits/BulkEdits.tsx
@@ -16,7 +16,7 @@
/* eslint-disable react/prop-types */
-import { Component, createRef } from 'react'
+import { Component, createRef, useRef } from 'react'
import Draggable, { DraggableEventHandler } from 'react-draggable'
import { useLocation } from 'react-router-dom'
import yamlJsParser from 'yaml'
@@ -42,6 +42,7 @@ import {
PageHeader,
Progressing,
ResponseType,
+ ROUTER_URLS,
SelectPicker,
SelectPickerVariantType,
showError,
@@ -285,7 +286,7 @@ class BulkEdits extends Component {
renderCodeEditorBody = () => {
const { codeEditorPayload, schema, activeOutputTab } = this.state
- const { gridTemplateRows } = this.props
+ const { gridTemplateRows, nodeRef } = this.props
const isV2Schema = (codeEditorPayload ?? '').match('apiVersion:\\s*batch/v1beta2')?.length
@@ -330,8 +331,12 @@ class BulkEdits extends Component {
position={{ x: 0, y: 0 }}
bounds={{ left: 0, right: 0 }}
onDrag={this.handleDrag}
+ nodeRef={nodeRef}
>
-
+
@@ -589,9 +594,12 @@ class BulkEdits extends Component
{
// eslint-disable-next-line class-methods-use-this
renderBreadcrumbs = () => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
const { pathname } = useLocation()
+ // eslint-disable-next-line react-hooks/rules-of-hooks
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.BULK_EDIT,
{
alias: {
...getApplicationManagementBreadcrumb(),
@@ -601,7 +609,7 @@ class BulkEdits extends Component {
[pathname],
)
- return
+ return
}
render() {
@@ -627,8 +635,9 @@ class BulkEdits extends Component {
const BulkEditsWithUseResizable = () => {
const outputHeightMV = useMotionValue(INITIAL_OUTPUT_PANEL_HEIGHT_PERCENTAGE)
const gridTemplateRows = useMotionTemplate`1fr 1px ${outputHeightMV}%`
+ const nodeRef = useRef(null)
- return
+ return
}
export default BulkEditsWithUseResizable
diff --git a/src/components/bulkEdits/bulkEdits.type.tsx b/src/components/bulkEdits/bulkEdits.type.tsx
index 7372799997..576060c4dd 100644
--- a/src/components/bulkEdits/bulkEdits.type.tsx
+++ b/src/components/bulkEdits/bulkEdits.type.tsx
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import { useRef } from 'react'
+
import { BulkEditVersion, OptionType, useMotionValue } from '@devtron-labs/devtron-fe-common-lib'
interface BaseOperationResponseType {
@@ -95,4 +97,5 @@ export interface OutputTabType {
export interface BulkEditsProps {
outputHeightMV: ReturnType>
gridTemplateRows: ReturnType>
+ nodeRef: ReturnType>
}
diff --git a/src/components/cdPipeline/BuildCD.tsx b/src/components/cdPipeline/BuildCD.tsx
index aaee618b28..4a991111af 100644
--- a/src/components/cdPipeline/BuildCD.tsx
+++ b/src/components/cdPipeline/BuildCD.tsx
@@ -33,14 +33,15 @@ import {
InfoBlock,
ButtonVariantType,
MODES,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { useParams, useHistory } from 'react-router-dom'
+import { useParams, useNavigate } from 'react-router-dom'
import yamlJsParser from 'yaml'
-import { ReactComponent as ICArrowRight } from '@Icons/ic-arrow-right.svg'
-import { ReactComponent as Add } from '@Icons/ic-add.svg'
-import { ReactComponent as Help } from '@Icons/ic-help.svg'
-import { ReactComponent as ICInfo } from '@Icons/ic-info-filled.svg'
-import { ReactComponent as ICHelpOutline } from '@Icons/ic-help-outline.svg'
+import ICArrowRight from '@Icons/ic-arrow-right.svg?react'
+import Add from '@Icons/ic-add.svg?react'
+import Help from '@Icons/ic-help.svg?react'
+import ICInfo from '@Icons/ic-info-filled.svg?react'
+import ICHelpOutline from '@Icons/ic-help-outline.svg?react'
import settings from '@Icons/ic-settings.svg'
import trash from '@Icons/misc/delete.svg'
import { ENV_ALREADY_EXIST_ERROR, RegistryPayloadWithSelectType, URLS, ViewType } from '../../config'
@@ -59,7 +60,6 @@ import { BuildCDProps } from './types'
import { MigrateToDevtron } from './MigrateToDevtron'
import TriggerTypeRadio from './TriggerTypeRadio'
import { DEPLOYMENT_APP_TYPE_LABEL } from './MigrateToDevtron/constants'
-import { MigrateToDevtronProps } from './MigrateToDevtron/types'
import { CDPipelineDeploymentAppType, SourceMaterialsSelector } from '@Pages/App/Configurations'
const HelmManifestPush = importComponentFromFELibrary('HelmManifestPush')
@@ -103,7 +103,7 @@ export default function BuildCD({
} = useContext(pipelineContext)
const validationRules = new ValidationRules()
- const history = useHistory()
+ const navigate = useNavigate()
const [gitopsConflictLoading, setGitopsConflictLoading] = useState(false)
let { cdPipelineId } = useParams<{
@@ -259,7 +259,7 @@ export default function BuildCD({
setGitopsConflictLoading(true)
getGitOpsRepoConfig(+appId)
.then(() => {
- history.push(`${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/edit/${URLS.APP_GITOPS_CONFIG}`)
+ navigate(`${(ROUTER_URLS.DEVTRON_APP_DETAILS.CONFIGURATIONS, { appId })}/${URLS.APP_GITOPS_CONFIG}`)
})
.catch((err) => {
if (err.code === 409) {
diff --git a/src/components/cdPipeline/CDPipeline.tsx b/src/components/cdPipeline/CDPipeline.tsx
index 7719f8f23f..641e9a8d4f 100644
--- a/src/components/cdPipeline/CDPipeline.tsx
+++ b/src/components/cdPipeline/CDPipeline.tsx
@@ -58,7 +58,7 @@ import {
handleAnalyticsEvent,
} from '@devtron-labs/devtron-fe-common-lib'
import { useEffect, useMemo, useRef, useState } from 'react'
-import { Redirect, Route, Switch, useParams, useRouteMatch } from 'react-router-dom'
+import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom'
import { CDDeploymentTabText, RegistryPayloadType, ViewType } from '../../config'
import { getPluginIdsFromBuildStage, importComponentFromFELibrary, sortObjectArrayAlphabetically } from '../common'
import BuildCD from './BuildCD'
@@ -121,7 +121,6 @@ const getDeploymentWindowProfileMetaData = importComponentFromFELibrary(
)
export default function CDPipeline({
- location,
appName,
close,
getWorkflows,
@@ -136,9 +135,10 @@ export default function CDPipeline({
isGitOpsInstalledButNotConfigured,
}: CDPipelineProps) {
const isCdPipeline = true
+ const location = useLocation()
const urlParams = new URLSearchParams(location.search)
const validationRules = new ValidationRules()
- const isWebhookCD = window.location.href.includes('webhook')
+ const isWebhookCD = location.pathname.includes('webhook')
const allStrategies = useRef<{ [key: string]: any }>({})
const noStrategyAvailable = useRef(false)
const addType = urlParams.get('addType')
@@ -223,7 +223,6 @@ export default function CDPipeline({
forceDeleteDialogMessage: '',
forceDeleteDialogTitle: '',
})
- const { path } = useRouteMatch()
const [pageState, setPageState] = useState(ViewType.LOADING)
const [isEnvUsedState, setIsEnvUsedState] = useState(false)
const [isVirtualEnvironment, setIsVirtualEnvironment] = useState()
@@ -1314,40 +1313,35 @@ export default function CDPipeline({
)}
-
- {isAdvanced && (
-
-
-
- )}
- {isAdvanced && (
-
-
-
- )}
-
-
-
-
-
+
+ {isAdvanced && } />}
+ {isAdvanced && } />}
+
+ }
+ />
+ } />
+
>
diff --git a/src/components/cdPipeline/MigrateToDevtron/MigrateToDevtronValidationFactory.tsx b/src/components/cdPipeline/MigrateToDevtron/MigrateToDevtronValidationFactory.tsx
index 971a220b83..e87d8e6d3b 100644
--- a/src/components/cdPipeline/MigrateToDevtron/MigrateToDevtronValidationFactory.tsx
+++ b/src/components/cdPipeline/MigrateToDevtron/MigrateToDevtronValidationFactory.tsx
@@ -28,14 +28,14 @@ import {
ImageWithFallback,
InfoBlock,
isNullOrUndefined,
+ ROUTER_URLS,
Tooltip,
- URLS as COMMON_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICArgoCDApp } from '@Icons/ic-argocd-app.svg'
-import { ReactComponent as ICArrowClockwise } from '@Icons/ic-arrow-clockwise.svg'
-import { ReactComponent as ICDefaultChart } from '@Icons/ic-default-chart.svg'
-import { ReactComponent as ICFluxCDApp } from '@Icons/ic-fluxcd-app.svg'
+import ICArgoCDApp from '@Icons/ic-argocd-app.svg?react'
+import ICArrowClockwise from '@Icons/ic-arrow-clockwise.svg?react'
+import ICDefaultChart from '@Icons/ic-default-chart.svg?react'
+import ICFluxCDApp from '@Icons/ic-fluxcd-app.svg?react'
import { URLS } from '@Config/routes'
import {
AddClusterFormPrefilledInfoType,
@@ -158,7 +158,7 @@ const MigrateToDevtronValidationFactory = ({
Chart version '{requiredChartVersion}' not found for '{requiredChartName}' chart.
(
GitOps credentials is required to deploy applications via GitOps.
> {
- match: any
- location: any
appName: any
close: any
getWorkflows: any
diff --git a/src/components/chartRepo/ChartRepo.tsx b/src/components/chartRepo/ChartRepo.tsx
index eac5ed8c78..df8cf6a7f9 100644
--- a/src/components/chartRepo/ChartRepo.tsx
+++ b/src/components/chartRepo/ChartRepo.tsx
@@ -35,13 +35,14 @@ import {
ComponentSizeType,
ERROR_STATUS_CODE,
DeleteConfirmationModal,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import { NavLink } from 'react-router-dom'
-import { ReactComponent as Add } from '@Icons/ic-add.svg'
-import { ReactComponent as Helm } from '@Icons/ic-helmchart.svg'
-import { ReactComponent as Trash } from '@Icons/ic-delete-interactive.svg'
-import { ReactComponent as ICHelpOutline } from '@Icons/ic-help-outline.svg'
+import Add from '@Icons/ic-add.svg?react'
+import Helm from '@Icons/ic-helmchart.svg?react'
+import Trash from '@Icons/ic-delete-interactive.svg?react'
+import ICHelpOutline from '@Icons/ic-help-outline.svg?react'
import { useForm } from '../common'
import { List } from '../globalConfigurations/GlobalConfiguration'
@@ -52,7 +53,7 @@ import {
deleteChartRepo,
} from './chartRepo.service'
import { getChartRepoList } from '../../services/service'
-import { PATTERNS, CHART_REPO_TYPE, CHART_REPO_AUTH_TYPE, CHART_REPO_LABEL, URLS, HEADER_TEXT } from '../../config'
+import { PATTERNS, CHART_REPO_TYPE, CHART_REPO_AUTH_TYPE, CHART_REPO_LABEL, HEADER_TEXT } from '../../config'
import { ValidateForm, VALIDATION_STATUS } from '../common/ValidateForm/ValidateForm'
import './chartRepo.scss'
import { DC_CHART_REPO_CONFIRMATION_MESSAGE, DeleteComponentsName } from '../../config/constantMessaging'
@@ -529,7 +530,7 @@ const ChartForm = ({
Looking to add OCI-based registry?
Add OCI Registry
diff --git a/src/components/charts/AdvancedConfig.tsx b/src/components/charts/AdvancedConfig.tsx
index 2b3e65ab78..059708f09c 100644
--- a/src/components/charts/AdvancedConfig.tsx
+++ b/src/components/charts/AdvancedConfig.tsx
@@ -25,14 +25,14 @@ import {
MarkDown,
MODES,
} from '@devtron-labs/devtron-fe-common-lib'
-import { useHistory } from 'react-router-dom'
+import { useNavigate } from 'react-router-dom'
import { Select, mapByKey, useKeyDown, Info, Pencil } from '../common'
import { getEnvironmentListMin } from '../../services/service'
import { ChartGroupEntry, AdvancedConfigHelpers, ChartValuesNativeType, ChartVersionType } from './charts.types'
import { getReadme, getChartValues } from './charts.service'
import { ValuesYamlConfirmDialog } from './dialogs/ValuesYamlConfirmDialog'
-import { ReactComponent as LockIcon } from '../../assets/icons/ic-locked.svg'
-import { ReactComponent as WarningIcon } from '../../assets/icons/ic-alert-triangle.svg'
+import LockIcon from '../../assets/icons/ic-locked.svg?react'
+import WarningIcon from '../../assets/icons/ic-alert-triangle.svg?react'
import { getSavedValuesListURL } from './charts.helper'
interface AdvancedConfigProps extends AdvancedConfigHelpers {
@@ -81,7 +81,7 @@ const AdvancedConfig: React.FC = ({
() => getReadme(appStoreApplicationVersionId),
[appStoreApplicationVersionId],
)
- const { push } = useHistory()
+ const navigate = useNavigate()
useEffect(() => {
async function getEnvironments() {
@@ -148,7 +148,7 @@ const AdvancedConfig: React.FC = ({
}
function openSavedValuesList() {
- push(getSavedValuesListURL(chart.id))
+ navigate(getSavedValuesListURL(chart.id))
}
let selectedChartValue: ChartValuesNativeType = {
diff --git a/src/components/charts/ChartGroupAdvanceDeploy.tsx b/src/components/charts/ChartGroupAdvanceDeploy.tsx
index 533ac723c8..73650ba0b3 100644
--- a/src/components/charts/ChartGroupAdvanceDeploy.tsx
+++ b/src/components/charts/ChartGroupAdvanceDeploy.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { useState, useEffect, useCallback } from 'react'
-import { useParams, useHistory, useLocation, useRouteMatch, Prompt } from 'react-router-dom'
+import { useState } from 'react'
+import { useParams, useNavigate, useLocation, generatePath } from 'react-router-dom'
import {
showError,
Progressing,
@@ -29,6 +29,8 @@ import {
getInfrastructureManagementBreadcrumb,
BreadcrumbText,
DOCUMENTATION,
+ ROUTER_URLS,
+ usePrompt,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import MultiChartSummary from './MultiChartSummary'
@@ -36,13 +38,15 @@ import useChartGroup from './useChartGroup'
import { Select, mapByKey } from '../common'
import AdvancedConfig from './AdvancedConfig'
import { deployChartGroup } from './charts.service'
-import { ReactComponent as WarningIcon } from '../../assets/icons/ic-alert-triangle.svg'
+import WarningIcon from '../../assets/icons/ic-alert-triangle.svg?react'
import { renderChartGroupDeploymentToastMessage } from './charts.helper'
import { getDeployableChartsFromConfiguredCharts } from './list/utils'
+const pagePathPattern = `${ROUTER_URLS.CHART_STORE}/group/:groupId/deploy`
+
export default function ChartGroupAdvanceDeploy() {
const { groupId } = useParams<{ groupId: string }>()
- const { push } = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
const [project, setProject] = useState({ id: null, error: '' })
const [installing, setInstalling] = useState(false)
@@ -63,6 +67,7 @@ export default function ChartGroupAdvanceDeploy() {
} = useChartGroup(groupId)
const projectsMap = mapByKey(state.projects, 'id')
const { breadcrumbs } = useBreadcrumb(
+ pagePathPattern,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
@@ -83,15 +88,22 @@ export default function ChartGroupAdvanceDeploy() {
)
const isLeavingPageAllowed = state.charts.every((chart) => chart.valuesYaml === chart.originalValuesYaml)
- const { url } = useRouteMatch()
const [deployed, setDeployed] = useState(false)
+ usePrompt({
+ shouldPrompt: !deployed && !isLeavingPageAllowed,
+ message: 'Your changes will be lost. Do you want to leave without deploying?',
+ })
+
useEffectAfterMount(() => {
if (state.loading) {
return
}
if (state.charts.length === 0) {
- push(url.replace('/deploy', ''))
+ navigate(
+ generatePath(`${ROUTER_URLS.CHART_STORE}/group/${groupId}`, { groupId }),
+ { replace: true },
+ )
}
configureChart((location?.state as any)?.configureChartIndex || 0)
}, [state.loading])
@@ -106,29 +118,12 @@ export default function ChartGroupAdvanceDeploy() {
}
}, [state.chartGroupDetailsLoading])
- const reloadCallback = useCallback(
- (event) => {
- event.preventDefault()
- if (!isLeavingPageAllowed) {
- event.returnValue = 'Your changes will be lost. Do you want to leave without deploying?'
- }
- },
- [isLeavingPageAllowed],
- )
-
- useEffect(() => {
- window.addEventListener('beforeunload', reloadCallback)
- return () => {
- window.removeEventListener('beforeunload', reloadCallback)
- }
- }, [reloadCallback])
-
useEffectAfterMount(() => {
// whenver deployment succeeds, go to deployments list
if (!deployed) {
return
}
- push(url.replace('/deploy', ''))
+ navigate(generatePath(`${ROUTER_URLS.CHART_STORE}/group/${groupId}`, { groupId }))
}, [deployed])
async function handleInstall() {
@@ -161,25 +156,15 @@ export default function ChartGroupAdvanceDeploy() {
const renderAdvanceBreadcrumb = () => {
return (
-
+
)
}
return (
-
+
- {!deployed && (
-
- )}
{state.loading &&
}
{!state.loading && (
diff --git a/src/components/charts/ChartGroupDeployments.tsx b/src/components/charts/ChartGroupDeployments.tsx
index df30de224f..8f06ec2a0b 100644
--- a/src/components/charts/ChartGroupDeployments.tsx
+++ b/src/components/charts/ChartGroupDeployments.tsx
@@ -15,19 +15,18 @@
*/
import React, { useState } from 'react'
-import { Link } from 'react-router-dom'
+import { generatePath, Link } from 'react-router-dom'
import {
not,
GenericEmptyState,
DeleteConfirmationModal,
- URLS as CommonURLS,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import moment from 'moment'
-import { ReactComponent as Delete } from '../../assets/icons/ic-delete.svg'
-import { ReactComponent as DownArrow } from '../../assets/icons/ic-chevron-down.svg'
+import Delete from '../../assets/icons/ic-delete.svg?react'
+import DownArrow from '../../assets/icons/ic-chevron-down.svg?react'
import NoDeploymentImg from '../../assets/img/app-not-configured.png'
import { InstalledChartGroup, InstalledChart, ChartGroupDeploymentsProps } from './charts.types'
-import { URLS } from '../../config'
import placeHolder from '../../assets/icons/ic-plc-chart.svg'
import { DeleteComponentsName, EMPTY_STATE_STATUS } from '../../config/constantMessaging'
import { ApplicationDeletionInfo } from '@Pages/Shared/ApplicationDeletionInfo/ApplicationDeletionInfo'
@@ -124,7 +123,10 @@ const CollapsibleDeployment: React.FC<{
{props.installedChartGroup.installedCharts.map((chart: InstalledChart, index) => {
return (
()
- const { push } = useHistory()
- const { url } = useRouteMatch()
+ const navigate = useNavigate()
const [projectId, setProjectId] = useState(null)
const [loading, setLoading] = useState(null)
const {
@@ -73,14 +74,15 @@ export default function ChartGroupDetails() {
setEnvironmentList,
} = useChartGroup(groupId)
const { breadcrumbs } = useBreadcrumb(
+ pagePathPattern,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
- 'chart-store': {
+ 'chart-store': null,
+ discover: {
component:
,
linked: true,
},
- 'discover': null,
group: 'Chart groups',
':groupId': {
component: (
@@ -90,6 +92,7 @@ export default function ChartGroupDetails() {
primaryValue="name"
matchedKeys={[]}
apiPrimaryKey="id"
+ path={pagePathPattern}
/>
),
linked: false,
@@ -108,15 +111,16 @@ export default function ChartGroupDetails() {
const [clickedOnAdvance, setClickedOnAdvance] = useState(null)
function handleAdvancedChart() {
- push(`${url}/deploy`, {
- charts: state.charts,
- configureChartIndex: state.charts.findIndex((chart) => chart.isEnabled),
+ navigate('deploy', {
+ state: {
+ charts: state.charts,
+ configureChartIndex: state.charts.findIndex((chart) => chart.isEnabled),
+ },
})
}
function redirectToConfigure() {
- const url = `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/group/${groupId}/edit`
- push(url)
+ navigate('edit')
}
async function deleteInstalledChartFromDeployments(installedAppId: number) {
@@ -156,7 +160,7 @@ export default function ChartGroupDetails() {
installedChartData: chartGroupInstalled?.result?.installedChartData,
}
await deleteChartGroup(payload)
- push(URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER)
+ navigate(ROUTER_URLS.CHART_STORE)
}
const renderDeleteComponent = () => {
@@ -229,7 +233,7 @@ export default function ChartGroupDetails() {
const renderBreadcrumbs = () => {
return (
-
+
)
}
@@ -267,10 +271,12 @@ export default function ChartGroupDetails() {
})
return
}
- push(`${url}/deploy`, {
- configureChartIndex: index,
- charts: state.charts,
- projectId,
+ navigate('deploy', {
+ state: {
+ configureChartIndex: index,
+ charts: state.charts,
+ projectId,
+ },
})
}}
handleChartValueChange={handleChartValueChange}
@@ -344,7 +350,7 @@ export default function ChartGroupDetails() {
handleEnvironmentChangeOfAllCharts={handleEnvironmentChangeOfAllCharts}
redirectToAdvancedOptions={() => {
toggleDeployModal(false)
- push(`${url}/deploy`, { charts: state.charts, projectId })
+ navigate('deploy', { state: { charts: state.charts, projectId } })
}}
setEnvironments={setEnvironmentList}
/>
diff --git a/src/components/charts/ChartGroupUpdate.tsx b/src/components/charts/ChartGroupUpdate.tsx
index 8bc0d28f1f..b1294164a7 100644
--- a/src/components/charts/ChartGroupUpdate.tsx
+++ b/src/components/charts/ChartGroupUpdate.tsx
@@ -15,7 +15,7 @@
*/
import { useState, useEffect, useRef, useMemo } from 'react'
-import { useParams, useRouteMatch, useHistory, useLocation, Prompt } from 'react-router-dom'
+import { useParams, useLocation, useNavigate } from 'react-router-dom'
import {
showError,
Progressing,
@@ -28,6 +28,8 @@ import {
getInfrastructureManagementBreadcrumb,
BreadcrumbText,
DOCUMENTATION,
+ ROUTER_URLS,
+ usePrompt,
} from '@devtron-labs/devtron-fe-common-lib'
import ChartCard from './ChartCard'
import { ChartGroupEntry, Chart, ChartListType } from './charts.types'
@@ -36,17 +38,17 @@ import AdvancedConfig from './AdvancedConfig'
import { updateChartGroupEntries, getChartProviderList } from './charts.service'
import useChartGroup from './useChartGroup'
import CreateChartGroup from './modal/CreateChartGroup'
-import { URLS } from '../../config'
-import { ReactComponent as SaveIcon } from '../../assets/icons/ic-save.svg'
+import SaveIcon from '../../assets/icons/ic-save.svg?react'
import ChartHeaderFilters from './ChartHeaderFilters'
import { QueryParams } from './constants'
import ChartEmptyState from '../common/emptyState/ChartEmptyState'
import { sortOptionsByLabel } from '../common'
+const pagePathPattern = `${ROUTER_URLS.CHART_STORE}/group/:groupId/edit`
+
export default function ChartGroupUpdate({}) {
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
- const match = useRouteMatch()
const { groupId } = useParams<{ groupId }>()
const [chartDetailsUpdate, setChartDetailsUpdate] = useState(false)
const [loading, setLoading] = useState(false)
@@ -74,7 +76,6 @@ export default function ChartGroupUpdate({}) {
const [appStoreName, setAppStoreName] = useState('')
const [searchApplied, setSearchApplied] = useState(false)
const [includeDeprecated, setIncludeDeprecated] = useState(0)
- const { url } = match
const [chartListLoading, setChartListLoading] = useState(true)
const chartList: Chart[] = Array.from(state.availableCharts.values())
const [chartLists, setChartLists] = useState
([])
@@ -82,6 +83,7 @@ export default function ChartGroupUpdate({}) {
const [isGrid, setIsGrid] = useState(true)
const { breadcrumbs } = useBreadcrumb(
+ pagePathPattern,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
@@ -166,12 +168,13 @@ export default function ChartGroupUpdate({}) {
useEffect(() => {
getChartFilter()
- window.addEventListener('beforeunload', reloadCallback)
- return () => {
- window.removeEventListener('beforeunload', reloadCallback)
- }
}, [])
+ usePrompt({
+ shouldPrompt: isLeavingPageNotAllowed.current,
+ message: 'Your changes will be lost. Do you want to leave without saving?',
+ })
+
useEffect(() => {
if (!state.loading) {
resetPaginationOffset()
@@ -180,16 +183,8 @@ export default function ChartGroupUpdate({}) {
}
}, [chartRepos, location.search, state.loading])
- function reloadCallback(event): void {
- event.preventDefault()
- if (isLeavingPageNotAllowed.current) {
- event.returnValue = 'Your changes will be lost. Do you want to leave without deploying?'
- }
- }
-
function redirectToGroupDetail(): void {
- const url = `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/group/${groupId}`
- history.push(url)
+ navigate(`${ROUTER_URLS.CHART_STORE}/group/${groupId}`)
}
function closeChartGroupModal(props): void {
@@ -265,7 +260,7 @@ export default function ChartGroupUpdate({}) {
await applyFilterOnCharts(location.search, false)
}
- const renderBreadcrumbs = () =>
+ const renderBreadcrumbs = () =>
const renderChartGroupEditActionButton = () => {
return (
@@ -288,7 +283,11 @@ export default function ChartGroupUpdate({}) {
}
function handleViewAllCharts(): void {
- history.push(`${url}?${QueryParams.IncludeDeprecated}=1`)
+ const newSearch = new URLSearchParams(location.search)
+ newSearch.append(QueryParams.IncludeDeprecated, '1')
+ navigate({
+ search: newSearch.toString()
+ })
}
return (
@@ -300,10 +299,6 @@ export default function ChartGroupUpdate({}) {
renderActionButtons={renderChartGroupEditActionButton}
docPath={DOCUMENTATION.INFRA_MANAGEMENT}
/>
-
{!state.loading ? (
@@ -379,9 +374,9 @@ export default function ChartGroupUpdate({}) {
{chartDetailsUpdate && (
{
- const match = useRouteMatch()
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
- const { url } = match
const handleSelection = (event): void => {
const chartRepoList = selectedChartRepo.filter((e) => e.value != event.value)
@@ -93,7 +90,9 @@ const ChartHeaderFilter = ({
if (chartCategoryIdsCsv) {
qsr = `${qsr}&${QueryParams.ChartCategoryId}=${chartCategoryIdsCsv}`
}
- history.push(`${url}?${qsr}`)
+ navigate({
+ search: qsr,
+ })
} else {
let qs = `${QueryParams.ChartRepoId}=${paramsChartRepoIds}`
if (paramsRegistryIds) {
@@ -108,7 +107,9 @@ const ChartHeaderFilter = ({
if (chartCategoryIdsCsv) {
qs = `${qs}&${QueryParams.ChartCategoryId}=${chartCategoryIdsCsv}`
}
- history.push(`${url}?${qs}`)
+ navigate({
+ search: qs,
+ })
}
}
@@ -127,7 +128,9 @@ const ChartHeaderFilter = ({
if (deprecate) {
qs = `${qs}&${QueryParams.IncludeDeprecated}=${deprecate}`
}
- history.push(`${url}?${qs}`)
+ navigate({
+ search: qs,
+ })
}
if (key === CHART_KEYS.DEPRECATED) {
@@ -144,7 +147,9 @@ const ChartHeaderFilter = ({
if (chartCategoryIdsCsv) {
qs = `${qs}&${QueryParams.ChartCategoryId}=${chartCategoryIdsCsv}`
}
- history.push(`${url}?${qs}`)
+ navigate({
+ search: qs,
+ })
}
if (key === CHART_KEYS.SEARCH) {
@@ -161,7 +166,9 @@ const ChartHeaderFilter = ({
if (chartCategoryIdsCsv) {
qs = `${qs}&${QueryParams.ChartCategoryId}=${chartCategoryIdsCsv}`
}
- history.push(`${url}?${qs}`)
+ navigate({
+ search: qs,
+ })
}
}
diff --git a/src/components/charts/ChartSourceAccordion.tsx b/src/components/charts/ChartSourceAccordion.tsx
index 4005f4ce04..1ce5b4dc0f 100644
--- a/src/components/charts/ChartSourceAccordion.tsx
+++ b/src/components/charts/ChartSourceAccordion.tsx
@@ -18,7 +18,7 @@ import { useState } from 'react'
import { Checkbox, CHECKBOX_VALUE } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Dropdown } from '@Icons/ic-chevron-down.svg'
+import Dropdown from '@Icons/ic-chevron-down.svg?react'
import AddChartSource from '@Components/charts/list/AddChartSource'
import { ChartSourceAccordionProps, SelectedChartRepositoryType } from './charts.types'
diff --git a/src/components/charts/Charts.tsx b/src/components/charts/Charts.tsx
index c6d198212f..b0635f3414 100644
--- a/src/components/charts/Charts.tsx
+++ b/src/components/charts/Charts.tsx
@@ -14,54 +14,46 @@
* limitations under the License.
*/
+import { PropsWithChildren } from 'react'
+
+import { TabGroup } from '@devtron-labs/devtron-fe-common-lib'
+
import DiscoverCharts from './list/DiscoverCharts'
+
import './list/list.scss'
import '../app/details/appDetails/appDetails.scss'
import './charts.scss'
-import { TabGroup } from '@devtron-labs/devtron-fe-common-lib'
-import { Route, Switch, Redirect, useRouteMatch } from 'react-router-dom'
-export default function Charts({ isSuperAdmin }: { isSuperAdmin: boolean }) {
- const { path } = useRouteMatch()
+const Charts = ({ isSuperAdmin }: { isSuperAdmin: boolean }) =>
- return (
-
- } />
-
-
- )
-}
+export default Charts
-export const GenericChartsHeader = ({ children = null }) => {
- return {children}
-}
+export const GenericChartsHeader = ({ children = null }: PropsWithChildren<{}>) => (
+ {children}
+)
-export const ChartDetailNavigator = () => {
- return (
- (
+
- )
-}
+ },
+ ]}
+ />
+)
-export const HeaderTitle = ({ children = null }) => {
- return {children}
-}
+export const HeaderTitle = ({ children = null }: PropsWithChildren<{}>) => (
+ {children}
+)
-export const HeaderSubtitle = ({ children = null }) => {
- return {children}
-}
+export const HeaderSubtitle = ({ children = null }: PropsWithChildren<{}>) => {children}
-export const HeaderButtonGroup = ({ children = null }) => {
- return {children}
-}
+export const HeaderButtonGroup = ({ children = null }: PropsWithChildren<{}>) => (
+ {children}
+)
diff --git a/src/components/charts/MultiChartSummary.tsx b/src/components/charts/MultiChartSummary.tsx
index 6ad63958f8..20bf84a499 100644
--- a/src/components/charts/MultiChartSummary.tsx
+++ b/src/components/charts/MultiChartSummary.tsx
@@ -19,11 +19,10 @@ import { DTSwitch, DTSwitchProps, getUniqueId, noop } from '@devtron-labs/devtro
import { Select, Pencil } from '../common'
import placeHolder from '../../assets/icons/ic-plc-chart.svg'
import { ChartGroupEntry, ChartValuesNativeType, ChartVersionType, MultiChartSummaryProps } from './charts.types'
-import { ReactComponent as Trash } from '../../assets/icons/ic-delete.svg'
-import { ReactComponent as Warning } from '../../assets/icons/ic-warning.svg'
-import { ReactComponent as EmptyFolder } from '../../assets/icons/img-folder-empty.svg'
-import DropDownFilled from '../../assets/icons/ic-dropdown-filled.svg'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Trash from '../../assets/icons/ic-delete.svg?react'
+import EmptyFolder from '../../assets/icons/img-folder-empty.svg?react'
+import DropDownFilled from '../../assets/icons/ic-dropdown-filled.svg?react'
+import Add from '../../assets/icons/ic-add.svg?react'
const MultiChartSummary: React.FC = ({
charts,
diff --git a/src/components/charts/chartValues/ChartValues.tsx b/src/components/charts/chartValues/ChartValues.tsx
index bbbf8a42e8..f406bdb0db 100644
--- a/src/components/charts/chartValues/ChartValues.tsx
+++ b/src/components/charts/chartValues/ChartValues.tsx
@@ -26,6 +26,7 @@ import {
getInfrastructureManagementBreadcrumb,
BreadcrumbText,
DOCUMENTATION,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import { getChartValuesCategorizedListParsed, getChartVersionDetails, getChartVersionsMin } from '../charts.service'
import ChartValuesView from '../../v2/values/chartValuesDiff/ChartValuesView'
@@ -79,15 +80,11 @@ export default function ChartValues() {
}
useEffect(() => {
- let id
- let kind
- if (chartValueId !== '0') {
- id = parseInt(chartValueId)
- kind = ChartKind.TEMPLATE
- } else {
- id = availableVersions[0]?.id
- kind = ChartKind.DEFAULT
- }
+ const [id, kind] =
+ chartValueId !== '0'
+ ? [parseInt(chartValueId), ChartKind.TEMPLATE]
+ : [availableVersions[0]?.id, ChartKind.DEFAULT]
+
if (id) {
const chartValues = chartValuesList.find((chrtValue) => {
if (chrtValue.kind === kind && chrtValue.id === id) {
@@ -109,7 +106,7 @@ export default function ChartValues() {
}
}, [availableVersions, chartValuesList])
- if (loader) {
+ if (loader || !chartVersionId) {
return
}
if (errorStatusCode > 0) {
@@ -134,16 +131,19 @@ export default function ChartValues() {
)
}
+const pagePathPattern = `${ROUTER_URLS.CHART_STORE}${URLS.CHART}/:chartId${URLS.PRESET_VALUES}/:chartValueId`
+
const Header = ({ appStoreApplicationName, name }) => {
const { breadcrumbs } = useBreadcrumb(
+ pagePathPattern,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
- 'chart-store': {
- component: ,
+ 'chart-store': null,
+ discover: {
+ component: ,
linked: true,
},
- discover: null,
':chartId': appStoreApplicationName || null,
chart: null,
':chartValueId': { component: name || 'New value', linked: false },
@@ -157,7 +157,10 @@ const Header = ({ appStoreApplicationName, name }) => {
() =>
breadcrumbs.map((item) =>
item.name === 'Preset values'
- ? { ...item, to: `${item.to.replace(URLS.PRESET_VALUES, '')}?tab=${ChartDetailsSegment.PRESET_VALUES}` }
+ ? {
+ ...item,
+ to: `${item.to.replace(URLS.PRESET_VALUES, '')}?tab=${ChartDetailsSegment.PRESET_VALUES}`,
+ }
: item,
),
[breadcrumbs],
@@ -166,9 +169,11 @@ const Header = ({ appStoreApplicationName, name }) => {
const renderChartValueBreadcrumbs = () => {
return (
-
+
)
}
- return
+ return (
+
+ )
}
diff --git a/src/components/charts/charts.helper.ts b/src/components/charts/charts.helper.ts
index fe4d826733..519bda8893 100644
--- a/src/components/charts/charts.helper.ts
+++ b/src/components/charts/charts.helper.ts
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-import { ToastManager, ToastVariantType, versionComparatorBySortOrder } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ ROUTER_URLS,
+ ToastManager,
+ ToastVariantType,
+ versionComparatorBySortOrder,
+} from '@devtron-labs/devtron-fe-common-lib'
import { ChartDetailsSegment } from '@Pages/ChartStore/ChartDetails/types'
-import { URLS } from '../../config'
import { ChartGroupDeployResponse, ChartValuesType } from './charts.types'
export const MultiChartSummaryView = {
@@ -28,26 +32,26 @@ export const MultiChartSummaryView = {
}
export function getDiscoverChartDetailsURL(chartId: string | number) {
- return `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/chart/${chartId}`
+ return `${ROUTER_URLS.CHART_STORE}/chart/${chartId}`
}
export function getSavedValuesListURL(chartId): string {
- return `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/chart/${chartId}?tab=${ChartDetailsSegment.PRESET_VALUES}`
+ return `${ROUTER_URLS.CHART_STORE}/chart/${chartId}?tab=${ChartDetailsSegment.PRESET_VALUES}`
}
export function getChartValuesURL(chartId: number | string, chartValueId?: number | string): string {
- return `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/chart/${chartId}/preset-values/${chartValueId || 0}`
+ return `${ROUTER_URLS.CHART_STORE}/chart/${chartId}/preset-values/${chartValueId || 0}`
}
export function getChartGroupURL(chartGroupId: number | string | null) {
if (chartGroupId) {
- return `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/group/${chartGroupId}`
+ return `${ROUTER_URLS.CHART_STORE}/group/${chartGroupId}`
}
- return `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE}/group`
+ return `${ROUTER_URLS.CHART_STORE}/group`
}
export function getChartGroupEditURL(chartGroupId: number | string) {
- return `${URLS.INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER}/group/${chartGroupId}/edit`
+ return `${ROUTER_URLS.CHART_STORE}/group/${chartGroupId}/edit`
}
export function getChartValuesFiltered(chartValuesList: ChartValuesType[]): {
diff --git a/src/components/charts/charts.types.tsx b/src/components/charts/charts.types.tsx
index 77e35a3ce9..2afd6db1e8 100644
--- a/src/components/charts/charts.types.tsx
+++ b/src/components/charts/charts.types.tsx
@@ -15,9 +15,8 @@
*/
import { Dispatch, SetStateAction } from 'react'
-import { RouteComponentProps } from 'react-router-dom'
-import { ActionMenuProps, OptionType, ResponseType } from '@devtron-labs/devtron-fe-common-lib'
+import { ActionMenuProps, OptionType, ResponseType, RouterV5Props } from '@devtron-labs/devtron-fe-common-lib'
export interface ChartGroupCardProps {
chartGroup: ChartGroup
@@ -70,9 +69,9 @@ export interface HeaderProps {
tabs: [{ key: string; isActive: boolean }]
}
-export interface ChartsProps extends RouteComponentProps {}
+export interface ChartsProps extends RouterV5Props<{}> {}
-export interface DeployedChartProps extends RouteComponentProps<{}> {}
+export interface DeployedChartProps extends RouterV5Props<{}> {}
export interface DeployedChartState {
code: number
@@ -137,7 +136,7 @@ export interface ChartVersionType {
version: string
}
-export interface ChartValuesProps extends RouteComponentProps<{ chartId: string; chartValueId?: string }> {}
+export interface ChartValuesProps extends RouterV5Props<{ chartId: string; chartValueId?: string }> {}
export interface ChartValuesState {
view: string
@@ -168,7 +167,7 @@ export interface ChartEntryIdType {
index: number
}
-export interface ChartGroupEditProps extends RouteComponentProps<{ chartGroupId?: string }> {
+export interface ChartGroupEditProps extends RouterV5Props<{ chartGroupId?: string }> {
view: string
getChartGroup: () => void
getChartEntryChartValues: (...args) => Promise
@@ -179,7 +178,7 @@ interface ChartGroupCreate {
name: string
description?: string
}
-export interface CreateChartGroupProps extends RouteComponentProps<{}> {
+export interface CreateChartGroupProps extends RouterV5Props<{}> {
closeChartGroupModal: (props: ChartGroupCreate | null) => void
chartGroupId?: number
name?: string
@@ -235,7 +234,7 @@ export interface ChartGroupListProps {
chartGroups: ChartGroup[]
}
-export interface DiscoverChartsContainerProps extends RouteComponentProps<{}> {}
+export interface DiscoverChartsContainerProps extends RouterV5Props<{}> {}
export interface ChartGroupProviderProps {}
diff --git a/src/components/charts/charts.util.tsx b/src/components/charts/charts.util.tsx
index 6237f951e0..2450ba16b5 100644
--- a/src/components/charts/charts.util.tsx
+++ b/src/components/charts/charts.util.tsx
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
import { Icon } from '@devtron-labs/devtron-fe-common-lib'
import { getAvailableCharts } from '@Services/service'
import { CHART_LIST_SELECT_QUERY } from './constants'
diff --git a/src/components/charts/constants.ts b/src/components/charts/constants.ts
index 16f8115cdf..3a241b742b 100644
--- a/src/components/charts/constants.ts
+++ b/src/components/charts/constants.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { URLS } from '@Config/routes'
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
import { AddSourceMenuOptions, CHART_SOURCE_TYPE } from './charts.types'
@@ -62,13 +62,13 @@ export const getAddSourceActionMenuOptions = (): AddSourceMenuOptions['options']
id: CHART_SOURCE_TYPE.CHART_REPO,
label: 'Add Chart Repository',
componentType: 'link',
- to: URLS.GLOBAL_CONFIG_CHART_REPO,
+ to: ROUTER_URLS.GLOBAL_CONFIG_CHART_REPOSITORIES,
},
{
id: CHART_SOURCE_TYPE.OCI,
label: 'Add OCI Registry',
componentType: 'link',
- to: `${URLS.GLOBAL_CONFIG_DOCKER}/0`,
+ to: `${ROUTER_URLS.GLOBAL_CONFIG_DOCKER}/0`,
},
],
},
diff --git a/src/components/charts/dialogs/ValuesYamlConfirmDialog.tsx b/src/components/charts/dialogs/ValuesYamlConfirmDialog.tsx
index c50625df74..c1f264c9e9 100644
--- a/src/components/charts/dialogs/ValuesYamlConfirmDialog.tsx
+++ b/src/components/charts/dialogs/ValuesYamlConfirmDialog.tsx
@@ -16,7 +16,7 @@
import React, { Component } from 'react'
import { VisibleModal } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Warn } from '../../../assets/icons/ic-warning.svg'
+import Warn from '../../../assets/icons/ic-warning.svg?react'
import close from '../../../assets/icons/ic-close.svg'
export class ValuesYamlConfirmDialog extends Component<{
diff --git a/src/components/charts/list/ChartGroup.tsx b/src/components/charts/list/ChartGroup.tsx
index b4ba90447b..bfe7f50e64 100644
--- a/src/components/charts/list/ChartGroup.tsx
+++ b/src/components/charts/list/ChartGroup.tsx
@@ -23,20 +23,24 @@ import {
PageHeader,
getInfrastructureManagementBreadcrumb,
DOCUMENTATION,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { useRouteMatch, useHistory, useLocation, Switch, Route, Link } from 'react-router-dom'
+import { useNavigate, useLocation, Route, Link, Routes } from 'react-router-dom'
import { getChartGroups } from '../charts.service'
import { ChartGroupCard } from '../ChartGroupCard'
import CreateChartGroup from '../modal/CreateChartGroup'
import ChartGroupUpdate from '../ChartGroupUpdate'
import ChartGroupDetails from '../ChartGroupDetails'
import ChartGroupAdvanceDeploy from '../ChartGroupAdvanceDeploy'
-import { ReactComponent as Add } from '../../../assets/icons/ic-add.svg'
+import Add from '../../../assets/icons/ic-add.svg?react'
+
+const pagePathPattern = `${ROUTER_URLS.CHART_STORE}/group`
const ChartGroupList = () => {
const [loading, result] = useAsync(getChartGroups, [])
const { breadcrumbs } = useBreadcrumb(
+ pagePathPattern,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
@@ -50,13 +54,11 @@ const ChartGroupList = () => {
},
[],
)
- const match = useRouteMatch()
- const { url } = match
const renderBreadcrumbs = () => {
return (
-
+
)
}
@@ -64,7 +66,7 @@ const ChartGroupList = () => {
const renderCreateGroupButton = () => {
return (
@@ -85,7 +87,9 @@ const ChartGroupList = () => {
{result?.result?.groups
?.sort((a, b) => a.name.localeCompare(b.name))
- .map((chartGroup) => )}
+ .map((chartGroup) => (
+
+ ))}
)}
@@ -94,27 +98,28 @@ const ChartGroupList = () => {
}
export default function ChartGroupRouter() {
- const history = useHistory()
+ const navigate = useNavigate()
const location = useLocation()
- const match = useRouteMatch()
- const { url, path } = match
return (
-
-
-
- history.push(url)}
- />
-
-
-
-
-
-
-
-
+
+
+
+ navigate(-1)}
+ />
+ >
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+
)
}
diff --git a/src/components/charts/list/ChartListPopUpRow.tsx b/src/components/charts/list/ChartListPopUpRow.tsx
index 7bd160afb6..53ba84d818 100644
--- a/src/components/charts/list/ChartListPopUpRow.tsx
+++ b/src/components/charts/list/ChartListPopUpRow.tsx
@@ -30,7 +30,7 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'
import { List } from '../../globalConfigurations/GlobalConfiguration'
import { updateChartProviderList, updateSyncSpecificChart } from '../charts.service'
-import { ReactComponent as Helm } from '../../../assets/icons/ic-helmchart.svg'
+import Helm from '../../../assets/icons/ic-helmchart.svg?react'
import { ChartListType } from '../charts.types'
import { getNonEditableChartRepoText } from '../../common'
import { TOAST_INFO } from '../../../config/constantMessaging'
diff --git a/src/components/charts/list/ChartsList.tsx b/src/components/charts/list/ChartsList.tsx
index d37ed26ac7..3fc7752270 100644
--- a/src/components/charts/list/ChartsList.tsx
+++ b/src/components/charts/list/ChartsList.tsx
@@ -14,6 +14,7 @@ import {
InfoBlock,
Popover,
Progressing,
+ ROUTER_URLS,
SearchBar,
ToastManager,
ToastVariantType,
@@ -23,7 +24,6 @@ import {
import { reSyncChartRepo } from '@Components/chartRepo/chartRepo.service'
import { TOAST_INFO } from '@Config/constantMessaging'
-import { URLS } from '@Config/routes'
import AddChartSource from './AddChartSource'
import ChartListPopUpRow from './ChartListPopUpRow'
@@ -115,9 +115,9 @@ export const ChartsList = ({ chartsList, isLoading }: ChartsListProps) => {
subTitle={
Add a
- Chart repositories
+ Chart repositories
or
- OCI Registries
+ OCI Registries
to view and deploy helm charts.
}
@@ -173,9 +173,11 @@ export const ChartsList = ({ chartsList, isLoading }: ChartsListProps) => {
Showing Chart repositories and OCI Registries (used as chart repositories).
You can add other
- Chart repositories
+
+ Chart repositories
+
or
- OCI Registries
+ OCI Registries
as chart sources.
}
diff --git a/src/components/charts/list/DiscoverCharts.tsx b/src/components/charts/list/DiscoverCharts.tsx
index 9d781a4c98..7a50468a7f 100644
--- a/src/components/charts/list/DiscoverCharts.tsx
+++ b/src/components/charts/list/DiscoverCharts.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import React, { useEffect, useMemo, useRef, useState } from 'react'
-import { Prompt, Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom'
+import { Dispatch, type JSX, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'
+import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
import Tippy from '@tippyjs/react'
import {
@@ -32,21 +32,21 @@ import {
InfoIconTippy,
PageHeader,
Progressing,
+ ROUTER_URLS,
showError,
stringComparatorBySortOrder,
ToastManager,
ToastVariantType,
useBreadcrumb,
useMainContext,
+ usePrompt,
useQuery,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ChartDetails } from '@Pages/ChartStore'
-
-import { ReactComponent as Add } from '../../../assets/icons/ic-add.svg'
-import { ReactComponent as WarningIcon } from '../../../assets/icons/ic-alert-triangle.svg'
-import { ReactComponent as Next } from '../../../assets/icons/ic-arrow-forward.svg'
-import { ReactComponent as BackIcon } from '../../../assets/icons/ic-back.svg'
+import Add from '../../../assets/icons/ic-add.svg?react'
+import WarningIcon from '../../../assets/icons/ic-alert-triangle.svg?react'
+import Next from '../../../assets/icons/ic-arrow-forward.svg?react'
+import BackIcon from '../../../assets/icons/ic-back.svg?react'
import empty from '../../../assets/img/ic-empty-chartgroup@2x.png'
import { SERVER_MODE, URLS } from '../../../config'
import { isGitOpsModuleInstalledAndConfigured } from '../../../services/service'
@@ -69,13 +69,12 @@ import ChartCardSkeletonRow from './ChartCardSkeleton'
import ChartGroupRouter from './ChartGroup'
import { ChartsList } from './ChartsList'
import { CHART_STORE_TIPPY_CONTENT, getDeployableChartsFromConfiguredCharts } from './utils'
+import { ChartDetailsWithKey } from '@Pages/ChartStore'
const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
const { serverMode } = useMainContext()
const location = useLocation()
- const history = useHistory()
- const match = useRouteMatch()
- const { url } = match
+ const navigate = useNavigate()
const {
state,
configureChart,
@@ -124,6 +123,11 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
(chart: ChartGroupEntry) => chart.originalValuesYaml === chart.valuesYaml,
)
+ usePrompt({
+ shouldPrompt: isLeavingPageNotAllowed.current,
+ message: 'Your changes will be lost. Do you want to leave without deploying?',
+ })
+
const { isLoading, data: chartsList } = useQuery({
queryKey: ['chartsList'],
queryFn: getChartProviderList,
@@ -220,7 +224,7 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
description: 'Deployment initiated',
})
setInstalling(false)
- history.push(URLS.HELM_APP_LIST)
+ navigate(ROUTER_URLS.INFRASTRUCTURE_MANAGEMENT_APP_LIST.HELM)
} catch (err) {
showError(err)
} finally {
@@ -301,7 +305,7 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
}
function handleViewAllCharts(): void {
- history.push(URLS.GLOBAL_CONFIG_CHART_REPO)
+ navigate(ROUTER_URLS.GLOBAL_CONFIG_CHART_REPOSITORIES)
}
function renderCreateGroupButton() {
@@ -323,6 +327,7 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
}
const { breadcrumbs } = useBreadcrumb(
+ ROUTER_URLS.CHART_STORE,
{
alias: {
...getInfrastructureManagementBreadcrumb(),
@@ -362,7 +367,7 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
)
}
- return
+ return
}
async function reloadNextAfterBottom() {
@@ -370,7 +375,7 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
}
const clearSearch = (): void => {
- history.push(url)
+ navigate(ROUTER_URLS.CHART_STORE)
}
const renderChartStoreEmptyState = (): JSX.Element =>
@@ -389,13 +394,12 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
<>
0 ? 'summary-show' : ''}`}>
0} wrap={(children) => {children}
}>
-
+
-
-
{!state.loading ? (
{typeof state.configureChartIndex !== 'number' && chartRepos?.length > 0 && (
@@ -485,8 +489,8 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
subtractChart={subtractChart}
onClick={(chartId) =>
state.charts.length === 0
- ? history.push(
- `${url}/chart/${chart.id}`,
+ ? navigate(
+ `${ROUTER_URLS.CHART_STORE}/chart/${chart.id}`,
)
: selectChart(chartId)
}
@@ -644,9 +648,9 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
{showChartGroupModal ? (
{
toggleChartGroupModal(!showChartGroupModal)
}}
@@ -659,24 +663,13 @@ const DiscoverChartList = ({ isSuperAdmin }: { isSuperAdmin: boolean }) => {
}
export default function DiscoverCharts({ isSuperAdmin }: { isSuperAdmin: boolean }) {
- const match = useRouteMatch()
- const { path } = match
-
return (
-
-
-
-
-
-
-
-
- {({ match: { params } }) => }
-
-
-
-
-
+
+ } />
+ } />
+ } />
+ } />
+
)
}
@@ -762,12 +755,11 @@ export const ChartGroupListMin = ({
}: {
chartGroups
showChartGroupModal?: boolean
- toggleChartGroupModal?: React.Dispatch>
+ toggleChartGroupModal?: Dispatch>
isGrid?: boolean
renderCreateGroupButton?: () => JSX.Element
}) => {
- const history = useHistory()
- const match = useRouteMatch()
+ const navigate = useNavigate()
if (chartGroups.length == 0) {
return (
@@ -775,7 +767,7 @@ export const ChartGroupListMin = ({
}
const redirectToGroup = () => {
- history.push(`${match.url}/group`)
+ navigate(`${ROUTER_URLS.CHART_STORE}/group`)
handleAnalyticsEvent({ category: 'Chart Store', action: 'CS_ALL_CHART_GROUPS' })
}
@@ -805,7 +797,9 @@ export const ChartGroupListMin = ({
- {chartGroups?.map((chartGroup, idx) => )}
+ {chartGroups?.map((chartGroup, idx) => (
+
+ ))}
)
diff --git a/src/components/charts/modal/ChartGroupBasicDeploy.tsx b/src/components/charts/modal/ChartGroupBasicDeploy.tsx
index 125dd7a6b0..e4eb102688 100644
--- a/src/components/charts/modal/ChartGroupBasicDeploy.tsx
+++ b/src/components/charts/modal/ChartGroupBasicDeploy.tsx
@@ -17,11 +17,10 @@
import { Component } from 'react'
import { ComponentSizeType, CustomInput, DialogForm, DialogFormSubmit, SelectPicker, showError, DEFAULT_ROUTE_PROMPT_MESSAGE, handleAnalyticsEvent } from '@devtron-labs/devtron-fe-common-lib'
import { ProjectType, ChartGroupEntry, EnvironmentType } from '../charts.types'
-import { ReactComponent as Edit } from '../../../assets/icons/ic-edit.svg'
-import { ReactComponent as Error } from '../../../assets/icons/ic-warning.svg'
+import Edit from '../../../assets/icons/ic-edit.svg?react'
+import Error from '../../../assets/icons/ic-warning.svg?react'
import placeHolder from '../../../assets/icons/ic-plc-chart.svg'
import { getEnvironmentListMin } from '../../../services/service'
-import { Prompt } from 'react-router-dom'
interface ChartGroupBasicDeployProps {
projects: ProjectType[]
@@ -227,7 +226,6 @@ export default class ChartGroupBasicDeploy extends Component
Advanced Options
-
Deploy Chart
diff --git a/src/components/charts/modal/CreateChartGroup.tsx b/src/components/charts/modal/CreateChartGroup.tsx
index 8a87a12578..2354915cb2 100644
--- a/src/components/charts/modal/CreateChartGroup.tsx
+++ b/src/components/charts/modal/CreateChartGroup.tsx
@@ -143,7 +143,7 @@ export default class CreateChartGroup extends Component {
diff --git a/src/components/checkList/AllChartsCheck.tsx b/src/components/checkList/AllChartsCheck.tsx
index 08890b0ea9..a95510b529 100644
--- a/src/components/checkList/AllChartsCheck.tsx
+++ b/src/components/checkList/AllChartsCheck.tsx
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-import React, { Component } from 'react'
+import { Component } from 'react'
import { NavLink } from 'react-router-dom'
-import { URLS } from '../../config'
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
+
import img from '../../assets/img/ic-checklist-chart@2x.png'
import './checklist.scss'
@@ -27,7 +28,7 @@ export class AllChartsCheck extends Component<{}, {}> {
Deploy charts using Devtron.
-
+
Discover Helm charts
diff --git a/src/components/checkList/AppCheckList.tsx b/src/components/checkList/AppCheckList.tsx
deleted file mode 100644
index e9e04b2a1a..0000000000
--- a/src/components/checkList/AppCheckList.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import { Component } from 'react'
-import { NavLink } from 'react-router-dom'
-import { URLS as COMMON_URLS } from '@devtron-labs/devtron-fe-common-lib'
-import { AppCheckListProps, AppCheckListState } from './checklist.type'
-import { URLS } from '../../config'
-import './checklist.scss'
-
-export class AppCheckList extends Component {
- render() {
- const { project, git, environment, docker, hostUrl } = this.props.appChecklist
-
- return (
- <>
-
-
To deploy custom application
-
-
- {!hostUrl && (
-
- Add host URL
-
- )}
- {!project && (
-
- Add project
-
- )}
- {!environment && (
-
- Add cluster & environment
-
- )}
- {!git && (
-
- Add git account
-
- )}
- {!docker && (
-
- Add container registry
-
- )}
-
- >
- )
- }
-}
diff --git a/src/components/checkList/ChartCheckList.tsx b/src/components/checkList/ChartCheckList.tsx
index 0a4d395139..3359abcaf7 100644
--- a/src/components/checkList/ChartCheckList.tsx
+++ b/src/components/checkList/ChartCheckList.tsx
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-import React, { Component } from 'react'
+import { Component } from 'react'
import { NavLink } from 'react-router-dom'
import { ChartCheckListProps } from './checklist.type'
-import { URLS as COMMON_URLS } from '@devtron-labs/devtron-fe-common-lib'
-import { URLS } from '../../config'
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
export class ChartCheckList extends Component {
render() {
@@ -32,7 +31,7 @@ export class ChartCheckList extends Component {
{!this.props.chartChecklist.project && (
@@ -41,7 +40,7 @@ export class ChartCheckList extends Component {
)}
{!this.props.chartChecklist.environment && (
diff --git a/src/components/checkList/CustomAppDeploy.tsx b/src/components/checkList/CustomAppDeploy.tsx
index b922730bef..6db3f96e30 100644
--- a/src/components/checkList/CustomAppDeploy.tsx
+++ b/src/components/checkList/CustomAppDeploy.tsx
@@ -16,7 +16,7 @@
import { NavLink } from 'react-router-dom'
import Deploy from '../../assets/img/ic-checklist-app@2x.png'
-import { URLS } from '@devtron-labs/devtron-fe-common-lib'
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
interface CustomAppDeployType {
parentClassName?: string
@@ -30,7 +30,7 @@ export default function CustomAppDeploy({ parentClassName, imageClassName }: Cus
Create, build and deploy a custom application.
Create Custom App
diff --git a/src/components/checkList/GlobalChartCheck.tsx b/src/components/checkList/GlobalChartCheck.tsx
index 6a864f494d..b38953137e 100644
--- a/src/components/checkList/GlobalChartCheck.tsx
+++ b/src/components/checkList/GlobalChartCheck.tsx
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import React, { Component } from 'react'
+import { Component } from 'react'
import { NavLink } from 'react-router-dom'
-import { URLS } from '../../config'
+import { ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
import img from '../../assets/img/ic-checklist-chart@2x.png'
import './checklist.scss'
@@ -27,7 +27,7 @@ export class GlobalChartsCheck extends Component {
Deploy charts using Devtron.
-
+
Discover charts
diff --git a/src/components/checkList/checklist.type.ts b/src/components/checkList/checklist.type.ts
index 9125b9c171..2cc6bd4388 100644
--- a/src/components/checkList/checklist.type.ts
+++ b/src/components/checkList/checklist.type.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { RouteComponentProps } from 'react-router-dom'
-
export interface AppCheckList {
gitOps: boolean
project: boolean
@@ -25,45 +23,12 @@ export interface AppCheckList {
hostUrl: boolean
}
-export interface AppCheckListState {}
-
export interface ChartCheckList {
gitOps: boolean
project: boolean
environment: boolean
}
-export interface ChartCheckListState {
- view: string
- statusCode: number
- isChartCollapsed: boolean
- saveLoading: boolean
- form: {
- chartChecklist: ChartCheckList
- }
-}
-
-export interface GlobalConfigCheckListProps extends RouteComponentProps<{}> {
- isLoading: boolean
- isAppCreated: boolean
- appChecklist: AppCheckList
- chartChecklist: ChartCheckList
- appStageCompleted: number
- chartStageCompleted: number
-}
-
-export interface GlobalConfigCheckListState {
- isChartCollapsed: boolean
- isAppCollapsed: boolean
-}
-export interface AppCheckListProps {
- showDivider: boolean
- appChecklist: AppCheckList
- appStageCompleted: number
- isAppCollapsed: boolean
- toggleAppChecklist: (event) => void
-}
-
export interface ChartCheckListProps {
isChartCollapsed: boolean
chartChecklist: ChartCheckList
diff --git a/src/components/ciConfig/BuildContext.tsx b/src/components/ciConfig/BuildContext.tsx
index 255ae2a588..537878ca0f 100644
--- a/src/components/ciConfig/BuildContext.tsx
+++ b/src/components/ciConfig/BuildContext.tsx
@@ -26,7 +26,7 @@ import {
SelectPicker,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Dropdown } from '../../assets/icons/ic-chevron-down.svg'
+import Dropdown from '../../assets/icons/ic-chevron-down.svg?react'
import { renderOptionIcon } from './CIBuildpackBuildOptions'
import { RootBuildContext } from './ciConfigConstant'
import { BuildContextProps } from './types'
diff --git a/src/components/ciConfig/CIAdvancedConfig.tsx b/src/components/ciConfig/CIAdvancedConfig.tsx
index 60df23677d..1e7648fe1f 100644
--- a/src/components/ciConfig/CIAdvancedConfig.tsx
+++ b/src/components/ciConfig/CIAdvancedConfig.tsx
@@ -15,8 +15,8 @@
*/
import { useState } from 'react'
-import { ReactComponent as PluginIcon } from '../../assets/icons/ic-plugin.svg'
-import { ReactComponent as Dropdown } from '../../assets/icons/ic-chevron-down.svg'
+import PluginIcon from '../../assets/icons/ic-plugin.svg?react'
+import Dropdown from '../../assets/icons/ic-chevron-down.svg?react'
import { CIAdvancedConfigProps } from './types'
import TargetPlatformSelector from './TargetPlatformSelector'
import DockerArgs from '../CIPipelineN/DockerArgs'
diff --git a/src/components/ciConfig/CIBuildConfigDiff.tsx b/src/components/ciConfig/CIBuildConfigDiff.tsx
index 6d95233b98..8c41570ff8 100644
--- a/src/components/ciConfig/CIBuildConfigDiff.tsx
+++ b/src/components/ciConfig/CIBuildConfigDiff.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import React, { useEffect, useState } from 'react'
-import { ReactComponent as CaretIcon } from '../../assets/icons/ic-chevron-down.svg'
+import { type JSX, useEffect, useState } from 'react'
+import CaretIcon from '../../assets/icons/ic-chevron-down.svg?react'
import { MODES } from '../../config'
import { getCIConfigDiffValues } from './CIConfig.utils'
import { CIBuildConfigDiffProps, CIConfigDiffType } from './types'
diff --git a/src/components/ciConfig/CIBuildpackBuildOptions.tsx b/src/components/ciConfig/CIBuildpackBuildOptions.tsx
index 8ff9aaf33d..7e58b7e0a7 100644
--- a/src/components/ciConfig/CIBuildpackBuildOptions.tsx
+++ b/src/components/ciConfig/CIBuildpackBuildOptions.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useEffect, useState } from 'react'
+import { type JSX, useEffect, useState } from 'react'
import { components } from 'react-select'
import {
CIBuildType,
@@ -33,10 +33,10 @@ import {
getCustomOptionSelectionStyle,
Option,
} from '../v2/common/ReactSelect.utils'
-import { ReactComponent as GitLab } from '../../assets/icons/git/gitlab.svg'
-import { ReactComponent as Git } from '../../assets/icons/git/git.svg'
-import { ReactComponent as GitHub } from '../../assets/icons/git/github.svg'
-import { ReactComponent as BitBucket } from '../../assets/icons/git/bitbucket.svg'
+import GitLab from '../../assets/icons/git/gitlab.svg?react'
+import Git from '../../assets/icons/git/git.svg?react'
+import GitHub from '../../assets/icons/git/github.svg?react'
+import BitBucket from '../../assets/icons/git/bitbucket.svg?react'
import { getAbsoluteProjectPath } from './CIConfig.utils'
import { DockerConfigOverrideKeys } from '../ciPipeline/types'
import {
diff --git a/src/components/ciConfig/CIConfigDiffView.tsx b/src/components/ciConfig/CIConfigDiffView.tsx
index 736a0f3e53..940c780658 100644
--- a/src/components/ciConfig/CIConfigDiffView.tsx
+++ b/src/components/ciConfig/CIConfigDiffView.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useEffect, useState } from 'react'
+import { type JSX, useEffect, useState } from 'react'
import {
showError,
Progressing,
@@ -27,15 +27,15 @@ import {
WorkflowType,
ToastVariantType,
ToastManager,
- URLS as CommonURLS,
ConfirmationModal,
ConfirmationModalVariantType,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { Link, useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom'
+import { generatePath, Link, useLocation, useNavigate, useParams } from 'react-router-dom'
import Tippy from '@tippyjs/react'
-import { ReactComponent as CloseIcon } from '../../assets/icons/ic-cross.svg'
-import { ReactComponent as EditIcon } from '../../assets/icons/ic-pencil.svg'
-import { ReactComponent as DeleteIcon } from '../../assets/icons/ic-delete-interactive.svg'
+import CloseIcon from '../../assets/icons/ic-cross.svg?react'
+import EditIcon from '../../assets/icons/ic-pencil.svg?react'
+import DeleteIcon from '../../assets/icons/ic-delete-interactive.svg?react'
import { Workflow } from '../workflowEditor/Workflow'
import { URLS } from '../../config'
import { CIConfigDiffViewProps, GetCIPipelineModalURLType, ProcessedWorkflowsType } from './types'
@@ -56,14 +56,9 @@ export default function CIConfigDiffView({
gitMaterials,
isTemplateView,
}: CIConfigDiffViewProps) {
- const history = useHistory()
const location = useLocation()
- const match = useRouteMatch<{
- appId: string
- }>()
- const { appId } = useParams<{
- appId: string
- }>()
+ const navigate = useNavigate()
+ const { appId } = useParams<{ appId: string }>()
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [deleteInProgress, setDeleteInProgress] = useState(false)
const [selectedWFId, setSelectedWFId] = useState(0)
@@ -147,14 +142,10 @@ export default function CIConfigDiffView({
}
if (ciNode.isLinkedCI) {
- return `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}/${getLinkedCIPipelineURL(
- appId,
- workflowId,
- ciNode.id,
- )}`
+ return getLinkedCIPipelineURL(appId, workflowId, ciNode.id, true)
}
- return `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}/${workflowId}/${
+ return `${generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.CONFIGURATIONS, { appId })}/${URLS.APP_WORKFLOW_CONFIG}/${workflowId}/${
URLS.APP_CI_CONFIG
}/${wfCIMap.get(workflowId)}/build`
}
@@ -299,9 +290,9 @@ export default function CIConfigDiffView({
height={getWorkflowHeight(_wf)}
width="100%"
nodes={_wf.nodes}
- history={history}
+ params={{ appId }}
location={location}
- match={match}
+ navigate={navigate}
handleCDSelect={noop}
handleCISelect={noop}
openEditWorkflow={noop}
@@ -324,7 +315,6 @@ export default function CIConfigDiffView({
))
}
-
return (
diff --git a/src/components/ciConfig/CIConfigForm.tsx b/src/components/ciConfig/CIConfigForm.tsx
index 541df5c43a..129a7b2a70 100644
--- a/src/components/ciConfig/CIConfigForm.tsx
+++ b/src/components/ciConfig/CIConfigForm.tsx
@@ -41,7 +41,7 @@ import {
SelectedGitMaterialType,
SourceConfigType,
} from './types'
-import { ReactComponent as NextIcon } from '../../assets/icons/ic-arrow-right.svg'
+import NextIcon from '../../assets/icons/ic-arrow-right.svg?react'
import CIConfigDiffView from './CIConfigDiffView'
import CIContainerRegistryConfig from './CIContainerRegistryConfig'
import CIDockerFileConfig from './CIDockerFileConfig'
diff --git a/src/components/ciConfig/CIContainerRegistryConfig.tsx b/src/components/ciConfig/CIContainerRegistryConfig.tsx
index a23e61f5ca..0a2619a308 100644
--- a/src/components/ciConfig/CIContainerRegistryConfig.tsx
+++ b/src/components/ciConfig/CIContainerRegistryConfig.tsx
@@ -24,8 +24,8 @@ import {
SelectPicker,
ButtonVariantType,
ButtonComponentType,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { URLS } from '../../config'
import { _multiSelectStyles } from './CIConfig.utils'
import { CIContainerRegistryConfigProps } from './types'
import { DockerConfigOverrideKeys } from '../ciPipeline/types'
@@ -114,7 +114,7 @@ export default function CIContainerRegistryConfig({
component: ButtonComponentType.link,
dataTestId: 'add-container-registry-button',
linkProps: {
- to: URLS.GLOBAL_CONFIG_DOCKER,
+ to: ROUTER_URLS.GLOBAL_CONFIG_DOCKER,
},
startIcon:
,
},
diff --git a/src/components/ciConfig/CIContainerRegistryInfoBlock.tsx b/src/components/ciConfig/CIContainerRegistryInfoBlock.tsx
index 50af657599..1afadf7185 100644
--- a/src/components/ciConfig/CIContainerRegistryInfoBlock.tsx
+++ b/src/components/ciConfig/CIContainerRegistryInfoBlock.tsx
@@ -16,11 +16,9 @@
import { generatePath, Link } from 'react-router-dom'
-import { Button, ButtonVariantType, InfoBlock, URLS } from '@devtron-labs/devtron-fe-common-lib'
+import { Button, ButtonVariantType, InfoBlock, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
-import { Routes } from '@Config/constants'
-
-import { ReactComponent as ArrowIcon } from '../../assets/icons/ic-arrow-left.svg'
+import ArrowIcon from '../../assets/icons/ic-arrow-left.svg?react'
import { CIContainerRegistryInfoBlockProps } from './types'
export const CIContainerRegistryInfoBlock = ({
@@ -55,13 +53,14 @@ export const CIContainerRegistryInfoBlock = ({
{isCDPipeline && (
Take me there
diff --git a/src/components/ciConfig/CIDockerFileConfig.tsx b/src/components/ciConfig/CIDockerFileConfig.tsx
index 1daf11d708..faa203f1f9 100644
--- a/src/components/ciConfig/CIDockerFileConfig.tsx
+++ b/src/components/ciConfig/CIDockerFileConfig.tsx
@@ -34,8 +34,8 @@ import { DockerConfigOverrideKeys } from '../ciPipeline/types'
import { BuildersAndFrameworksType, CIDockerFileConfigProps, LoadingState } from './types'
import { RootBuildContext, CI_BUILD_TYPE_OPTIONS } from './ciConfigConstant'
import { FEATURE_DISABLED } from '../../config/constantMessaging'
-import { ReactComponent as CheckIcon } from '../../assets/icons/ic-check.svg'
-import { ReactComponent as ErrorIcon } from '../../assets/icons/ic-error-exclamation.svg'
+import CheckIcon from '../../assets/icons/ic-check.svg?react'
+import ErrorIcon from '../../assets/icons/ic-error-exclamation.svg?react'
export default function CIDockerFileConfig({
configOverrideView,
diff --git a/src/components/ciConfig/CreateDockerFileLanguageOptions.tsx b/src/components/ciConfig/CreateDockerFileLanguageOptions.tsx
index de0b5ec216..54c1d87eca 100644
--- a/src/components/ciConfig/CreateDockerFileLanguageOptions.tsx
+++ b/src/components/ciConfig/CreateDockerFileLanguageOptions.tsx
@@ -21,7 +21,7 @@ import { SelectPicker, SelectPickerVariantType } from '@devtron-labs/devtron-fe-
import { getGitProviderIcon } from '@Components/common'
-import { ReactComponent as Reset } from '../../assets/icons/ic-arrow-anticlockwise.svg'
+import Reset from '../../assets/icons/ic-arrow-anticlockwise.svg?react'
import { CREATE_DOCKER_FILE_LANGUAGE_OPTIONS_TEXT } from './ciConfigConstant'
import { CreateDockerFileLanguageOptionsProps, ResetEditorChangesProps } from './types'
diff --git a/src/components/ciConfig/TargetPlatformSelector.tsx b/src/components/ciConfig/TargetPlatformSelector.tsx
index 55bcaeee51..7786b21c31 100644
--- a/src/components/ciConfig/TargetPlatformSelector.tsx
+++ b/src/components/ciConfig/TargetPlatformSelector.tsx
@@ -15,7 +15,7 @@
*/
import { SelectPicker, SelectPickerProps } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as WarningIcon } from '@Icons/ic-warning.svg'
+import WarningIcon from '@Icons/ic-warning.svg?react'
import { DockerConfigOverrideKeys } from '../ciPipeline/types'
import { TARGET_PLATFORM_LIST } from './CIConfig.utils'
import { SelectorMessaging } from './ciConfigConstant'
diff --git a/src/components/ciConfig/ciConfigConstant.ts b/src/components/ciConfig/ciConfigConstant.ts
index 0a2c5b5acb..2c953305de 100644
--- a/src/components/ciConfig/ciConfigConstant.ts
+++ b/src/components/ciConfig/ciConfigConstant.ts
@@ -16,9 +16,9 @@
import { CIBuildType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as AddIcon } from '../../assets/icons/ic-add.svg'
-import { ReactComponent as BuildpackIcon } from '../../assets/icons/ic-builpack.svg'
-import { ReactComponent as FileIcon } from '../../assets/icons/ic-file-text.svg'
+import AddIcon from '../../assets/icons/ic-add.svg?react'
+import BuildpackIcon from '../../assets/icons/ic-builpack.svg?react'
+import FileIcon from '../../assets/icons/ic-file-text.svg?react'
import { CIBuildTypeOptionType } from './types'
export const SelectorMessaging = {
diff --git a/src/components/ciPipeline/ConfigureWebhook.tsx b/src/components/ciPipeline/ConfigureWebhook.tsx
index 0b98162014..ee53a00f21 100644
--- a/src/components/ciPipeline/ConfigureWebhook.tsx
+++ b/src/components/ciPipeline/ConfigureWebhook.tsx
@@ -15,9 +15,9 @@
*/
import { useState } from 'react'
-import { ReactComponent as Webhook } from '../../assets/icons/ic-CIWebhook.svg'
-import { ReactComponent as Info } from '../../assets/icons/ic-info-filled-purple.svg'
-import { ReactComponent as Add } from '../../assets/icons/ic-add.svg'
+import Webhook from '../../assets/icons/ic-CIWebhook.svg?react'
+import Info from '../../assets/icons/ic-info-filled-purple.svg?react'
+import Add from '../../assets/icons/ic-add.svg?react'
import { WebhookSelectorCondition } from './WebhookSelectorCondition'
import { ClipboardButton, copyToClipboard, InfoBlock } from '@devtron-labs/devtron-fe-common-lib'
diff --git a/src/components/ciPipeline/LinkedCIPipelineEdit.tsx b/src/components/ciPipeline/LinkedCIPipelineEdit.tsx
index 2a88f93cc9..b08cfd7a44 100644
--- a/src/components/ciPipeline/LinkedCIPipelineEdit.tsx
+++ b/src/components/ciPipeline/LinkedCIPipelineEdit.tsx
@@ -30,19 +30,19 @@ import {
import AsyncSelect from 'react-select/async'
import { saveLinkedCIPipeline } from './ciPipeline.service'
import { ViewType } from '../../config'
-import { CIPipelineBuildType, CIPipelineProps, LinkedCIPipelineState } from './types'
+import { CIPipelineBuildType, LinkedCIPipelineEditProps, LinkedCIPipelineState } from './types'
import { Typeahead, TypeaheadOption, TypeaheadErrorOption } from '../common'
import { ValidationRules } from './validationRules'
import { Info } from '../common/icons/Icons'
import { getCIConfig } from '../../services/service'
import error from '../../assets/icons/misc/errorInfo.svg'
-import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
+import Close from '../../assets/icons/ic-close.svg?react'
import './ciPipeline.scss'
import { appListOptions } from '../AppSelector/AppSelectorUtil'
-import { ReactComponent as Warning } from '../../assets/icons/ic-warning.svg'
+import Warning from '../../assets/icons/ic-warning.svg?react'
import { DUPLICATE_PIPELINE_NAME_VALIDATION, REQUIRED_FIELD_MSG } from '../../config/constantMessaging'
-export default class LinkedCIPipeline extends Component
{
+export default class LinkedCIPipeline extends Component {
validationRules
urlRef
@@ -122,7 +122,7 @@ export default class LinkedCIPipeline extends Component pipeline.name === form.name)
- isValid.name = +this.props.match.params.appId === this.state.form.parentAppId ? !isFound : true
+ isValid.name = +this.props.params.appId === this.state.form.parentAppId ? !isFound : true
this.setState({ form, isValid })
}
@@ -132,7 +132,7 @@ export default class LinkedCIPipeline extends Component ci.id === this.state.form.parentCIPipelineId)
parentCIPipeline.pipelineType = CIPipelineBuildType.CI_LINKED
const params = {
- appId: +this.props.match.params.appId,
- workflowId: +this.props.match.params.workflowId,
+ appId: +this.props.params.appId,
+ workflowId: +this.props.params.workflowId,
name: this.state.form.name,
isTemplateView: this.props.isTemplateView,
}
diff --git a/src/components/ciPipeline/LinkedCIPipelineView.tsx b/src/components/ciPipeline/LinkedCIPipelineView.tsx
index 3999dd5142..29a4ed8ea3 100644
--- a/src/components/ciPipeline/LinkedCIPipelineView.tsx
+++ b/src/components/ciPipeline/LinkedCIPipelineView.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Component } from 'react'
+import { useEffect, useState, useCallback } from 'react'
import {
showError,
Progressing,
@@ -25,224 +25,179 @@ import {
RadioGroupItem,
CustomInput,
noop,
- ToastVariantType,
- ToastManager,
TriggerType,
URLS as CommonURLS,
Button,
ButtonVariantType,
ButtonStyleType,
DeleteCINodeButton,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
-import { Link } from 'react-router-dom'
+import { Link, useParams } from 'react-router-dom'
import Tippy from '@tippyjs/react'
import { getInitDataWithCIPipeline } from './ciPipeline.service'
import { ViewType, URLS } from '../../config'
-import { CIPipelineProps, CIPipelineState } from './types'
+import { LinkedCIPipelineViewProps, CIPipelineState } from './types'
import { getCIPipelineURL, Info } from '../common'
import { getWorkflowList } from '../../services/service'
-import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
-import { ReactComponent as Warning } from '../../assets/icons/ic-warning.svg'
+import Close from '../../assets/icons/ic-close.svg?react'
+import Warning from '../../assets/icons/ic-warning.svg?react'
import { SourceMaterials } from './SourceMaterials'
import './ciPipeline.scss'
-export default class LinkedCIPipelineView extends Component {
- constructor(props) {
- super(props)
- this.state = {
- code: 0,
- view: ViewType.LOADING,
- form: {
- name: '',
- args: [{ key: '', value: '', id: 0 }],
- materials: [],
- triggerType: TriggerType.Auto,
- beforeDockerBuildScripts: [],
- afterDockerBuildScripts: [],
- gitHost: undefined,
- webhookEvents: [],
- ciPipelineSourceTypeOptions: [],
- webhookConditionList: [],
- ciPipelineEditable: true,
- isOffendingMandatoryPlugin: false,
- },
- ciPipeline: {
- parentCiPipeline: 0,
- parentAppId: 0,
- active: true,
- ciMaterial: [],
- dockerArgs: {},
- externalCiConfig: {},
- id: 0,
- isExternal: false,
- isManual: false,
- name: '',
- linkedCount: 0,
- },
- showDeleteModal: false,
- showDockerArgs: false,
- loadingData: true,
- sourcePipelineURL: '',
- showPreBuild: false,
- showPostBuild: false,
- showDocker: false,
- }
- this.escFunction = this.escFunction.bind(this)
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.escFunction)
- getInitDataWithCIPipeline(
- this.props.match.params.appId,
- this.props.match.params.ciPipelineId,
- true,
- this.props.isTemplateView,
- )
- .then((response) => {
- this.setState({ ...response, loadingData: false }, () => {
- this.generateSourceUrl().catch(() => {
- this.setState({
- sourcePipelineURL: `${URLS.APPLICATION_MANAGEMENT_APP}/${this.state.ciPipeline.parentAppId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}`,
- })
- })
- })
- })
- .catch((error: ServerErrors) => {
- showError(error)
- this.setState({ loadingData: false, view: ViewType.FORM })
- })
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.escFunction)
- }
-
- escFunction(event) {
- if ((event.keyCode === 27 || event.key === 'Escape') && typeof this.props.close === 'function') {
- this.props.close()
- }
- }
+const LinkedCIPipelineView = ({
+ close,
+ connectCDPipelines,
+ getWorkflows,
+ isTemplateView,
+}: LinkedCIPipelineViewProps) => {
+ const { appId, ciPipelineId, workflowId } = useParams<{ appId: string; ciPipelineId: string; workflowId: string }>()
+ const [view, setView] = useState(ViewType.LOADING)
+ const [form, setForm] = useState({
+ name: '',
+ args: [{ key: '', value: '', id: 0 }],
+ materials: [],
+ triggerType: TriggerType.Auto,
+ beforeDockerBuildScripts: [],
+ afterDockerBuildScripts: [],
+ gitHost: undefined,
+ webhookEvents: [],
+ ciPipelineSourceTypeOptions: [],
+ webhookConditionList: [],
+ ciPipelineEditable: true,
+ isOffendingMandatoryPlugin: false,
+ })
+ const [ciPipeline, setCiPipeline] = useState({
+ parentCiPipeline: 0,
+ parentAppId: 0,
+ active: true,
+ ciMaterial: [],
+ dockerArgs: {},
+ externalCiConfig: {},
+ id: 0,
+ isExternal: false,
+ isManual: false,
+ name: '',
+ linkedCount: 0,
+ })
+ const [sourcePipelineURL, setSourcePipelineURL] = useState('')
- async generateSourceUrl() {
- const parentCiPipelineId = this.state.ciPipeline.parentCiPipeline
- const { result } = await getWorkflowList(this.state.ciPipeline.parentAppId, null, this.props.isTemplateView)
- let wf
- if (result.workflows) {
- const allWorkflows = result.workflows
- for (let i = 0; i < allWorkflows.length; i++) {
- if (allWorkflows[i].tree) {
- wf = allWorkflows[i].tree.find(
- (nd) => nd.type === 'CI_PIPELINE' && nd.componentId === parentCiPipelineId,
+ const generateSourceUrl = useCallback(
+ async (pipeline: CIPipelineState['ciPipeline']) => {
+ try {
+ const parentCiPipelineId = pipeline.parentCiPipeline
+ const { result } = await getWorkflowList(pipeline.parentAppId, null, isTemplateView)
+ let wf
+ if (result.workflows) {
+ const allWorkflows = result.workflows
+ for (let i = 0; i < allWorkflows.length; i++) {
+ if (allWorkflows[i].tree) {
+ wf = allWorkflows[i].tree.find(
+ (nd) => nd.type === 'CI_PIPELINE' && nd.componentId === parentCiPipelineId,
+ )
+ }
+ if (wf) {
+ break
+ }
+ }
+ const url = getCIPipelineURL(
+ pipeline.parentAppId.toString(),
+ wf.appWorkflowId,
+ false,
+ parentCiPipelineId,
+ false,
+ false,
+ isTemplateView,
+ )
+ setSourcePipelineURL(
+ `${ROUTER_URLS.DEVTRON_APP}/${pipeline.parentAppId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}/${url}`,
)
}
- if (wf) {
- break
- }
+ } catch {
+ setSourcePipelineURL(
+ `${ROUTER_URLS.DEVTRON_APP}/${pipeline.parentAppId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}`,
+ )
}
- const url = getCIPipelineURL(
- this.state.ciPipeline.parentAppId.toString(),
- wf.appWorkflowId,
- false,
- parentCiPipelineId,
- false,
- false,
- this.props.isTemplateView,
- )
- this.setState({
- sourcePipelineURL: `${URLS.APPLICATION_MANAGEMENT_APP}/${this.state.ciPipeline.parentAppId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}/${url}`,
- })
- }
- }
+ },
+ [isTemplateView],
+ )
- // invoked on Done, Discard, collapse icon
- toggleCollapse(stageId, stageIndex: number, key: 'beforeDockerBuildScripts' | 'afterDockerBuildScripts') {
- const stage = this.state.form[key].find((s) => s.id == stageId)
- if (stage.name.length && stage.script.length) {
- const { form } = { ...this.state }
- const stages = this.state.form[key]
- stages[stageIndex].isCollapsed = !stages[stageIndex].isCollapsed
- form[key] = stages
- this.setState({ form })
- } else {
- ToastManager.showToast({
- variant: ToastVariantType.error,
- description: 'Fill the required fields or cancel changes',
+ useEffect(() => {
+ getInitDataWithCIPipeline(appId, ciPipelineId, true, isTemplateView)
+ .then((response) => {
+ setView(response.view)
+ setForm(response.form)
+ setCiPipeline(response.ciPipeline)
+ generateSourceUrl(response.ciPipeline)
})
- }
- }
+ .catch((error: ServerErrors) => {
+ showError(error)
+ setView(ViewType.FORM)
+ })
+ }, [appId, ciPipelineId, isTemplateView, close, generateSourceUrl])
- renderInfoDialog() {
- return (
-
-
-
-
Configurations(Read Only)
-
- You cannot edit a linked Pipeline. To make changes edit the source Pipeline.
-
- View Source Pipeline
-
-
+ const renderInfoDialog = () => (
+
+
+
+
Configurations(Read Only)
+
+ You cannot edit a linked Pipeline. To make changes edit the source Pipeline.
+
+ View Source Pipeline
+
- )
- }
+
+ )
- renderTriggerType() {
- return (
-
-
- {}} disabled>
- Automatic
- Manual
-
-
- )
- }
+ const renderTriggerType = () => (
+
+
+
+ Automatic
+ Manual
+
+
+ )
- renderMaterials() {
- return (
-
- )
- }
+ const renderMaterials = () => (
+
+ )
- onClose = () => this.props.close() // Need to fix this: while direct use of close in onclick opening What do you want to do next? modal
-
- renderHeader() {
- return (
-
-
Linked build pipeline
- }
- ariaLabel="Close"
- showAriaLabelInTippy={false}
- onClick={this.onClose}
- variant={ButtonVariantType.borderLess}
- style={ButtonStyleType.negativeGrey}
- />
-
- )
- }
+ const renderHeader = () => (
+
+
Linked build pipeline
+ }
+ ariaLabel="Close"
+ showAriaLabelInTippy={false}
+ onClick={close}
+ variant={ButtonVariantType.borderLess}
+ style={ButtonStyleType.negativeGrey}
+ />
+
+ )
- renderDeleteButton() {
- const canDeletePipeline = this.props.connectCDPipelines === 0 && this.state.ciPipeline.linkedCount === 0
+ const renderDeleteButton = () => {
+ const canDeletePipeline = connectCDPipelines === 0 && ciPipeline.linkedCount === 0
const deleteConfig = {
- appId: this.props.match.params.appId,
- pipelineId: this.state.ciPipeline.id,
- pipelineName: this.state.ciPipeline.name,
- appWorkflowId: Number(this.props.match.params.workflowId),
+ appId,
+ pipelineId: ciPipeline.id,
+ pipelineName: ciPipeline.name,
+ appWorkflowId: Number(workflowId),
}
- if (this.props.match.params.ciPipelineId) {
+ if (ciPipelineId) {
return (
)
}
+ return null
}
- closeCIDeleteModal = () => this.setState({ showDeleteModal: false })
-
- renderCIPipelineBody() {
- if (this.state.view == ViewType.LOADING) {
+ const renderCIPipelineBody = () => {
+ if (view === ViewType.LOADING) {
return (
@@ -287,45 +241,45 @@ export default class LinkedCIPipelineView extends Component
- {this.renderTriggerType()}
- {this.renderMaterials()}
+ {renderTriggerType()}
+ {renderMaterials()}
>
)
}
- render() {
- return (
-
-
- {this.renderHeader()}
-
- {this.renderInfoDialog()}
- {this.renderCIPipelineBody()}
-
- {this.state.view !== ViewType.LOADING && (
-
- {this.renderDeleteButton()}
-
- {this.state.form.isOffendingMandatoryPlugin && (
-
- )}
- View Source Pipeline
-
-
- )}
+ return (
+
+
+ {renderHeader()}
+
+ {renderInfoDialog()}
+ {renderCIPipelineBody()}
-
- )
- }
+ {view !== ViewType.LOADING && (
+
+ {renderDeleteButton()}
+
+ {form.isOffendingMandatoryPlugin && (
+
+ )}
+ View Source Pipeline
+
+
+ )}
+
+
+ )
}
+
+export default LinkedCIPipelineView
diff --git a/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx b/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx
index 68b0b5667e..58f3177107 100644
--- a/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx
+++ b/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { Fragment, useEffect, useRef, useState } from 'react'
+import { type JSX, Fragment, MouseEvent, useEffect, useRef, useState } from 'react'
import {
showError,
Progressing,
@@ -42,11 +42,11 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'
import { useParams } from 'react-router-dom'
import Tippy from '@tippyjs/react'
-import { ReactComponent as Close } from '../../../assets/icons/ic-close.svg'
-import { ReactComponent as Help } from '../../../assets/icons/ic-help.svg'
-import { ReactComponent as ICHelpOutline } from '../../../assets/icons/ic-help-outline.svg'
-import { ReactComponent as Add } from '../../../assets/icons/ic-add.svg'
-import { ReactComponent as Tag } from '../../../assets/icons/ic-tag.svg'
+import Close from '../../../assets/icons/ic-close.svg?react'
+import Help from '../../../assets/icons/ic-help.svg?react'
+import ICHelpOutline from '../../../assets/icons/ic-help-outline.svg?react'
+import Add from '../../../assets/icons/ic-add.svg?react'
+import Tag from '../../../assets/icons/ic-tag.svg?react'
import './webhookDetails.scss'
import { getUserRole, createOrUpdateUser } from '@Pages/GlobalConfigurations/Authorization/authorization.service'
import { MODES, SERVER_MODE, WEBHOOK_NO_API_TOKEN_ERROR } from '../../../config'
@@ -386,7 +386,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
)
}
- const handleCopyToClipboard = async ({ e, token }: { e: React.MouseEvent; token: string }) => {
+ const handleCopyToClipboard = async ({ e, token }: { e: MouseEvent; token: string }) => {
stopPropagation(e)
setCopyToClipboardPromise(copyToClipboard(token))
}
@@ -543,7 +543,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
const renderSchema = (schemaData: SchemaType, schemaName: string): JSX.Element => {
return (
(schemaRef.current[schemaName] = el)}
+ ref={(el) => { schemaRef.current[schemaName] = el }}
className={`dc__border-top ${selectedSchema === schemaName ? 'bcy-1' : ''}`}
>
diff --git a/src/components/ciPipeline/WebhookSelectorCondition.tsx b/src/components/ciPipeline/WebhookSelectorCondition.tsx
index dc189a9b37..67dbc7f715 100644
--- a/src/components/ciPipeline/WebhookSelectorCondition.tsx
+++ b/src/components/ciPipeline/WebhookSelectorCondition.tsx
@@ -15,7 +15,7 @@
*/
import { ComponentSizeType, CustomInput, SelectPicker } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as CloseIcon } from '../../assets/icons/ic-close.svg'
+import CloseIcon from '../../assets/icons/ic-close.svg?react'
import { WebhookConditionType } from './types'
export const WebhookSelectorCondition = ({
diff --git a/src/components/ciPipeline/ciPipeline.service.ts b/src/components/ciPipeline/ciPipeline.service.ts
index 0e1175d9d0..f5d4a989e6 100644
--- a/src/components/ciPipeline/ciPipeline.service.ts
+++ b/src/components/ciPipeline/ciPipeline.service.ts
@@ -65,7 +65,9 @@ export function getInitData(
includeWebhookData: boolean = false,
isJobCard: boolean,
isTemplateView: AppConfigProps['isTemplateView'],
+ forceDockerfileScan: boolean
): Promise
{
+
return Promise.all([
getCIPipelineNameSuggestion(appId, isTemplateView),
getPipelineMetaConfiguration(appId.toString(), includeWebhookData, true, isJobCard, isTemplateView),
@@ -102,6 +104,7 @@ export function getInitData(
preBuildStage: emptyStepsData(),
postBuildStage: emptyStepsData(),
scanEnabled,
+ dockerfileScanEnabled: forceDockerfileScan || false,
ciPipelineEditable: true,
workflowCacheConfig: pipelineMetaConfig.result.workflowCacheConfig ?? null,
},
@@ -420,6 +423,7 @@ function createCIPatchRequest(ciPipeline, formData, isExternalCI: boolean, webho
preBuildStage,
postBuildStage,
scanEnabled: formData.scanEnabled,
+ dockerfileScanEnabled: formData.dockerfileScanEnabled,
dockerArgs: formData.args
.filter((arg) => arg.key && arg.key.length && arg.value && arg.value.length)
.reduce((agg, curr) => {
@@ -621,6 +625,7 @@ function parseCIResponse(
args: args.length ? args : [],
externalCiConfig: createCurlRequest(ciPipeline.externalCiConfig),
scanEnabled: ciPipeline.scanEnabled,
+ dockerfileScanEnabled: ciPipeline.dockerfileScanEnabled,
gitHost,
webhookEvents,
ciPipelineSourceTypeOptions,
diff --git a/src/components/ciPipeline/types.ts b/src/components/ciPipeline/types.ts
index 5c82c4b020..57ae140d23 100644
--- a/src/components/ciPipeline/types.ts
+++ b/src/components/ciPipeline/types.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { RouteComponentProps } from 'react-router-dom'
-
import {
AppConfigProps,
ChangeCIPayloadType,
@@ -30,6 +28,7 @@ import {
PipelineFormType,
PluginType,
RefVariableType,
+ RouterV5Props,
ScriptType,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -295,6 +294,7 @@ export interface CIPipelineDataType {
name: string
linkedCount: number
scanEnabled?: boolean
+ dockerfileScanEnabled?: boolean
isDockerConfigOverridden?: boolean
dockerConfigOverride?: DockerConfigOverrideType
environmentId?: any
@@ -335,14 +335,17 @@ export interface LinkedCIPipelineState {
}
}
-export interface CIPipelineProps
- extends RouteComponentProps<{ appId: string; ciPipelineId: string; workflowId: string }>,
- Required> {
- appName: string
+export interface LinkedCIPipelineViewProps extends Required> {
connectCDPipelines: number
getWorkflows: () => void
close: () => void
- changeCIPayload?: ChangeCIPayloadType
+}
+
+export interface LinkedCIPipelineEditProps
+ extends RouterV5Props<{ appId: string; workflowId: string }>,
+ LinkedCIPipelineViewProps {
+ appName: string
+ changeCIPayload: ChangeCIPayloadType
}
export const PatchAction = {
diff --git a/src/components/common/Carousel/Carousel.tsx b/src/components/common/Carousel/Carousel.tsx
index 2dd9476b30..062bd88569 100644
--- a/src/components/common/Carousel/Carousel.tsx
+++ b/src/components/common/Carousel/Carousel.tsx
@@ -15,7 +15,7 @@
*/
import React, { useEffect } from 'react'
-import { ReactComponent as ArrowIcon } from '../../../assets/icons/ic-arrow-left.svg'
+import ArrowIcon from '../../../assets/icons/ic-arrow-left.svg?react'
import './Carousel.scss'
export default function Carousel({ imageUrls, className }: { imageUrls: string[]; className?: string }) {
diff --git a/src/components/common/ClusterMetaDataBar/ClusterMetaDataBar.tsx b/src/components/common/ClusterMetaDataBar/ClusterMetaDataBar.tsx
index 939e9d56f9..c83c097ecd 100644
--- a/src/components/common/ClusterMetaDataBar/ClusterMetaDataBar.tsx
+++ b/src/components/common/ClusterMetaDataBar/ClusterMetaDataBar.tsx
@@ -14,26 +14,28 @@
* limitations under the License.
*/
-import { generatePath, useHistory } from 'react-router-dom'
+import { generatePath, useLocation } from 'react-router-dom'
import {
AppThemeType,
getComponentSpecificThemeClass,
K8S_EMPTY_GROUP,
- RESOURCE_BROWSER_ROUTES,
+ ROUTER_URLS,
useMainContext,
useTheme,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ArrowLeft } from '../../../assets/icons/ic-arrow-left.svg'
-import { ReactComponent as AllResourcesIcon } from '../../../assets/icons/ic-resource.svg'
-import { ReactComponent as TerminalIcon } from '../../../assets/icons/ic-terminal-fill.svg'
+import ArrowLeft from '../../../assets/icons/ic-arrow-left.svg?react'
+import AllResourcesIcon from '../../../assets/icons/ic-resource.svg?react'
+import TerminalIcon from '../../../assets/icons/ic-terminal-fill.svg?react'
import { URLS } from '../../../config'
import { NodeDetailTabs } from '../../app/types'
import { ClusterMetaDataBarProps } from './types'
import './ClusterMetaData.scss'
+const RESOURCE_BROWSER_ROUTES = ROUTER_URLS.RESOURCE_BROWSER.CLUSTER_DETAILS
+
export const ClusterMetaDataBar = ({
clusterName,
namespace,
@@ -41,8 +43,7 @@ export const ClusterMetaDataBar = ({
isVirtualEnvironment,
}: ClusterMetaDataBarProps) => {
const { isSuperAdmin } = useMainContext()
- const history = useHistory()
- const { pathname } = history.location
+ const { pathname } = useLocation()
const { appTheme } = useTheme()
const darkTheme =
pathname.includes(NodeDetailTabs.MANIFEST.toLowerCase()) ||
@@ -55,7 +56,7 @@ export const ClusterMetaDataBar = ({
@@ -67,7 +68,7 @@ export const ClusterMetaDataBar = ({
diff --git a/src/components/common/ContentCard/ContentCard.scss b/src/components/common/ContentCard/ContentCard.scss
index 86223e8852..1c177a99ff 100644
--- a/src/components/common/ContentCard/ContentCard.scss
+++ b/src/components/common/ContentCard/ContentCard.scss
@@ -19,7 +19,6 @@
box-shadow:
0 8px 12px 0 rgba(30, 35, 96, 0.1),
0 1px 4px 0 rgba(0, 0, 0, 0.1);
- border-radius: 400;
}
.horizontal {
diff --git a/src/components/common/ContentCard/ContentCard.tsx b/src/components/common/ContentCard/ContentCard.tsx
index 9077dedeca..76e05699de 100644
--- a/src/components/common/ContentCard/ContentCard.tsx
+++ b/src/components/common/ContentCard/ContentCard.tsx
@@ -101,8 +101,9 @@ export default function ContentCard({
) : (
+ `dc__no-decor fw-6 cursor cn-9 ${direction || CardContentDirection.Vertical} ${isActive ? 'active' : ''}`
+ }
onClick={onClick || noop}
>
{getContent()}
diff --git a/src/components/common/DatePickers/Calender.tsx b/src/components/common/DatePickers/Calender.tsx
deleted file mode 100644
index 51a6cdded6..0000000000
--- a/src/components/common/DatePickers/Calender.tsx
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React, { Component, useState } from 'react'
-import 'react-dates/initialize'
-import 'react-dates/lib/css/_datepicker.css'
-import CustomizableCalendarDay from 'react-dates/esm/components/CustomizableCalendarDay.js'
-import ReactGA from 'react-ga4'
-import moment, { Moment } from 'moment'
-import { isInclusivelyBeforeDay, DateRangePicker, SingleDatePicker } from 'react-dates'
-import { noop } from '@devtron-labs/devtron-fe-common-lib'
-import './calendar.css'
-
-interface DatePickerProps {
- endDate
- startDate
- handleFocusChange
- handleDatesChange
- focusedInput
-}
-
-const hoveredSpanStyles = {
- background: 'var(--B100)',
- color: 'var(--B500)',
-}
-
-const selectedStyles = {
- background: 'var(--B100)',
- color: 'var(--B500)',
-
- hover: {
- background: 'var(--B500)',
- color: 'var(--N0)',
- },
-}
-
-const selectedSpanStyles = {
- background: 'var(--B100)',
- color: 'var(--B500)',
- hover: {
- background: 'var(--B500)',
- color: 'var(--N0)',
- },
-}
-
-export const customDayStyles = {
- selectedStartStyles: selectedStyles,
- selectedEndStyles: selectedStyles,
- hoveredSpanStyles,
- selectedSpanStyles,
- selectedStyles,
- border: 'none',
-}
-
-const styless = {
- PresetDateRangePicker_panel: {
- padding: '22px 16px',
- width: '200px',
- height: '100%',
- },
- PresetDateRangePicker_button: {
- width: '178px',
- background: 'var(--transparent)',
- border: 'none',
- color: 'var(--N900)',
- padding: '8px',
- font: 'inherit',
- fontWeight: 500,
- lineHeight: 'normal',
- overflow: 'visible',
- cursor: 'pointer',
- ':active': {
- outline: 0,
- },
- },
- DayPicker_portal__horizontal: {
- zIndex: 1,
- },
- PresetDateRangePicker_button__selected: {
- color: 'var(--B500)',
- fontWeight: 600,
- background: 'var(--B100)',
- outline: 'none',
- },
-}
-
-export class DatePicker extends Component {
- constructor(props) {
- super(props)
-
- this.renderDatePresets = this.renderDatePresets.bind(this)
- }
-
- getInitialVisibleMonth = () => {
- return this.props.endDate
- }
-
- handleIsDayBlocked(day) {
- return false
- }
-
- renderDatePresets() {
- const p = [
- { text: 'Last 15 days', end: moment(), start: moment().subtract(15, 'days') },
- { text: 'Last 1 month', end: moment(), start: moment().subtract(1, 'months') },
- { text: 'Last 2 months', end: moment(), start: moment().subtract(2, 'months') },
- { text: 'Last 3 months', end: moment(), start: moment().subtract(3, 'months') },
- { text: 'Last 4 months', end: moment(), start: moment().subtract(4, 'months') },
- { text: 'Last 5 months', end: moment(), start: moment().subtract(5, 'months') },
- { text: 'Last 6 months', end: moment(), start: moment().subtract(6, 'months') },
- ]
- return (
-
- {p.map(({ text, start, end }) => {
- const isSelected =
- start.isSame(this.props.startDate, 'day') && end.isSame(this.props.endDate, 'day')
- let buttonStyles = {
- ...styless.PresetDateRangePicker_button,
- }
- if (isSelected) {
- buttonStyles = {
- ...buttonStyles,
- ...styless.PresetDateRangePicker_button__selected,
- ...styless.DayPicker_portal__horizontal,
- }
- }
- return (
- {
- ReactGA.event({
- category: 'Deployment Metrics',
- action: 'Date Range Changed',
- label: 'Predefined',
- })
- this.props.handleDatesChange({ startDate: start, endDate: end })
- this.props.handleFocusChange(null)
- }}
- >
- {text}
-
- )
- })}
-
- )
- }
-
- render() {
- return (
- {
- ReactGA.event({
- category: 'Deployment Metrics',
- action: 'Date Range Changed',
- label: 'Custom',
- })
- this.props.handleDatesChange(range)
- }} // PropTypes.func.isRequired,
- focusedInput={this.props.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
- onFocusChange={this.props.handleFocusChange} // PropTypes.func.isRequired,
- displayFormat="DD-MM-YYYY"
- isOutsideRange={(day) => !isInclusivelyBeforeDay(day, moment())} // enable past dates
- renderCalendarInfo={this.renderDatePresets}
- calendarInfoPosition="before"
- initialVisibleMonth={this.getInitialVisibleMonth}
- renderCalendarDay={(props) => }
- hideKeyboardShortcutsPanel
- numberOfMonths={1}
- block={false}
- small
- withFullScreenPortal={false}
- anchorDirection="right"
- orientation="horizontal"
- minimumNights={1}
- isDayBlocked={this.handleIsDayBlocked}
- />
- )
- }
-}
-
-interface SingleDatePickerProps {
- date: Moment
- handleDatesChange: (e) => void
- readOnly?: boolean
- isTodayBlocked?: boolean
-}
-
-const blockToday = (day: Moment): boolean => {
- return day.isSame(moment(), 'day')
-}
-
-/**
- *
- * @param param0 date, handleDatesChange, readOnly, isTodayBlocked
- * @deprecated it is replaced with DatePicker in common
- * @returns
- */
-
-export const SingleDatePickerComponent = ({
- date,
- handleDatesChange,
- readOnly,
- isTodayBlocked,
-}: SingleDatePickerProps) => {
- const [focused, setFocused] = useState(false)
-
- const handleFocusChange = ({ focused }) => {
- setFocused(focused)
- }
-
- return (
- }
- hideKeyboardShortcutsPanel
- withFullScreenPortal={false}
- orientation="horizontal"
- readOnly={readOnly || false}
- isDayBlocked={isTodayBlocked ? blockToday : noop}
- />
- )
-}
diff --git a/src/components/common/DatePickers/calendar.css b/src/components/common/DatePickers/calendar.css
deleted file mode 100644
index b395ca0d3a..0000000000
--- a/src/components/common/DatePickers/calendar.css
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-/* OVerride Date picker */
-
-.DayPicker__horizontal, .SingleDatePicker_picker {
- z-index: 11;
- box-shadow:
- 0 2px 6px rgba(0, 0, 0, 0.05),
- 0 0 0 1px rgba(0, 0, 0, 0.07);
- background: var(--bg-overlay-primary);
-}
-
-.DateRangePickerInput__withBorder {
- border: solid 1px var(--N200);
- background-color: var(--bg-secondary);
-}
-
-.DateRangePickerInput {
- height: 40px;
- border-radius: 4px;
-}
-
-.DateInput__small {
- background: var(--transparent);
- padding: 4px 8px;
-}
-
-.DateInput__small,
-.DateInput_input__focused,
-.DateInput_input {
- height: 100%;
-}
-
-.DateInput_input {
- font-weight: 400;
- border: none;
-}
-
-.DateInput_input__focused {
- border-bottom: solid 2px var(--B500);
-}
-
-.DateInput__small:first-child {
- height: 100%;
- border-bottom-left-radius: 4px;
- border-top-left-radius: 4px;
-}
-
-.DateInput__small:first-child .DateInput_input {
- border-bottom-left-radius: 4px;
- border-top-left-radius: 4px;
-}
-
-.DateInput__small:last-child {
- height: 100%;
- border-bottom-right-radius: 4px;
- border-top-right-radius: 4px;
-}
-
-.DateInput__small:last-child .DateInput_input {
- border-bottom-right-radius: 4px;
- border-top-right-radius: 4px;
-}
-
-.SingleDatePickerInput__withBorder,
-.SingleDatePickerInput__withBorder .DateInput,
-.SingleDatePickerInput__withBorder .DateInput_input#single_date_picker {
- border-radius: 4px;
-}
-
-.SingleDatePickerInput__withBorder .DateInput {
- width: 200px;
- height: 36px;
-}
-
-.SingleDatePickerInput__withBorder .DateInput_input#single_date_picker {
- font-size: 13px;
- border-bottom: none;
- cursor: pointer;
- background-color: var(--bg-secondary);
-}
-
-.SingleDatePickerInput__withBorder .DateInput_input__focused#single_date_picker {
- border-color: var(--B500);
-}
-
-.SingleDatePickerInput__withBorder .SingleDatePicker_picker {
- bottom: 0 !important;
- top: 46px !important;
-}
-
-.SingleDatePickerInput__withBorder .DateInput_input__focused#single_date_picker + .DateInput_fang {
- display: none;
-}
-
-.SingleDatePickerInput__withBorder .DayPicker .CalendarDay[aria-disabled='true'] {
- border: 1px solid var(--N100) !important;
- color: var(--N200) !important;
- background: var(--bg-primary) !important;
-}
-
-.CalendarMonthGrid__horizontal, .CalendarMonth {
- background: var(--transparent);
-}
-
-.CalendarMonth_caption {
- color: var(--N700);
-}
-
-.DayPickerNavigation_button__default {
- border: 1px solid var(--N200);
- background-color: var(--transparent);
- color: var(--N200);
-
- &:hover,&:active,&:focus-visible,&:focus {
- border-color: var(--N300);
- background-color: var(--transparent);
- }
-}
-
-.DayPickerNavigation_svg__horizontal {
- fill: var(--N600);
-}
-
-.CalendarDay {
- border: 1px solid var(--N200) !important;
- color: var(--N700) !important;
- background: var(--transparent) !important;
-
- &[aria-disabled="true"] {
- opacity: 0.5;
- cursor: not-allowed;
- }
-
- &[aria-label^="Selected"] {
- background: var(--B300) !important;
- color: var(--N0) !important;
- }
-
- &:hover:not([aria-disabled="true"]) {
- color: var(--B500) !important;
- background: var(--bg-secondary) !important;
- }
-}
-
-.DateRangePicker_picker {
- background: var(--bg-overlay-primary);
- border-radius: 4px;
-}
-
-.DateRangePickerInput_arrow {
- color: var(--N700);
-}
diff --git a/src/components/common/DeploymentTypeIcon/DeploymentTypeIcon.tsx b/src/components/common/DeploymentTypeIcon/DeploymentTypeIcon.tsx
index 45e9cd3c37..29137cccbe 100644
--- a/src/components/common/DeploymentTypeIcon/DeploymentTypeIcon.tsx
+++ b/src/components/common/DeploymentTypeIcon/DeploymentTypeIcon.tsx
@@ -14,11 +14,13 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
+
import { DeploymentAppTypes, Tooltip } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ArgoCD } from '@Icons/argo-cd-app.svg'
-import { ReactComponent as Helm } from '@Icons/helm-app.svg'
-import { ReactComponent as FluxCD } from '@Icons/ic-fluxcd.svg'
+import ArgoCD from '@Icons/argo-cd-app.svg?react'
+import Helm from '@Icons/helm-app.svg?react'
+import FluxCD from '@Icons/ic-fluxcd.svg?react'
import { importComponentFromFELibrary } from '../helpers/Helpers'
diff --git a/src/components/common/DeprecatedUpdateWarn.tsx b/src/components/common/DeprecatedUpdateWarn.tsx
index 42c5c4936b..fc4305c7b7 100644
--- a/src/components/common/DeprecatedUpdateWarn.tsx
+++ b/src/components/common/DeprecatedUpdateWarn.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { Component } from 'react'
+import { Component } from 'react'
import triangleAlert from '../../assets/icons/ic-alert-triangle.svg'
export class DeprecatedWarn extends Component {
diff --git a/src/components/common/Description/GenericDescription.scss b/src/components/common/Description/GenericDescription.scss
deleted file mode 100644
index d25a40fbdd..0000000000
--- a/src/components/common/Description/GenericDescription.scss
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-.tab-description {
- .tab-list__tab {
- .tab-hover {
- &:hover,
- &.active {
- color: var(--B500);
- .edit-yaml-icon {
- path {
- stroke: var(--B500);
- }
- }
- .terminal-icon {
- path {
- fill: var(--B500);
- }
- }
- }
- }
- .node-details__active-tab {
- height: 2px;
- background-color: var(--B500);
- border-radius: 2px 2px 0px 0px;
- margin-top: -1px;
- }
- }
-}
-
-.cluster__body-details {
- flex-basis: 100%;
-
- .pencil-icon:hover {
- color: var(--B500);
- text-decoration: none;
- svg path {
- stroke: var(--B500);
- }
- }
-
- .mark-down-editor-container {
- border-bottom: 1px solid var(--N200);
- border-radius: 3px 3px 0 0;
- font-family: 'Open Sans', Arial, sans-serif !important;
- font-size: 13px !important;
- line-height: 1.67;
-
- .mark-down-editor-preview {
- background-color: var(--bg-primary) !important;
- }
-
- .mark-down-editor-textarea-wrapper {
- border-radius: 3px !important;
- border: 1px solid var(--N200) !important;
- background-color: var(--bg-secondary) !important;
- }
-
- .mde-textarea-wrapper {
- background-color: var(--bg-primary) !important;
- padding: 10px !important;
- }
-
- .mark-down-editor-toolbar {
- justify-content: space-between !important;
- padding-left: 16px !important;
- padding-right: 16px !important;
- background-color: var(--bg-primary) !important;
- }
-
- .mde-header {
- height: 36px !important;
- .mde-tabs {
- height: inherit !important;
- button {
- padding-left: 0px !important;
- padding-right: 0px !important;
- margin: 0 16px 0 0px !important;
- padding-top: 4px !important;
- }
- .active-tab {
- border-bottom: 2px solid var(--B500) !important;
- border-radius: 0px !important;
- }
- }
- ul.mde-header-group {
- height: inherit !important;
- display: flex !important;
- justify-content: center !important;
- align-items: center !important;
- padding-top: 0px !important;
- padding-bottom: 0px !important;
- padding-right: 0px !important;
- .mde-header-item button {
- padding: 10px 4px !important;
- height: inherit !important;
- }
- }
- }
-
- .mde-preview {
- .mde-preview-content {
- padding: 0px !important;
- }
- }
-
- .mark-down-editor__hidden {
- display: none !important;
- }
- }
-
- .mark-down-editor__no-border {
- border: 0px;
- }
-
- .cluster__description-footer {
- border-radius: 0 0 3px 3px;
- border: 1px solid var(--N200);
- border-top: 0px;
- }
-}
-
-.cluster-column-container {
- max-height: 100%;
- flex-basis: 250px;
- flex-shrink: 0;
-}
-
-.cluster__description-footer {
- border-radius: 0 0 3px 3px;
- border: 1px solid var(--N200);
-}
-
-.create-app-description {
- border-bottom: 1px solid var(--N200) !important;
- border-radius: 3px !important;
-}
diff --git a/src/components/common/Description/GenericDescription.tsx b/src/components/common/Description/GenericDescription.tsx
deleted file mode 100644
index 604a191a68..0000000000
--- a/src/components/common/Description/GenericDescription.tsx
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React, { useState, useEffect, useRef } from 'react'
-import ReactMde from 'react-mde'
-import Tippy from '@tippyjs/react'
-import moment from 'moment'
-import { patchApplicationNote, patchClusterNote } from '../../ClusterNodes/clusterNodes.service'
-import 'react-mde/lib/styles/css/react-mde-all.css'
-import { MarkDown, showError, ToastManager, ToastVariantType, Tooltip } from '@devtron-labs/devtron-fe-common-lib'
-import { MDEditorSelectedTabType } from '../../ClusterNodes/types'
-import { ReactComponent as HeaderIcon } from '../../../assets/icons/mdeditor/ic-header.svg'
-import { ReactComponent as BoldIcon } from '../../../assets/icons/mdeditor/ic-bold.svg'
-import { ReactComponent as ItalicIcon } from '../../../assets/icons/mdeditor/ic-italic.svg'
-import { ReactComponent as LinkIcon } from '../../../assets/icons/mdeditor/ic-link.svg'
-import { ReactComponent as StrikethroughIcon } from '../../../assets/icons/mdeditor/ic-strikethrough.svg'
-import { ReactComponent as CodeIcon } from '../../../assets/icons/mdeditor/ic-code.svg'
-import { ReactComponent as QuoteIcon } from '../../../assets/icons/mdeditor/ic-quote.svg'
-import { ReactComponent as ImageIcon } from '../../../assets/icons/mdeditor/ic-image.svg'
-import { ReactComponent as OrderedListIcon } from '../../../assets/icons/mdeditor/ic-ordered-list.svg'
-import { ReactComponent as UnorderedListIcon } from '../../../assets/icons/mdeditor/ic-unordered-list.svg'
-import { ReactComponent as CheckedListIcon } from '../../../assets/icons/mdeditor/ic-checked-list.svg'
-import { ReactComponent as Edit } from '../../../assets/icons/ic-pencil.svg'
-import { deepEqual } from '..'
-import { Moment12HourFormat } from '../../../config'
-import {
- DEFAULT_MARKDOWN_EDITOR_PREVIEW_MESSAGE,
- MARKDOWN_EDITOR_COMMANDS,
- MD_EDITOR_TAB,
- CLUSTER_DESCRIPTION_EMPTY_ERROR_MSG,
- CLUSTER_DESCRIPTION_UNSAVED_CHANGES_MSG,
- CLUSTER_DESCRIPTION_UPDATE_MSG,
- MARKDOWN_EDITOR_COMMAND_ICON_TIPPY_CONTENT,
- MARKDOWN_EDITOR_COMMAND_TITLE,
-} from '../../ClusterNodes/constants'
-import './GenericDescription.scss'
-import { AppMetaInfo } from '../../app/types'
-
-export default function GenericDescription({
- isClusterTerminal,
- clusterId,
- appId,
- descriptionId,
- initialDescriptionText,
- initialDescriptionUpdatedBy,
- initialDescriptionUpdatedOn,
- initialEditDescriptionView,
- updateCreateAppFormDescription,
- appMetaInfo,
- tabIndex,
-}: {
- isClusterTerminal: boolean
- clusterId?: string
- appId?: number
- descriptionId?: number
- initialDescriptionText?: string
- initialDescriptionUpdatedBy?: string
- initialDescriptionUpdatedOn?: string
- initialEditDescriptionView: boolean
- updateCreateAppFormDescription?: (string) => void
- appMetaInfo?: AppMetaInfo
- tabIndex?: number
-}) {
- const [isEditDescriptionView, setEditDescriptionView] = useState(initialEditDescriptionView)
- const [descriptionText, setDescriptionText] = useState(initialDescriptionText)
- const [descriptionUpdatedBy, setDescriptionUpdatedBy] = useState(initialDescriptionUpdatedBy)
- const [descriptionUpdatedOn, setDescriptionUpdatedOn] = useState(initialDescriptionUpdatedOn)
- const [modifiedDescriptionText, setModifiedDescriptionText] = useState(initialDescriptionText)
- const [selectedTab, setSelectedTab] = useState(MD_EDITOR_TAB.WRITE)
- const isDescriptionModified: boolean = !deepEqual(descriptionText, modifiedDescriptionText)
- const mdeRef = useRef(null)
-
- useEffect(() => {
- if (typeof updateCreateAppFormDescription === 'function') {
- updateCreateAppFormDescription(modifiedDescriptionText)
- }
- }, [modifiedDescriptionText])
-
- useEffect(() => {
- if (!isClusterTerminal && appId == 0) {
- mdeRef.current?.finalRefs?.textarea?.current?.focus()
- }
- }, [isEditDescriptionView])
-
- const validateDescriptionText = (): boolean => {
- let isValid = true
- if (modifiedDescriptionText.length === 0) {
- ToastManager.showToast({
- variant: ToastVariantType.error,
- description: CLUSTER_DESCRIPTION_EMPTY_ERROR_MSG,
- })
- isValid = false
- }
- return isValid
- }
-
- const toggleDescriptionView = () => {
- let isConfirmed: boolean = true
- if (isDescriptionModified) {
- isConfirmed = window.confirm(CLUSTER_DESCRIPTION_UNSAVED_CHANGES_MSG)
- }
- if (isConfirmed) {
- setModifiedDescriptionText(descriptionText)
- setEditDescriptionView(!isEditDescriptionView)
- setSelectedTab(MD_EDITOR_TAB.WRITE)
- }
- }
-
- const handleSave = () => {
- if (isClusterTerminal) {
- updateClusterAbout()
- } else {
- updateApplicationAbout()
- }
- }
-
- const updateApplicationAbout = (): void => {
- const isValidate = validateDescriptionText()
- if (!isValidate) {
- return
- }
- const requestPayload = {
- id: descriptionId,
- identifier: Number(appId),
- description: modifiedDescriptionText,
- }
- patchApplicationNote(requestPayload)
- .then((response) => {
- if (response.result) {
- setDescriptionText(response.result.description)
- setDescriptionUpdatedBy(response.result.updatedBy)
- const _moment = moment(response.result.updatedOn, 'YYYY-MM-DDTHH:mm:ssZ')
- const _date = _moment.isValid() ? _moment.format(Moment12HourFormat) : response.result.updatedOn
- setDescriptionUpdatedOn(_date)
- setModifiedDescriptionText(response.result.description)
- appMetaInfo.note = response.result
- ToastManager.showToast({
- variant: ToastVariantType.success,
- description: CLUSTER_DESCRIPTION_UPDATE_MSG,
- })
- setEditDescriptionView(true)
- }
- })
- .catch((error) => {
- showError(error)
- })
- }
-
- const updateClusterAbout = (): void => {
- const isValidate = validateDescriptionText()
- if (!isValidate) {
- return
- }
- const requestPayload = {
- id: descriptionId,
- identifier: Number(clusterId),
- description: modifiedDescriptionText,
- }
- patchClusterNote(requestPayload)
- .then((response) => {
- if (response.result) {
- setDescriptionText(response.result.description)
- setDescriptionUpdatedBy(response.result.updatedBy)
- const _moment = moment(response.result.updatedOn, 'YYYY-MM-DDTHH:mm:ssZ')
- const _date = _moment.isValid() ? _moment.format(Moment12HourFormat) : response.result.updatedOn
- setDescriptionUpdatedOn(_date)
- setModifiedDescriptionText(response.result.description)
- ToastManager.showToast({
- variant: ToastVariantType.success,
- description: CLUSTER_DESCRIPTION_UPDATE_MSG,
- })
- setEditDescriptionView(true)
- }
- })
- .catch((error) => {
- showError(error)
- })
- }
-
- const editorCustomIcon = (commandName: string): JSX.Element => {
- switch (commandName) {
- case MARKDOWN_EDITOR_COMMAND_TITLE.HEADER:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.BOLD:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.ITALIC:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.STRIKETHROUGH:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.LINK:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.QUOTE:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.CODE:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.IMAGE:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.UNORDERED_LIST:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.ORDERED_LIST:
- return (
-
-
-
-
-
- )
- case MARKDOWN_EDITOR_COMMAND_TITLE.CHECKED_LIST:
- return (
-
-
-
-
-
- )
- }
- }
-
- return (
-
-
- {isEditDescriptionView ? (
-
-
-
Readme
- {descriptionUpdatedBy && descriptionUpdatedOn && (
-
-
- {`Last updated by ${descriptionUpdatedBy} on ${descriptionUpdatedOn}`}
-
-
- )}
-
-
- Edit
-
-
-
- Promise.resolve()
- }
- />
-
- ) : (
-
-
editorCustomIcon(commandName)}
- toolbarCommands={MARKDOWN_EDITOR_COMMANDS}
- value={modifiedDescriptionText}
- onChange={setModifiedDescriptionText}
- minEditorHeight={window.innerHeight - 165}
- minPreviewHeight={150}
- selectedTab={selectedTab}
- onTabChange={setSelectedTab}
- generateMarkdownPreview={(markdown: string) =>
- Promise.resolve(
- ,
- )
- }
- childProps={{
- writeButton: {
- className: `tab-list__tab pointer fs-13 ${
- selectedTab === MD_EDITOR_TAB.WRITE && 'cb-5 fw-6 active active-tab'
- }`,
- },
- previewButton: {
- className: `tab-list__tab pointer fs-13 ${
- selectedTab === MD_EDITOR_TAB.PREVIEW && 'cb-5 fw-6 active active-tab'
- }`,
- },
- textArea: {
- tabIndex,
- },
- }}
- />
- {initialEditDescriptionView && (
-
-
-
- Cancel
-
-
- Save
-
-
-
- )}
-
- )}
-
-
- )
-}
diff --git a/src/components/common/DynamicTabs/DynamicTabs.timer.tsx b/src/components/common/DynamicTabs/DynamicTabs.timer.tsx
index 02db126c13..bb01ebcc87 100644
--- a/src/components/common/DynamicTabs/DynamicTabs.timer.tsx
+++ b/src/components/common/DynamicTabs/DynamicTabs.timer.tsx
@@ -47,7 +47,7 @@ const Timer: React.FC = ({ start, callback, transition, transpose, fo
return () => clearTimeout(timeout)
}, [start])
- return loading ? transition() : transpose?.(now) || {now || '0s'}
+ return loading ? transition?.() : transpose?.(now) || {now || '0s'}
}
export default Timer
diff --git a/src/components/common/DynamicTabs/DynamicTabs.tsx b/src/components/common/DynamicTabs/DynamicTabs.tsx
index 2750a5cd1f..f1a70c6675 100644
--- a/src/components/common/DynamicTabs/DynamicTabs.tsx
+++ b/src/components/common/DynamicTabs/DynamicTabs.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import React, { cloneElement, MouseEventHandler, RefCallback, useMemo, useRef } from 'react'
-import { useHistory } from 'react-router-dom'
+import React, { cloneElement, type JSX, MouseEventHandler, ReactElement, RefCallback, useMemo, useRef } from 'react'
+import { useNavigate } from 'react-router-dom'
import Tippy from '@tippyjs/react'
import { Dayjs } from 'dayjs'
@@ -32,7 +32,7 @@ import {
Progressing,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICArrowClockwise } from '@Icons/ic-arrow-clockwise.svg'
+import ICArrowClockwise from '@Icons/ic-arrow-clockwise.svg?react'
import Timer from './DynamicTabs.timer'
import DynamicTabsSelect from './DynamicTabsSelect'
@@ -68,7 +68,7 @@ const DynamicTabs = ({
iconsConfig = {},
backgroundColorToken,
}: DynamicTabsProps) => {
- const { push } = useHistory()
+ const navigate = useNavigate()
const dynamicTabsContainerRef = useRef(null)
@@ -86,7 +86,7 @@ const DynamicTabs = ({
markTabActiveById(tab.id)
.then((isFound) => {
if (isFound) {
- push(tab.url)
+ navigate(tab.url)
return
}
@@ -112,8 +112,8 @@ const DynamicTabs = ({
>
{iconsConfig[tab.id] &&
- cloneElement(iconsConfig[tab.id], {
- className: `icon-dim-16 ${iconsConfig[tab.id].props.className}`,
+ cloneElement(iconsConfig[tab.id] as ReactElement<{ className?: string }>, {
+ className: `icon-dim-16 ${(iconsConfig[tab.id].props as { className?: string })?.className ?? ''}`,
})}
{shouldRenderTitle && (
@@ -132,14 +132,14 @@ const DynamicTabs = ({
const handleTabCloseAction: React.MouseEventHandler = (event) => {
event.stopPropagation()
removeTabByIdentifier(event.currentTarget.dataset.id)
- .then((url) => url && push(url))
+ .then((url) => url && navigate(url))
.catch(noop)
}
const handleTabStopAction = (e) => {
e.stopPropagation()
stopTabByIdentifier(e.currentTarget.dataset.id)
- .then((url) => url && push(url))
+ .then((url) => url && navigate(url))
.catch(noop)
}
diff --git a/src/components/common/DynamicTabs/__tests__/DynamicTabs.test.tsx b/src/components/common/DynamicTabs/__tests__/DynamicTabs.test.tsx
deleted file mode 100644
index 6651d19182..0000000000
--- a/src/components/common/DynamicTabs/__tests__/DynamicTabs.test.tsx
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import { fireEvent, render } from '@testing-library/react'
-import '@testing-library/jest-dom'
-import { BrowserRouter } from 'react-router-dom'
-import { DynamicTabs } from '..'
-import {
- dynamicTabsData,
- fixedTabsData,
- mockedRemoveTabByIdentifier,
- tabsData,
-} from '../__mocks__/DynamicTabs.mock'
-
-describe('DynamicTabs component', () => {
- it('Component renders properly', () => {
- const { container } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- expect(container.querySelector('.dynamic-tabs-section')).toBeInTheDocument()
- expect(container.querySelector('.fixed-tabs-container')).not.toBeInTheDocument()
- expect(container.querySelector('.dynamic-tabs-container')).not.toBeInTheDocument()
- expect(container.querySelector('.more-tabs-wrapper')).not.toBeInTheDocument()
- })
-
- it('Fixed tabs are rendered but no dynamic tabs', () => {
- const { container, getByText } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- expect(container.querySelector('.fixed-tabs-container')).toBeInTheDocument()
- expect(container.querySelector('.fixed-tabs-wrapper')?.children).toHaveLength(2) // 1 tab + 1 border
- expect(container.querySelector('.fixed-tab')).toBeInTheDocument()
- expect(getByText('K8s Resources')).toBeInTheDocument()
- expect(container.querySelector('.dynamic-tabs-container')).not.toBeInTheDocument()
- expect(container.querySelector('.more-tabs-wrapper')).not.toBeInTheDocument()
- })
-
- it('Dynamic tabs are rendered but no fixed tabs', () => {
- const { container, getByText } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- expect(container.querySelector('.fixed-tabs-container')).not.toBeInTheDocument()
- expect(container.querySelector('.dynamic-tabs-container')).toBeInTheDocument()
- expect(container.querySelector('.more-tabs-wrapper')).toBeInTheDocument()
- expect(container.querySelector('.dynamic-tabs-wrapper')?.children).toHaveLength(6) // 3 tabs + 3 borders
- expect(container.querySelector('.dynamic-tab')).toBeInTheDocument()
- expect(getByText('pod/devtron-nats-0')).toBeInTheDocument()
- expect(getByText('pod/git-sensor-0')).toBeInTheDocument()
- expect(getByText('statefulset/my-release-mariadb')).toBeInTheDocument()
- })
-
- it('Fixed & Dynamic tabs are rendered properly', () => {
- const { container, getByText } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- expect(container.querySelector('.fixed-tabs-container')).toBeInTheDocument()
-
- const fixedTabsWrapper = container.querySelector('.fixed-tabs-wrapper') as HTMLElement
- expect(fixedTabsWrapper.children).toHaveLength(2) // 1 tab + 1 border
-
- // fixedTabsWrapper should contain fixed-tab - `K8s Resources`
- // & should be in selected state
- const fixedTab = fixedTabsWrapper.querySelector('.fixed-tab')
- expect(fixedTab).toBeInTheDocument()
- expect(fixedTab).toHaveClass('dynamic-tab__item-selected')
- expect(getByText('K8s Resources')).toBeInTheDocument()
-
- expect(container.querySelector('.dynamic-tabs-container')).toBeInTheDocument()
- expect(container.querySelector('.more-tabs-wrapper')).toBeInTheDocument()
- expect(container.querySelector('.dynamic-tabs-wrapper')?.children).toHaveLength(6) // 3 tabs + 3 borders
- expect(container.querySelector('.dynamic-tab')).toBeInTheDocument()
- expect(getByText('pod/devtron-nats-0')).toBeInTheDocument()
- expect(getByText('pod/git-sensor-0')).toBeInTheDocument()
- expect(getByText('statefulset/my-release-mariadb')).toBeInTheDocument()
- })
-
- it('More tabs option works properly', () => {
- const { container, getByText } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- expect(container.querySelector('.more-tabs-wrapper')).toBeInTheDocument()
- expect(container.querySelector('.more-tabs__menu-wrapper')).not.toBeInTheDocument()
- expect(container.querySelector('.more-tabs__blanket')).not.toBeInTheDocument()
-
- const moreTabsButton = container.querySelector('.more-tabs-option') as HTMLElement
- expect(moreTabsButton).toBeInTheDocument()
- fireEvent.click(moreTabsButton)
-
- // After clicking on more tabs button expect the menu to be in open state
- // & related children are rendered
- expect(container.querySelector('.more-tabs__menu-wrapper')).toBeInTheDocument()
- expect(container.querySelector('.more-tabs__blanket')).toBeInTheDocument()
-
- // Different actions on more tabs select
- const selectControl = container.querySelector('.tab-search-select__control') as HTMLElement
- const selectMenu = container.querySelector('.tab-search-select__menu') as HTMLElement
- const searchTabInput = selectControl.querySelector('.tab-search-select__input') as HTMLElement
- expect(selectControl).toBeInTheDocument()
- expect(searchTabInput).toBeInTheDocument()
- expect(selectMenu).toBeInTheDocument()
- expect(selectMenu.querySelectorAll('.tab-search-select__option')).toHaveLength(3)
-
- // Search for complete term `pod`. It should highlight term `pod` from pod tab option.
- fireEvent.input(searchTabInput, { target: { value: 'pod' } })
- expect(searchTabInput.getAttribute('value')).toEqual('pod')
- const matchedOptionA = selectMenu.getElementsByClassName('tab-search-select__option')[0]
- expect(matchedOptionA).toBeDefined()
- expect(matchedOptionA.getElementsByTagName('mark')[0]).toHaveTextContent('pod')
-
- // Search for partial term `state`. It should highlight term `state` from statefulset tab option.
- fireEvent.input(searchTabInput, { target: { value: 'state' } })
- expect(searchTabInput.getAttribute('value')).toEqual('state')
- const matchedOptionB = selectMenu.getElementsByClassName('tab-search-select__option')[0]
- expect(matchedOptionB).toBeDefined()
- expect(matchedOptionB.getElementsByTagName('mark')[0]).toHaveTextContent('state')
-
- // Search for term `git-sensor-1` which is not present in options &
- // select should show noMatchingTabs message
- fireEvent.input(searchTabInput, { target: { value: 'git-sensor-1' } })
- expect(searchTabInput.getAttribute('value')).toEqual('git-sensor-1')
- const matchedOptionC = selectMenu.getElementsByClassName('tab-search-select__option')[0]
- expect(matchedOptionC).toBeUndefined()
- expect(getByText('No matching tabs')).toBeInTheDocument()
-
- // After clicking outside expect the menu to be in closed state
- // & related children are not rendered
- fireEvent.click(container.querySelector('.more-tabs__blanket') as HTMLElement)
- expect(container.querySelector('.more-tabs__menu-wrapper')).not.toBeInTheDocument()
- expect(container.querySelector('.more-tabs__blanket')).not.toBeInTheDocument()
- })
-
- it('Dynamic tab close action works properly', () => {
- let _tabsData = [...tabsData]
- const _removeTabByIdentifier = (title) => {
- const { pushURL, updatedTabsData } = mockedRemoveTabByIdentifier(title, tabsData)
- _tabsData = updatedTabsData
- return pushURL
- }
-
- const { container, rerender } = render(
- ,
- {
- wrapper: BrowserRouter,
- },
- )
-
- expect(_tabsData).toHaveLength(4) // 1 fixed tab & 3 dynamic tabs
- expect(container.querySelector('.dynamic-tabs-container')).toBeInTheDocument()
-
- const dynamicTabsWrapper = container.querySelector('.dynamic-tabs-wrapper') as HTMLElement
- expect(dynamicTabsWrapper.children).toHaveLength(6) // 3 tabs + 3 borders
- const dynamicTabA = dynamicTabsWrapper.querySelectorAll('.dynamic-tab')[0]
- expect(dynamicTabA).toBeInTheDocument()
- fireEvent.click(dynamicTabA.querySelector('.dynamic-tab__close') as HTMLElement)
-
- // Re-render component with updated tabs data
- rerender(
- ,
- )
- expect(_tabsData).toHaveLength(3) // 1 fixed tab & 2 dynamic tabs
- expect((container.querySelector('.dynamic-tabs-wrapper') as HTMLElement).children).toHaveLength(4) // 2 tabs + 2 borders
- })
-})
diff --git a/src/components/common/DynamicTabs/types.ts b/src/components/common/DynamicTabs/types.ts
index f04c692541..eaa17dc2fc 100644
--- a/src/components/common/DynamicTabs/types.ts
+++ b/src/components/common/DynamicTabs/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { ReactElement } from 'react'
+import { type JSX, ReactElement } from 'react'
import { Dayjs } from 'dayjs'
import { DynamicTabType, InitTabType } from '@devtron-labs/devtron-fe-common-lib'
diff --git a/src/components/common/GitTabs/GitProviderTabIcons.tsx b/src/components/common/GitTabs/GitProviderTabIcons.tsx
index f98eff2e3d..7534b743a0 100644
--- a/src/components/common/GitTabs/GitProviderTabIcons.tsx
+++ b/src/components/common/GitTabs/GitProviderTabIcons.tsx
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-import { ReactComponent as Bitbucket } from '@Icons/git/bitbucket.svg'
-import { ReactComponent as ICGit } from '@Icons/git/git.svg'
-import { ReactComponent as GitHub } from '@Icons/git/github.svg'
-import { ReactComponent as GitLab } from '@Icons/git/gitlab.svg'
-import { ReactComponent as ICAwsCodeCommit } from '@Icons/ic-aws-codecommit.svg'
-import { ReactComponent as Azure } from '@Icons/ic-azure.svg'
+import Bitbucket from '@Icons/git/bitbucket.svg?react'
+import ICGit from '@Icons/git/git.svg?react'
+import GitHub from '@Icons/git/github.svg?react'
+import GitLab from '@Icons/git/gitlab.svg?react'
+import ICAwsCodeCommit from '@Icons/ic-aws-codecommit.svg?react'
+import Azure from '@Icons/ic-azure.svg?react'
import { GitProviderTabIconsProps } from '../../gitOps/gitops.type'
import { GitProvider } from './constants'
diff --git a/src/components/common/List/List.tsx b/src/components/common/List/List.tsx
index 7952af1526..0ead5393c1 100644
--- a/src/components/common/List/List.tsx
+++ b/src/components/common/List/List.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react'
-import arrowTriangle from '../../../assets/icons/appstatus/ic-chevron-down.svg'
+import arrowTriangle from '../../../assets/icons/appstatus/ic-chevron-down.svg?react'
import './list.scss'
const Logo = ({ src = '', style = {}, className = '', children = null }) => {
diff --git a/src/components/common/SidePanel/SidePanel.tsx b/src/components/common/SidePanel/SidePanel.tsx
index 508611d109..cb9ce25ce5 100644
--- a/src/components/common/SidePanel/SidePanel.tsx
+++ b/src/components/common/SidePanel/SidePanel.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useEffect, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
// eslint-disable-next-line import/no-extraneous-dependencies
import Draggable, { DraggableEventHandler } from 'react-draggable'
@@ -34,6 +34,7 @@ import { SidePanelProps } from './types'
import './SidePanel.scss'
export const SidePanel = ({ asideWidth }: SidePanelProps) => {
+ const nodeRef = useRef(null)
// STATES
const [contentOverlay, setContentOverlay] = useState(false)
@@ -87,6 +88,7 @@ export const SidePanel = ({ asideWidth }: SidePanelProps) => {
className="flexbox h-100vh dc__overflow-hidden"
>
{
onStart={handleDragStart}
onStop={handleDragStop}
>
-
+
diff --git a/src/components/common/SidePanel/SidePanelContent.tsx b/src/components/common/SidePanel/SidePanelContent.tsx
index 9965a451bd..73dffa86dd 100644
--- a/src/components/common/SidePanel/SidePanelContent.tsx
+++ b/src/components/common/SidePanel/SidePanelContent.tsx
@@ -28,7 +28,7 @@ import {
useMainContext,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICMaintenance } from '@Images/ic-maintenance.svg'
+import ICMaintenance from '@Images/ic-maintenance.svg?react'
import EnterpriseTrialDialog from '@Pages/GlobalConfigurations/ClustersAndEnvironments/CreateCluster/EnterpriseTrialDialog'
import { importComponentFromFELibrary } from '../helpers/Helpers'
diff --git a/src/components/common/SidePanel/types.ts b/src/components/common/SidePanel/types.ts
index b2af4b7d5f..0e97ea468f 100644
--- a/src/components/common/SidePanel/types.ts
+++ b/src/components/common/SidePanel/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { FunctionComponent } from 'react'
+import { FunctionComponent, PropsWithChildren } from 'react'
import { IconName, MainContext, SidePanelTab, useMotionValue } from '@devtron-labs/devtron-fe-common-lib'
@@ -23,7 +23,7 @@ export interface SidePanelProps {
}
export interface SidePanelContentBaseProps {
- SidePanelHeaderActions: FunctionComponent
+ SidePanelHeaderActions: FunctionComponent
>
}
export interface SidePanelContentProps extends Pick {
diff --git a/src/components/common/TLSConnectionForm/TLSInputField.tsx b/src/components/common/TLSConnectionForm/TLSInputField.tsx
index e43b587e3c..2fec99ec6a 100644
--- a/src/components/common/TLSConnectionForm/TLSInputField.tsx
+++ b/src/components/common/TLSConnectionForm/TLSInputField.tsx
@@ -18,7 +18,7 @@ import { SyntheticEvent, useState } from 'react'
import { DEFAULT_SECRET_PLACEHOLDER, Textarea } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICError } from '@Icons/ic-error.svg'
+import ICError from '@Icons/ic-error.svg?react'
import { TLSInputFieldProps } from './types'
diff --git a/src/components/common/ValidateForm/ValidateForm.tsx b/src/components/common/ValidateForm/ValidateForm.tsx
index b5ad8fda1b..814fb42973 100644
--- a/src/components/common/ValidateForm/ValidateForm.tsx
+++ b/src/components/common/ValidateForm/ValidateForm.tsx
@@ -16,6 +16,7 @@
import { ButtonProps, ButtonVariantType, InfoBlock, Progressing } from '@devtron-labs/devtron-fe-common-lib'
import './validateForm.css'
+import { ReactNode } from 'react'
export enum VALIDATION_STATUS {
SUCCESS = 'SUCCESS',
@@ -99,7 +100,7 @@ const ValidateFailure = ({ formId, validationError, onClickValidate, isChartRepo
{Object.entries(validationError).map(([value, name]) => (
{value}:
- {name}
+ {name as ReactNode}
))}
{warning && (
diff --git a/src/components/common/eaEmptyState/EAEmptyState.tsx b/src/components/common/eaEmptyState/EAEmptyState.tsx
index dbff1a5f02..8bee90d698 100644
--- a/src/components/common/eaEmptyState/EAEmptyState.tsx
+++ b/src/components/common/eaEmptyState/EAEmptyState.tsx
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-import React from 'react'
import appDetailEmpty from '../../../assets/img/ic-empty-ea-app-detail.png'
import securityEmpty from '../../../assets/img/ic-empty-ea--security.png'
-import { ModuleNameMap, URLS } from '../../../config'
import './eaEmptyState.css'
import { NavLink } from 'react-router-dom'
+import { ModuleNameMap, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
export enum EAEmptyStateType {
SECURITY = 'security',
@@ -56,7 +55,7 @@ export default function EAEmptyState({ title, msg, stateType, knowMoreLink, head
{title}
{msg}
-
+
View Integration
diff --git a/src/components/common/errorBoundary.tsx b/src/components/common/errorBoundary.tsx
index dc9b04f5ec..318ed56ec6 100644
--- a/src/components/common/errorBoundary.tsx
+++ b/src/components/common/errorBoundary.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Component } from 'react'
+import { Component, PropsWithChildren } from 'react'
import * as Sentry from '@sentry/browser'
import { DISCORD_LINK, Reload } from '@devtron-labs/devtron-fe-common-lib'
import bugFixing from '../../assets/img/bug-fixing.webp'
@@ -27,7 +27,7 @@ interface errorBoundaryState {
interface ErrorBoundaryProps {}
-export default class ErrorBoundary extends Component {
+export default class ErrorBoundary extends Component, errorBoundaryState> {
constructor(props) {
super(props)
this.state = { eventId: null, hasError: false, isChunkLoadError: false }
diff --git a/src/components/common/formFields/Typeahead.tsx b/src/components/common/formFields/Typeahead.tsx
index 42f0d2440e..73afbb736d 100644
--- a/src/components/common/formFields/Typeahead.tsx
+++ b/src/components/common/formFields/Typeahead.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { Component, createContext } from 'react'
+import React, { Component, createContext, PropsWithChildren } from 'react'
import dropdown from '../../../assets/icons/appstatus/ic-chevron-down.svg'
import { isArrayEqual } from '../helpers/utils'
// id must exist
@@ -41,7 +41,7 @@ export interface TypeaheadProps {
}
export class Typeahead extends Component<
- TypeaheadProps,
+ PropsWithChildren,
{ defaultSelections: any[]; search: string; showMenu: boolean; selections: any[] }
> {
constructor(props) {
@@ -199,7 +199,9 @@ export class Typeahead extends Component<
}
}
-export class TypeaheadOption extends Component<{ dataTestIdMenuList?: string; item: any; id: number | string }> {
+export class TypeaheadOption extends Component<
+ PropsWithChildren<{ dataTestIdMenuList?: string; item: any; id: number | string }>
+> {
render() {
return (
@@ -225,7 +227,7 @@ export class TypeaheadOption extends Component<{ dataTestIdMenuList?: string; it
}
}
-export class TypeaheadErrorOption extends Component<{ className?: string }> {
+export class TypeaheadErrorOption extends Component> {
render() {
return (
diff --git a/src/components/common/helpers/Helpers.tsx b/src/components/common/helpers/Helpers.tsx
index bec441a8bd..bc10bf4d8d 100644
--- a/src/components/common/helpers/Helpers.tsx
+++ b/src/components/common/helpers/Helpers.tsx
@@ -31,6 +31,7 @@ import {
InfoBlock,
ToastManager,
ToastVariantType,
+ ROUTER_URLS,
} from '@devtron-labs/devtron-fe-common-lib'
import YAML from 'yaml'
import { Link } from 'react-router-dom'
@@ -49,7 +50,6 @@ import { PATTERNS } from '../../../config/constants'
import { AppEnvLocalStorageKeyType, FilterParentType } from '@Components/ApplicationGroup/AppGroup.types'
import { APP_GROUP_LOCAL_STORAGE_KEY, ENV_GROUP_LOCAL_STORAGE_KEY } from '@Components/ApplicationGroup/Constants'
import { GetAndSetAppGroupFiltersParamsType, SetFiltersInLocalStorageParamsType } from './types'
-import { URLS } from '@Config/routes'
import { HOST_ERROR_MESSAGE } from '@Config/constantMessaging'
let module
@@ -1181,7 +1181,7 @@ export const setAppGroupFilterInLocalStorage = ({
const renderHostURLWarning = () => (
{HOST_ERROR_MESSAGE.NotConfigured}
-
+
{HOST_ERROR_MESSAGE.Review}
diff --git a/src/components/common/helpers/utils.tsx b/src/components/common/helpers/utils.tsx
index 0bc025ae8c..22da359551 100644
--- a/src/components/common/helpers/utils.tsx
+++ b/src/components/common/helpers/utils.tsx
@@ -14,11 +14,12 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
import { sortCallback, GitProviderType } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as GitLab } from '../../../assets/icons/git/gitlab.svg'
-import { ReactComponent as Git } from '../../../assets/icons/git/git.svg'
-import { ReactComponent as GitHub } from '../../../assets/icons/git/github.svg'
-import { ReactComponent as BitBucket } from '../../../assets/icons/git/bitbucket.svg'
+import GitLab from '../../../assets/icons/git/gitlab.svg?react'
+import Git from '../../../assets/icons/git/git.svg?react'
+import GitHub from '../../../assets/icons/git/github.svg?react'
+import BitBucket from '../../../assets/icons/git/bitbucket.svg?react'
export function subtractArray(a: any[], b: any[], key: string): any[] {
if (!(a && a.length && a.length > 0)) {
diff --git a/src/components/common/helpers/workflowURL.ts b/src/components/common/helpers/workflowURL.ts
index a929026258..738591c9da 100644
--- a/src/components/common/helpers/workflowURL.ts
+++ b/src/components/common/helpers/workflowURL.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { AppConfigProps, URLS as CommonURLS } from '@devtron-labs/devtron-fe-common-lib'
+import { AppConfigProps, ROUTER_URLS } from '@devtron-labs/devtron-fe-common-lib'
import { URLS } from '../../../config'
import { generatePath } from 'react-router-dom'
@@ -26,16 +26,16 @@ export function getCDPipelineURL(
cdPipelineId: string = null,
shouldComputeCompleteURL: boolean = false,
) {
- const prefix = `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${CommonURLS.APP_CONFIG}/${URLS.APP_WORKFLOW_CONFIG}/`
const suffix = `${workflowId}/${isWebhookParent ? 'webhook' : 'ci-pipeline'}/${ciPipelineId}/cd-pipeline${
cdPipelineId ? `/${cdPipelineId}` : ''
}`
- if (shouldComputeCompleteURL) {
- return `${prefix}${suffix}`
+ if (!shouldComputeCompleteURL) {
+ return suffix
}
- return suffix
+ const prefix = `${generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.CONFIGURATIONS, { appId })}/${URLS.APP_WORKFLOW_CONFIG}/`
+ return `${prefix}${suffix}`
}
export function getCIPipelineURL(
@@ -51,10 +51,12 @@ export function getCIPipelineURL(
if (addPrefix) {
prefixURL = `${
isTemplateView
- ? generatePath(CommonURLS.APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP_DETAIL, {
+ ? generatePath(ROUTER_URLS.APP_TEMPLATE_DETAIL, {
+ appId,
+ })
+ : generatePath(isJobView ? ROUTER_URLS.JOB_DETAIL.ROOT : ROUTER_URLS.DEVTRON_APP_DETAILS.ROOT, {
appId,
})
- : `${isJobView ? CommonURLS.AUTOMATION_AND_ENABLEMENT_JOB : CommonURLS.APPLICATION_MANAGEMENT_APP}/${appId}`
}/edit/workflow/`
}
const ciPipelineSuffix = ciPipelineId ? `/${ciPipelineId}` : ''
@@ -74,7 +76,7 @@ export function getLinkedCIPipelineURL(
) {
const suffix = `${workflowId}/linked-ci${ciPipelineId ? `/${ciPipelineId}` : ''}`
if (addPrefix) {
- return `${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/edit/workflow/${suffix}`
+ return `${generatePath(ROUTER_URLS.DEVTRON_APP_DETAILS.CONFIGURATIONS, { appId: String(appId) })}/workflow/${suffix}`
}
return suffix
diff --git a/src/components/common/hooks/__tests__/FileReader.test.tsx b/src/components/common/hooks/__tests__/FileReader.test.tsx
deleted file mode 100644
index c8df493a5e..0000000000
--- a/src/components/common/hooks/__tests__/FileReader.test.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) 2024. Devtron Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * 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.
- */
-
-import React from 'react'
-import { fireEvent, render } from '@testing-library/react'
-import { useFileReader } from '../FileReader'
-import { FileReaderStatus, ReadFileAs } from '../types'
-import { validator } from '../../../scopedVariables/utils'
-
-describe('useFileReader', () => {
- it('should return fileData(as null), progress(as 0), status (as null), readFile and abortRead initially', () => {
- const DummyComponent = () => {
- const { fileData, progress, status, readFile, abortRead } = useFileReader()
- return (
-
- {!fileData &&
File Data
}
- {fileData &&
{fileData.data}
}
- {!progress &&
Progress
}
- {!status &&
Status
}
- {abortRead && (
-
- Abort Read
-
- )}
-
- readFile(
- 'Some Text',
- () => ({
- status: FileReaderStatus.SUCCESS,
- message: {
- data: 'a: b\n',
- description: 'File uploaded successfully',
- },
- }),
- ReadFileAs.TEXT,
- )
- }
- >
- Read File
-
-
- )
- }
- const { getByTestId } = render()
- expect(getByTestId('dummy-component')).toBeTruthy()
- expect(getByTestId('file-data')).toBeTruthy()
- expect(getByTestId('progress')).toBeTruthy()
- expect(getByTestId('status')).toBeTruthy()
- expect(getByTestId('abort-read')).toBeTruthy()
- fireEvent.click(getByTestId('read-file'))
- expect(getByTestId('valid-file-data')).toBeTruthy()
- // This tests the readFile function
- })
-
- it('should abort read when abortRead is called', () => {
- const DummyComponent = () => {
- const { fileData, progress, status, readFile, abortRead } = useFileReader()
- const mockInputFile = new File(['Some Text'], 'test.txt', {
- type: 'text/plain',
- })
- return (
-
- {!fileData &&
File Data
}
- {fileData &&
{fileData.data}
}
- {!progress &&
Progress
}
- {progress &&
{progress}
}
- {!status &&
Status
}
- {abortRead && (
-
- Abort Read
-
- )}
-
readFile(mockInputFile, validator, ReadFileAs.TEXT)}>
- Read File
-
-
- )
- }
- const { getByTestId } = render()
- expect(getByTestId('dummy-component')).toBeTruthy()
- expect(getByTestId('file-data')).toBeTruthy()
- expect(getByTestId('progress')).toBeTruthy()
- expect(getByTestId('status')).toBeTruthy()
- expect(getByTestId('abort-read')).toBeTruthy()
- fireEvent.click(getByTestId('read-file'))
- expect(getByTestId('valid-file-data')).toBeTruthy()
- fireEvent.click(getByTestId('abort-read'))
- expect(getByTestId('file-data')).toBeTruthy()
- })
-})
diff --git a/src/components/common/hooks/useOnline/useOnline.ts b/src/components/common/hooks/useOnline/useOnline.ts
index e4808e633c..bd7e5880c2 100644
--- a/src/components/common/hooks/useOnline/useOnline.ts
+++ b/src/components/common/hooks/useOnline/useOnline.ts
@@ -96,7 +96,9 @@ export const useOnline = ({ onOnline = noop }: { onOnline?: () => void }) => {
}, [])
useEffect(() => {
- if (hideInternetConnectivityBanner) return null
+ if (hideInternetConnectivityBanner) {
+ return noop
+ }
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
diff --git a/src/components/common/hooks/useVersionUpdate/types.ts b/src/components/common/hooks/useVersionUpdate/types.ts
index 92f8df765d..ecbc3c1625 100644
--- a/src/components/common/hooks/useVersionUpdate/types.ts
+++ b/src/components/common/hooks/useVersionUpdate/types.ts
@@ -14,10 +14,11 @@
* limitations under the License.
*/
+import type { JSX } from 'react'
+
export interface VersionUpdateProps {
toastEligibleRoutes: {
path: string
- exact: boolean
condition: boolean
component: JSX.Element
eligibleLocation: string
diff --git a/src/components/common/icons/Icons.tsx b/src/components/common/icons/Icons.tsx
index 2f593b759b..3f0769049f 100644
--- a/src/components/common/icons/Icons.tsx
+++ b/src/components/common/icons/Icons.tsx
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import React from 'react'
-
export const Pod = ({ color = 'var(--B500)', style = {}, onClick = null }) => {
return (