Skip to content

Commit 57489b7

Browse files
authored
feat: adds multiple actions to release pipeline stage (#5767)
1 parent 759c789 commit 57489b7

10 files changed

Lines changed: 194 additions & 94 deletions

File tree

frontend/common/services/useReleasePipelines.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ export const releasePipelinesService = service
4848
Res['releasePipeline'],
4949
Req['getReleasePipeline']
5050
>({
51-
providesTags: [{ type: 'ReleasePipelines' }],
51+
providesTags: (result, error, { pipelineId }) => [
52+
{ id: pipelineId, type: 'ReleasePipelines' },
53+
],
5254
query: (query: Req['getReleasePipeline']) => ({
5355
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/`,
5456
}),
@@ -91,10 +93,12 @@ export const releasePipelinesService = service
9193
Res['releasePipeline'],
9294
Req['updateReleasePipeline']
9395
>({
94-
invalidatesTags: (res) => [
95-
{ id: 'LIST', type: 'ReleasePipelines' },
96-
{ id: res?.id, type: 'ReleasePipelines' },
97-
],
96+
invalidatesTags: (res) => {
97+
return [
98+
{ id: res?.id, type: 'ReleasePipelines' },
99+
{ id: 'LIST', type: 'ReleasePipelines' },
100+
]
101+
},
98102
query: (query: Req['updateReleasePipeline']) => ({
99103
body: {
100104
description: query.description,

frontend/common/types/requests.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ export type RegisterRequest = {
7474
utm_data?: UtmsType
7575
}
7676

77+
export type StageActionBody = { enabled: boolean; segment_id?: number }
7778
export interface StageActionRequest {
78-
action_type: StageActionType
79-
action_body: { enabled: boolean; segment_id?: number }
79+
action_type: StageActionType | ''
80+
action_body: StageActionBody
8081
}
8182

8283
export interface ReleasePipelineRequest {
@@ -90,7 +91,7 @@ export interface UpdateReleasePipelineRequest extends ReleasePipelineRequest {
9091
id: number
9192
}
9293

93-
export type PipelineStageRequest = {
94+
export interface PipelineStageRequest {
9495
name: string
9596
environment: number
9697
order: number

frontend/web/components/release-pipelines/CreatePipelineStage.tsx

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ import {
99
} from 'common/types/responses'
1010
import Button from 'components/base/forms/Button'
1111
import Icon from 'components/Icon'
12-
import { TIME_UNIT_OPTIONS, TimeUnit, TRIGGER_OPTIONS } from './constants'
13-
import { StageAction } from 'common/types/responses'
12+
import {
13+
NEW_PIPELINE_STAGE,
14+
NEW_PIPELINE_STAGE_ACTION,
15+
TIME_UNIT_OPTIONS,
16+
TimeUnit,
17+
TRIGGER_OPTIONS,
18+
} from './constants'
1419
import moment from 'moment'
1520
import Input from 'components/base/forms/Input'
16-
import { PipelineStageRequest } from 'common/types/requests'
21+
import { PipelineStageRequest, StageActionRequest } from 'common/types/requests'
1722
import PipelineStageActions from './PipelineStageActions'
1823

19-
type DraftStageType = PipelineStageRequest
2024
type TimeUnitType = (typeof TimeUnit)[keyof typeof TimeUnit]
2125

2226
const CreatePipelineStage = ({
@@ -26,8 +30,8 @@ const CreatePipelineStage = ({
2630
showRemoveButton,
2731
stageData,
2832
}: {
29-
stageData: DraftStageType
30-
onChange: (stageData: DraftStageType) => void
33+
stageData: PipelineStageRequest
34+
onChange: (stageData: PipelineStageRequest) => void
3135
projectId: number
3236
showRemoveButton?: boolean
3337
onRemove?: () => void
@@ -73,8 +77,8 @@ const CreatePipelineStage = ({
7377
}, [environmentsData])
7478

7579
const handleOnChange = (
76-
fieldName: keyof DraftStageType,
77-
value: string | number | StageTrigger | Omit<StageAction, 'id'>[],
80+
fieldName: keyof PipelineStageRequest,
81+
value: string | number | StageTrigger | StageActionRequest[],
7882
) => {
7983
onChange({ ...stageData, [fieldName]: value })
8084
}
@@ -104,25 +108,36 @@ const CreatePipelineStage = ({
104108
// eslint-disable-next-line react-hooks/exhaustive-deps
105109
}, [environmentWithFeatureVersioningOptions, stageData])
106110

107-
const handleSegmentChange = (option: { value: number; label: string }) => {
108-
if (option?.value) {
109-
const actions = stageData.actions.map((action) => {
110-
if (action.action_type === StageActionType.TOGGLE_FEATURE_FOR_SEGMENT) {
111-
return {
112-
...action,
113-
action_body: { ...action.action_body, segment_id: option?.value },
114-
}
111+
const handleSegmentChange = (
112+
option: { value: number; label: string },
113+
actionIndex: number,
114+
) => {
115+
if (!option?.value) {
116+
return
117+
}
118+
119+
const actions = stageData.actions.map((action, idx) => {
120+
if (
121+
action.action_type === StageActionType.TOGGLE_FEATURE_FOR_SEGMENT &&
122+
idx === actionIndex
123+
) {
124+
return {
125+
...action,
126+
action_body: { ...action.action_body, segment_id: option?.value },
115127
}
116-
return action
117-
})
128+
}
129+
return action
130+
})
118131

119-
handleOnChange('actions', actions)
120-
}
132+
handleOnChange('actions', actions)
121133
}
122134

123-
const handleActionChange = (option: { value: string; label: string }) => {
135+
const handleActionChange = (
136+
option: { value: string; label: string },
137+
actionIndex: number,
138+
) => {
124139
if (option.value === '') {
125-
return handleOnChange('actions', [])
140+
return
126141
}
127142

128143
const isSegment = option.value.includes('FOR_SEGMENT')
@@ -134,7 +149,23 @@ const CreatePipelineStage = ({
134149

135150
const action_body = { enabled }
136151

137-
handleOnChange('actions', [{ action_body, action_type }])
152+
const actions = stageData.actions.map((action, idx) => {
153+
if (idx === actionIndex) {
154+
return { action_body, action_type }
155+
}
156+
return action
157+
})
158+
handleOnChange('actions', actions)
159+
}
160+
161+
const handleAddAction = () => {
162+
const defaultDraftAction = NEW_PIPELINE_STAGE_ACTION
163+
handleOnChange('actions', [...stageData.actions, defaultDraftAction])
164+
}
165+
166+
const handleRemoveAction = (index: number) => {
167+
const actions = stageData.actions.filter((_, i) => i !== index)
168+
handleOnChange('actions', actions)
138169
}
139170

140171
const setWaitForTrigger = (time: number, unit: TimeUnitType) => {
@@ -281,10 +312,11 @@ const CreatePipelineStage = ({
281312
projectId={projectId}
282313
onActionChange={handleActionChange}
283314
onSegmentChange={handleSegmentChange}
315+
onAddAction={handleAddAction}
316+
onRemoveAction={handleRemoveAction}
284317
/>
285318
</div>
286319
)
287320
}
288321

289-
export type { DraftStageType }
290322
export default CreatePipelineStage

frontend/web/components/release-pipelines/CreateReleasePipeline.tsx

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { useCallback, useEffect, useState } from 'react'
2-
import CreatePipelineStage, { DraftStageType } from './CreatePipelineStage'
2+
import CreatePipelineStage from './CreatePipelineStage'
33
import Breadcrumb from 'components/Breadcrumb'
44
import { Button } from 'components/base/forms/Button'
55
import PageTitle from 'components/PageTitle'
66
import InputGroup from 'components/base/forms/InputGroup'
77
import Utils from 'common/utils/utils'
8-
import { StageActionType, StageTriggerType } from 'common/types/responses'
98
import Icon from 'components/Icon'
109
import {
1110
useCreateReleasePipelineMutation,
@@ -14,20 +13,14 @@ import {
1413
} from 'common/services/useReleasePipelines'
1514
import { useHistory, useParams } from 'react-router-dom'
1615
import StageArrow from './StageArrow'
17-
import { ReleasePipelineRequest } from 'common/types/requests'
16+
import {
17+
PipelineStageRequest,
18+
ReleasePipelineRequest,
19+
} from 'common/types/requests'
1820
import { useRouteContext } from 'components/providers/RouteContext'
1921
import PlanBasedAccess from 'components/PlanBasedAccess'
20-
21-
const blankStage: DraftStageType = {
22-
actions: [],
23-
environment: -1,
24-
name: '',
25-
order: 0,
26-
trigger: {
27-
trigger_body: null,
28-
trigger_type: StageTriggerType.ON_ENTER,
29-
},
30-
}
22+
import { NEW_PIPELINE_STAGE, NEW_PIPELINE_STAGE_ACTION_TYPE } from './constants'
23+
import { StageActionType } from 'common/types/responses'
3124

3225
type CreateReleasePipelineParams = {
3326
id?: string
@@ -72,7 +65,7 @@ function CreateReleasePipeline() {
7265
const [pipelineData, setPipelineData] = useState<ReleasePipelineRequest>({
7366
name: '',
7467
project: Number(projectId),
75-
stages: [blankStage],
68+
stages: [NEW_PIPELINE_STAGE],
7669
})
7770

7871
const [isEditingName, setIsEditingName] = useState(
@@ -116,30 +109,42 @@ function CreateReleasePipeline() {
116109
])
117110

118111
useEffect(() => {
119-
console.log('existingPipeline', existingPipeline)
120112
if (existingPipeline) {
121113
setPipelineData(existingPipeline)
122114
}
123115
}, [existingPipeline])
124116

125-
const handleOnChange = (newStageData: DraftStageType, index: number) => {
117+
const handleOnChange = (
118+
newStageData: PipelineStageRequest,
119+
index: number,
120+
) => {
126121
const updatedStages = pipelineData.stages.map((stage, i) =>
127122
i === index ? newStageData : stage,
128123
)
129124
setPipelineData((prev) => ({ ...prev, stages: updatedStages }))
130125
}
131126

132-
const validateStage = (stage: DraftStageType) => {
127+
const validateStage = (stage: PipelineStageRequest) => {
133128
if (!stage.actions.length) {
134129
return false
135130
}
136131

137-
const segment = stage.actions.find(
132+
// action in creation state
133+
if (
134+
stage.actions.some(
135+
(action) => action.action_type === NEW_PIPELINE_STAGE_ACTION_TYPE,
136+
)
137+
) {
138+
return false
139+
}
140+
141+
const segments = stage.actions.filter(
138142
(action) =>
139143
action.action_type === StageActionType.TOGGLE_FEATURE_FOR_SEGMENT,
140144
)
141-
if (segment) {
142-
return !!segment.action_body.segment_id
145+
146+
if (segments.length) {
147+
return segments.every((segment) => !!segment.action_body.segment_id)
143148
}
144149

145150
return !!stage.name.length
@@ -196,7 +201,7 @@ function CreateReleasePipeline() {
196201
}
197202

198203
return (
199-
<div className='app-container container'>
204+
<div className='app-container container pb-0'>
200205
<PlanBasedAccess feature={'RELEASE_PIPELINES'} theme={'page'}>
201206
<Breadcrumb
202207
items={[
@@ -253,14 +258,14 @@ function CreateReleasePipeline() {
253258
</Button>
254259
}
255260
/>
256-
<div className='px-2 pb-4 overflow-auto'>
261+
<div className='release-pipeline-container px-2 overflow-auto'>
257262
<Row className='no-wrap'>
258263
{pipelineData.stages.map((stageData, index) => (
259264
<Row key={index}>
260265
<Row className='align-items-start no-wrap'>
261266
<CreatePipelineStage
262267
stageData={stageData}
263-
onChange={(stageData: DraftStageType) =>
268+
onChange={(stageData: PipelineStageRequest) =>
264269
handleOnChange(stageData, index)
265270
}
266271
projectId={Number(projectId)}
@@ -275,7 +280,7 @@ function CreateReleasePipeline() {
275280
onAddStage={() =>
276281
setPipelineData((prev) => ({
277282
...prev,
278-
stages: prev.stages.concat([blankStage]),
283+
stages: prev.stages.concat([NEW_PIPELINE_STAGE]),
279284
}))
280285
}
281286
/>

frontend/web/components/release-pipelines/FlagActionDetail.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { StageActionBody, StageActionType } from 'common/types/responses'
44
type FlagActionDetailProps = {
55
actionType: StageActionType
66
actionBody: StageActionBody
7-
projectId: string
7+
projectId: number
88
}
99

1010
const renderActionDetail = (
@@ -41,7 +41,7 @@ const FlagActionDetail = ({
4141
const { data: segmentData } = useGetSegmentQuery(
4242
{
4343
id: `${actionBody.segment_id}`,
44-
projectId: projectId,
44+
projectId: `${projectId}`,
4545
},
4646
{
4747
skip: !actionBody.segment_id || !isSegmentAction,

0 commit comments

Comments
 (0)