Skip to content

Commit dc71c07

Browse files
authored
feat(deployment-stage): add feature (#499)
1 parent 36e2367 commit dc71c07

File tree

46 files changed

+1259
-426
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1259
-426
lines changed

libs/domains/environment/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './lib/slices/environments.slice'
22
export * from './lib/slices/environment.actions'
3+
export * from './lib/slices/deployment-stage'
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { ActionReducerMapBuilder, Update, createAsyncThunk } from '@reduxjs/toolkit'
2+
import { DeploymentStageMainCallsApi } from 'qovery-typescript-axios'
3+
import { EnvironmentEntity, EnvironmentsState } from '@qovery/shared/interfaces'
4+
import { toastError } from '@qovery/shared/ui'
5+
import { environmentsAdapter } from './environments.slice'
6+
7+
const deploymentStageMainCallApi = new DeploymentStageMainCallsApi()
8+
9+
export const fetchDeploymentStageList = createAsyncThunk(
10+
'environment/fetchDeploymentStageList',
11+
async (payload: { environmentId: string }) => {
12+
const response = await deploymentStageMainCallApi.listEnvironmentDeploymentStage(payload.environmentId)
13+
return response.data.results
14+
}
15+
)
16+
17+
export const addServiceToDeploymentStage = createAsyncThunk(
18+
'environment/addServiceToDeploymentStage',
19+
async (payload: { deploymentStageId: string; serviceId: string }) => {
20+
const response = await deploymentStageMainCallApi.attachServiceToDeploymentStage(
21+
payload.deploymentStageId,
22+
payload.serviceId
23+
)
24+
return response.data.results
25+
}
26+
)
27+
28+
export const deploymentStageExtraReducers = (builder: ActionReducerMapBuilder<EnvironmentsState>) => {
29+
builder
30+
// fetch deployment stage list
31+
.addCase(fetchDeploymentStageList.pending, (state: EnvironmentsState, action) => {
32+
const update: Update<EnvironmentEntity> = {
33+
id: action.meta.arg.environmentId,
34+
changes: {
35+
deploymentStage: {
36+
loadingStatus: 'loading',
37+
},
38+
},
39+
}
40+
environmentsAdapter.updateOne(state, update)
41+
})
42+
.addCase(fetchDeploymentStageList.fulfilled, (state: EnvironmentsState, action) => {
43+
const update: Update<EnvironmentEntity> = {
44+
id: action.meta.arg.environmentId,
45+
changes: {
46+
deploymentStage: {
47+
loadingStatus: 'loaded',
48+
items: action.payload,
49+
},
50+
},
51+
}
52+
environmentsAdapter.updateOne(state, update)
53+
})
54+
.addCase(fetchDeploymentStageList.rejected, (state: EnvironmentsState, action) => {
55+
toastError(action.error)
56+
})
57+
// add service to deployment stage
58+
.addCase(addServiceToDeploymentStage.fulfilled, (state: EnvironmentsState, action) => {
59+
const environmentId = action.payload ? action.payload[0].environment.id : ''
60+
const update: Update<EnvironmentEntity> = {
61+
id: environmentId,
62+
changes: {
63+
deploymentStage: {
64+
loadingStatus: 'loaded',
65+
items: action.payload,
66+
},
67+
},
68+
}
69+
environmentsAdapter.updateOne(state, update)
70+
})
71+
.addCase(addServiceToDeploymentStage.rejected, (state: EnvironmentsState, action) => {
72+
toastError(action.error)
73+
})
74+
}

libs/domains/environment/src/lib/slices/environments.slice.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { EnvironmentEntity, EnvironmentsState, WebsocketRunningStatusInterface }
2626
import { ToastEnum, toast, toastError } from '@qovery/shared/ui'
2727
import { addOneToManyRelation, getEntitiesByIds, refactoPayload, shortToLongId, sortByKey } from '@qovery/shared/utils'
2828
import { RootState } from '@qovery/store'
29+
import { deploymentStageExtraReducers } from './deployment-stage'
2930

3031
export const ENVIRONMENTS_FEATURE_KEY = 'environments'
3132

@@ -181,6 +182,7 @@ export const environmentsSlice = createSlice({
181182
},
182183
},
183184
extraReducers: (builder) => {
185+
deploymentStageExtraReducers(builder)
184186
builder
185187
// get environments
186188
.addCase(fetchEnvironments.pending, (state: EnvironmentsState) => {

libs/pages/application/src/lib/ui/crud-environment-variable-modal/crud-environment-variable-modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export function CrudEnvironmentVariableModal(props: CrudEnvironmentVariableModal
8888
<div>
8989
<div className="flex items-center mb-3">
9090
<Icon name={IconEnum.CHILDREN_ARROW} className="mr-2 ml-1" />
91-
<span className="bg-accent3-500 font-bold rounded-sm text-xxs text-text-100 px-1 inline-flex items-center h-4 mr-3">
91+
<span className="bg-accent3-500 font-bold rounded-sm text-2xs text-text-100 px-1 inline-flex items-center h-4 mr-3">
9292
ALIAS
9393
</span>
9494
</div>
@@ -117,7 +117,7 @@ export function CrudEnvironmentVariableModal(props: CrudEnvironmentVariableModal
117117
{props.type === EnvironmentVariableType.OVERRIDE && (
118118
<div className="flex items-center mb-3">
119119
<Icon name={IconEnum.CHILDREN_ARROW} className="mr-2 ml-1" />
120-
<span className="bg-brand-500 font-bold rounded-sm text-xxs text-text-100 px-1 inline-flex items-center h-4 mr-3">
120+
<span className="bg-brand-500 font-bold rounded-sm text-2xs text-text-100 px-1 inline-flex items-center h-4 mr-3">
121121
OVERRIDE
122122
</span>
123123
</div>

libs/pages/application/src/lib/ui/table-row-environment-variable/table-row-environment-variable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,23 @@ export function TableRowEnvironmentVariable(props: TableRowEnvironmentVariablePr
7272
(variable as SecretEnvironmentVariableEntity).aliased_secret ? (
7373
<>
7474
<Icon name={IconEnum.CHILDREN_ARROW} className="mr-2 ml-1" />
75-
<span className="bg-accent3-500 font-bold rounded-sm text-xxs text-text-100 px-1 inline-flex items-center h-4 mr-3">
75+
<span className="bg-accent3-500 font-bold rounded-sm text-2xs text-text-100 px-1 inline-flex items-center h-4 mr-3">
7676
ALIAS
7777
</span>
7878
</>
7979
) : (variable as EnvironmentVariableEntity).overridden_variable ||
8080
(variable as SecretEnvironmentVariableEntity).overridden_secret ? (
8181
<>
8282
<Icon name={IconEnum.CHILDREN_ARROW} className="mr-2 ml-1" />
83-
<span className="bg-brand-500 font-bold rounded-sm text-xxs text-text-100 px-1 inline-flex items-center h-4 mr-3">
83+
<span className="bg-brand-500 font-bold rounded-sm text-2xs text-text-100 px-1 inline-flex items-center h-4 mr-3">
8484
OVERRIDE
8585
</span>
8686
</>
8787
) : (
8888
''
8989
)}
9090
{(variable as EnvironmentVariableEntity).mount_path ? (
91-
<span className="bg-accent1-500 font-bold rounded-sm text-xxs text-text-100 px-1 inline-flex items-center h-4 mr-3">
91+
<span className="bg-accent1-500 font-bold rounded-sm text-2xs text-text-100 px-1 inline-flex items-center h-4 mr-3">
9292
FILE
9393
</span>
9494
) : (

libs/pages/clusters/src/lib/ui/card-cluster/card-cluster.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function CardCluster(props: CardClusterProps) {
5050
<Skeleton height={12} width={100} show={!statusLoading}>
5151
<p
5252
data-testid="status-message"
53-
className={`text-xxs mt-0.5 font-medium ${getColorForStatus(cluster.extendedStatus?.status?.status)}`}
53+
className={`text-2xs mt-0.5 font-medium ${getColorForStatus(cluster.extendedStatus?.status?.status)}`}
5454
>
5555
{getStatusClusterMessage(
5656
cluster.extendedStatus?.status?.status,

libs/pages/database/src/lib/ui/page-settings-general/page-settings-general.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export function PageSettingsGeneral(props: PageSettingsGeneralProps) {
107107
className="!text-xs ml-4 gap-0.5"
108108
link="https://hub.qovery.com/docs/using-qovery/configuration/database/#modes"
109109
linkLabel="Learn more"
110-
iconRight="icon-solid-arrow-up-right-from-square text-xxs"
110+
iconRight="icon-solid-arrow-up-right-from-square text-2xs"
111111
external
112112
/>
113113
</div>
@@ -146,7 +146,7 @@ export function PageSettingsGeneral(props: PageSettingsGeneralProps) {
146146
className="!text-xs ml-4 gap-0.5"
147147
link="https://hub.qovery.com/docs/using-qovery/configuration/database/#accessibility"
148148
linkLabel="Learn more"
149-
iconRight="icon-solid-arrow-up-right-from-square text-xxs"
149+
iconRight="icon-solid-arrow-up-right-from-square text-2xs"
150150
external
151151
/>
152152
</>

libs/pages/logs/application/src/lib/page-application-logs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function PageApplicationLogs() {
103103
>
104104
<StatusChip status={currentPod?.state} className="mr-2.5" />
105105
<p className="text-xs font-medium text-text-200 mr-5">{data.pod_name}</p>
106-
<span className="block text-xxs text-text-400 mr-2">
106+
<span className="block text-2xs text-text-400 mr-2">
107107
<Icon name={IconAwesomeEnum.CODE_COMMIT} className="mr-2 text-text-100" />
108108
{data.version?.substring(0, 6)}
109109
</span>

libs/pages/logs/deployment/src/lib/ui/row/row.spec.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('Row', () => {
7272

7373
expect(cellDate.textContent).toBe('Deployment_In_Progress')
7474
expect(cellDate).toHaveClass(
75-
'py-1 pl-2.5 pr-2 text-xxs font-bold shrink-0 truncate uppercase w-[154px] text-accent2-400'
75+
'py-1 pl-2.5 pr-2 text-2xs font-bold shrink-0 truncate uppercase w-[154px] text-accent2-400'
7676
)
7777
})
7878

@@ -92,7 +92,7 @@ describe('Row', () => {
9292
const cellDate = screen.getByTestId('cell-status')
9393

9494
expect(cellDate).toHaveClass(
95-
'py-1 pl-2.5 pr-2 text-xxs font-bold shrink-0 truncate uppercase w-[154px] text-error-500'
95+
'py-1 pl-2.5 pr-2 text-2xs font-bold shrink-0 truncate uppercase w-[154px] text-error-500'
9696
)
9797
})
9898

@@ -112,7 +112,7 @@ describe('Row', () => {
112112
const cellDate = screen.getByTestId('cell-status')
113113

114114
expect(cellDate).toHaveClass(
115-
'py-1 pl-2.5 pr-2 text-xxs font-bold shrink-0 truncate uppercase w-[154px] text-success-400'
115+
'py-1 pl-2.5 pr-2 text-2xs font-bold shrink-0 truncate uppercase w-[154px] text-success-400'
116116
)
117117
})
118118

libs/pages/logs/deployment/src/lib/ui/row/row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function Row(props: RowProps) {
6161
</div>
6262
<div
6363
data-testid="cell-status"
64-
className={`py-1 pl-2.5 pr-2 text-xxs font-bold shrink-0 truncate uppercase w-[154px] ${
64+
className={`py-1 pl-2.5 pr-2 text-2xs font-bold shrink-0 truncate uppercase w-[154px] ${
6565
success ? 'text-success-400' : error ? 'text-error-500' : 'text-accent2-400'
6666
}`}
6767
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { render } from '__tests__/utils/setup-jest'
2+
import PageSettingsDeploymentPipelineFeature from './page-settings-deployment-pipeline-feature'
3+
4+
const mockDispatch = jest.fn()
5+
jest.mock('react-redux', () => ({
6+
...jest.requireActual('react-redux'),
7+
useDispatch: () => mockDispatch,
8+
}))
9+
10+
describe('PageSettingsDeploymentPipelineFeature', () => {
11+
it('should render successfully', () => {
12+
const { baseElement } = render(<PageSettingsDeploymentPipelineFeature />)
13+
expect(baseElement).toBeTruthy()
14+
})
15+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import equal from 'fast-deep-equal'
2+
import { DeploymentStageResponse } from 'qovery-typescript-axios'
3+
import { useEffect, useState } from 'react'
4+
import { toast as toastAction } from 'react-hot-toast'
5+
import { useDispatch, useSelector } from 'react-redux'
6+
import { useParams } from 'react-router-dom'
7+
import { selectApplicationsEntitiesByEnvId } from '@qovery/domains/application'
8+
import { selectDatabasesEntitiesByEnvId } from '@qovery/domains/database'
9+
import {
10+
addServiceToDeploymentStage,
11+
environmentsLoadingStatus,
12+
fetchDeploymentStageList,
13+
selectEnvironmentById,
14+
} from '@qovery/domains/environment'
15+
import { EnvironmentEntity } from '@qovery/shared/interfaces'
16+
import { ToastEnum, toast } from '@qovery/shared/ui'
17+
import { AppDispatch, RootState } from '@qovery/store'
18+
import PageSettingsDeploymentPipeline from '../../ui/page-settings-deployment-pipeline/page-settings-deployment-pipeline'
19+
20+
export interface StageRequest {
21+
deploymentStageId: string
22+
serviceId: string
23+
}
24+
25+
export function PageSettingsDeploymentPipelineFeature() {
26+
const { environmentId = '' } = useParams()
27+
const dispatch: AppDispatch = useDispatch()
28+
29+
const applications = useSelector(
30+
(state: RootState) => selectApplicationsEntitiesByEnvId(state, environmentId),
31+
(a, b) => a.length === b.length
32+
)
33+
const databases = useSelector(
34+
(state: RootState) => selectDatabasesEntitiesByEnvId(state, environmentId),
35+
(a, b) => a.length === b.length
36+
)
37+
38+
const loadingStatus = useSelector(environmentsLoadingStatus)
39+
40+
const deploymentStage = useSelector<RootState, EnvironmentEntity | undefined>(
41+
(state) => selectEnvironmentById(state, environmentId),
42+
(a, b) => equal(a?.deploymentStage?.items, b?.deploymentStage?.items)
43+
)?.deploymentStage
44+
45+
useEffect(() => {
46+
if (loadingStatus === 'loaded') dispatch(fetchDeploymentStageList({ environmentId }))
47+
}, [dispatch, environmentId, loadingStatus])
48+
49+
const [stages, setStages] = useState<DeploymentStageResponse[] | undefined>(deploymentStage?.items)
50+
51+
useEffect(() => {
52+
if (deploymentStage?.items) setStages(deploymentStage?.items)
53+
}, [setStages, deploymentStage?.items])
54+
55+
const onSubmit = async (newStage: StageRequest, prevStage: StageRequest) => {
56+
// dispatch function with two actions, undo and update stage
57+
function dispatchServiceToDeployment(stage: StageRequest, previous?: boolean) {
58+
dispatch(addServiceToDeploymentStage({ deploymentStageId: stage.deploymentStageId, serviceId: stage.serviceId }))
59+
.unwrap()
60+
.then(() => {
61+
if (previous) {
62+
// toast after apply undo
63+
toast(ToastEnum.SUCCESS, 'Your deployment stage is updated')
64+
} else {
65+
// default toast when we don't apply undo
66+
toast(
67+
ToastEnum.SUCCESS,
68+
'Your deployment stage is updated',
69+
'Do you need to go back?',
70+
() => dispatchServiceToDeployment(prevStage, true),
71+
'',
72+
'Undo update'
73+
)
74+
}
75+
})
76+
.catch((e) => console.error(e))
77+
}
78+
79+
if (deploymentStage?.items) {
80+
// remove current toast to avoid flood of multiple toasts
81+
toastAction.remove()
82+
// dispatch action
83+
dispatchServiceToDeployment(newStage)
84+
}
85+
}
86+
87+
return (
88+
<PageSettingsDeploymentPipeline
89+
stages={stages}
90+
setStages={setStages}
91+
onSubmit={onSubmit}
92+
services={[...applications, ...databases]}
93+
/>
94+
)
95+
}
96+
97+
export default PageSettingsDeploymentPipelineFeature

0 commit comments

Comments
 (0)