Skip to content

Commit 3b49228

Browse files
committed
adds view state for release pipelines
1 parent f6af9c2 commit 3b49228

20 files changed

Lines changed: 608 additions & 401 deletions

frontend/common/services/useReleasePipelines.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { service } from 'common/service'
22
import { Req } from 'common/types/requests'
33
import { Res } from 'common/types/responses'
4+
import Utils from 'common/utils/utils'
45

56
export const releasePipelinesService = service
67
.enhanceEndpoints({ addTagTypes: ['ReleasePipelines'] })
@@ -14,13 +15,13 @@ export const releasePipelinesService = service
1415
query: (query: Req['createPipelineStage']) => ({
1516
body: {
1617
actions: query.actions,
17-
environment: query.environmentId,
18+
environment: query.environment,
1819
name: query.name,
1920
order: query.order,
2021
triggers: query.triggers,
2122
},
2223
method: 'POST',
23-
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/stages/`,
24+
url: `projects/${query.project}/release-pipelines/${query.pipeline}/stages/`,
2425
}),
2526
}),
2627
createReleasePipeline: builder.mutation<
@@ -48,32 +49,40 @@ export const releasePipelinesService = service
4849
Req['getPipelineStage']
4950
>({
5051
query: (query: Req['getPipelineStage']) => ({
51-
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/stages/${query.stageId}`,
52+
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/stages/${query.stageId}/`,
5253
}),
5354
}),
5455
getPipelineStages: builder.query<
5556
Res['pipelineStages'],
5657
Req['getPipelineStages']
5758
>({
58-
query: (query: Req['getPipelineStages']) => ({
59-
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/stages/`,
59+
query: ({
60+
pipelineId,
61+
projectId,
62+
...rest
63+
}: Req['getPipelineStages']) => ({
64+
url: `projects/${projectId}/release-pipelines/${pipelineId}/stages/?${Utils.toParam(
65+
rest,
66+
)}`,
6067
}),
6168
}),
6269
getReleasePipeline: builder.query<
6370
Res['releasePipeline'],
6471
Req['getReleasePipeline']
6572
>({
6673
query: (query: Req['getReleasePipeline']) => ({
67-
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}`,
74+
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/`,
6875
}),
6976
}),
7077
getReleasePipelines: builder.query<
7178
Res['releasePipelines'],
7279
Req['getReleasePipelines']
7380
>({
7481
providesTags: [{ id: 'LIST', type: 'ReleasePipelines' }],
75-
query: (query: Req['getReleasePipelines']) => ({
76-
url: `projects/${query.projectId}/release-pipelines/`,
82+
query: ({ projectId, ...rest }: Req['getReleasePipelines']) => ({
83+
url: `projects/${projectId}/release-pipelines/?${Utils.toParam(
84+
rest,
85+
)}`,
7786
}),
7887
}),
7988
// END OF ENDPOINTS

frontend/common/types/requests.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
IdentityTrait,
2121
StageTrigger,
2222
StageAction,
23+
PipelineStatus,
2324
} from './responses'
2425

2526
export type PagedRequest<T> = T & {
@@ -679,6 +680,7 @@ export type Req = {
679680
createReleasePipeline: {
680681
projectId: number
681682
name: string
683+
status: PipelineStatus
682684
}
683685
getPipelineStages: PagedRequest<{
684686
projectId: number
@@ -691,12 +693,12 @@ export type Req = {
691693
}
692694
createPipelineStage: {
693695
name: string
694-
projectId: number
695-
environmentId: number
696-
pipelineId: number
696+
project: number
697+
environment: number
698+
pipeline: number
697699
order: number
698-
triggers: Omit<StageTrigger, 'id'>[]
699-
actions: Omit<StageAction, 'id'>[]
700+
triggers: StageTrigger
701+
actions: StageAction[]
700702
}
701703
deleteReleasePipeline: {
702704
projectId: number

frontend/common/types/responses.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -813,14 +813,17 @@ export type ReleasePipeline = {
813813
}
814814

815815
export type StageTrigger = {
816-
id: number
817816
trigger_type: 'ON_ENTER'
818817
trigger_body: string | null
819818
}
820819

820+
export enum StageActionType {
821+
ENABLE_FEATURE = 'ENABLE_FEATURE',
822+
DISABLE_FEATURE = 'DISABLE_FEATURE',
823+
}
824+
821825
export type StageAction = {
822-
id: number
823-
action_type: 'ENABLE_FEATURE' | 'DISABLE_FEATURE'
826+
action_type: StageActionType
824827
action_body: string | null
825828
}
826829

@@ -830,7 +833,7 @@ export type PipelineStage = {
830833
pipeline: number
831834
environment: number
832835
order: number
833-
triggers: StageTrigger[]
836+
triggers: StageTrigger
834837
actions: StageAction[]
835838
}
836839

frontend/web/components/App.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -613,23 +613,27 @@ const App = class extends Component {
613613
)
614614
}
615615
</Permission>
616-
<Permission
617-
level='project'
618-
permission='ADMIN'
619-
id={projectId}
620-
>
621-
{({ permission }) =>
622-
permission && (
623-
<NavSubLink
624-
icon={<Icon name='flash' />}
625-
id='release-pipelines-link'
626-
to={`/project/${projectId}/release-pipelines`}
627-
>
628-
Release Pipelines
629-
</NavSubLink>
630-
)
631-
}
632-
</Permission>
616+
{Utils.getFlagsmithHasFeature(
617+
'release_pipelines',
618+
) && (
619+
<Permission
620+
level='project'
621+
permission='ADMIN'
622+
id={projectId}
623+
>
624+
{({ permission }) =>
625+
permission && (
626+
<NavSubLink
627+
icon={<Icon name='flash' />}
628+
id='release-pipelines-link'
629+
to={`/project/${projectId}/release-pipelines`}
630+
>
631+
Release Pipelines
632+
</NavSubLink>
633+
)
634+
}
635+
</Permission>
636+
)}
633637
</>
634638
) : (
635639
!!AccountStore.getOrganisation() && (

frontend/web/components/base/DropdownMenu.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Icon, { IconName } from 'components/Icon'
33
import classNames from 'classnames'
44
import useOutsideClick from 'common/useOutsideClick'
55
import { createPortal } from 'react-dom'
6-
import Button from './forms/Button'
76

87
type MenuItem = {
98
icon?: IconName
@@ -63,7 +62,10 @@ const DropdownMenu: React.FC<DropdownMenuProps> = ({
6362
<div className={classNames('feature-action', className)} tabIndex={-1}>
6463
<button
6564
className={classNames('btn btn-link p-0', buttonClassName)}
66-
onClick={() => setIsOpen(!isOpen)}
65+
onClick={(e) => {
66+
e.stopPropagation()
67+
setIsOpen(!isOpen)
68+
}}
6769
ref={btnRef}
6870
>
6971
<Icon
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import ReleasePipelineDetail from 'components/release-pipelines/ReleasePipelineDetail'
2+
3+
type ReleasePipelineDetailPageType = {
4+
match: {
5+
params: {
6+
projectId: string
7+
id: string
8+
}
9+
}
10+
}
11+
12+
export default function ReleasePipelineDetailPage({
13+
match,
14+
}: ReleasePipelineDetailPageType) {
15+
const { projectId } = match.params
16+
return <ReleasePipelineDetail projectId={projectId} id={match.params.id} />
17+
}

frontend/web/components/pages/ReleasePipelinesPage.tsx

Lines changed: 26 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import PageTitle from 'components/PageTitle'
2-
import {
3-
useDeleteReleasePipelineMutation,
4-
useGetReleasePipelinesQuery,
5-
} from 'common/services/useReleasePipelines'
2+
import { useGetReleasePipelinesQuery } from 'common/services/useReleasePipelines'
63
import { Button } from 'components/base/forms/Button'
74
import { RouterChildContext } from 'react-router'
85
import ConfigProvider from 'common/providers/ConfigProvider'
9-
import { ReleasePipeline } from 'common/types/responses'
10-
import Card from 'components/Card'
11-
import DropdownMenu from 'components/base/DropdownMenu'
6+
import ReleasePipelinesList from 'components/release-pipelines/ReleasePipelinesList'
7+
import { useState } from 'react'
128

139
type ReleasePipelinesPageType = {
1410
router: RouterChildContext['router']
@@ -20,123 +16,47 @@ type ReleasePipelinesPageType = {
2016
}
2117
}
2218

23-
const NoReleasePipelines = ({
24-
projectId,
25-
router,
26-
}: {
27-
router: RouterChildContext['router']
28-
projectId: string
29-
}) => {
30-
return (
31-
<div className='mt-5 text-center'>
32-
<p>
33-
Create release pipelines to automate and standardize your release
34-
process throughout your organization.
35-
</p>
36-
<Row className='align-items-center justify-content-center gap-3'>
37-
<Button
38-
onClick={() =>
39-
router.history.push(
40-
`/project/${projectId}/release-pipelines/create`,
41-
)
42-
}
43-
>
44-
Create release pipeline
45-
</Button>
46-
<Button theme='outline'>Learn more</Button>
47-
</Row>
48-
</div>
49-
)
50-
}
51-
52-
type ReleasePipelinesPageContentProps = {
53-
data: ReleasePipeline[] | undefined
54-
isLoading: boolean
55-
projectId: string
56-
router: RouterChildContext['router']
57-
}
58-
59-
const ReleasePipelinesPageContent = ({
60-
data,
61-
isLoading,
62-
projectId,
63-
router,
64-
}: ReleasePipelinesPageContentProps) => {
65-
const [deleteReleasePipeline] = useDeleteReleasePipelineMutation()
66-
67-
if (isLoading) {
68-
return (
69-
<div className='text-center'>
70-
<Loader />
71-
</div>
72-
)
73-
}
74-
75-
if (!data?.length) {
76-
return <NoReleasePipelines router={router} projectId={projectId} />
77-
}
78-
79-
return (
80-
<>
81-
{data?.map((pipeline) => (
82-
<Card
83-
contentClassName='bg-light200 bg-white'
84-
className='rounded position-relative border-1 px-2'
85-
key={pipeline.id}
86-
>
87-
<Row className='align-items-center justify-content-between'>
88-
<span className='fw-bold'>{pipeline.name}</span>
89-
<Row className=' gap-3'>
90-
<div className='text-center'>
91-
<div className='fw-bold'>{pipeline.stages_count ?? 0}</div>
92-
<div>Stages</div>
93-
</div>
94-
<div className='text-center'>
95-
<div className='fw-bold'>{pipeline.flags_count ?? 0}</div>
96-
<div>Flags</div>
97-
</div>
98-
<DropdownMenu
99-
items={[
100-
{
101-
icon: 'trash-2',
102-
label: 'Remove Release Pipeline',
103-
onClick: () =>
104-
deleteReleasePipeline({
105-
pipelineId: pipeline.id,
106-
projectId: Number(projectId),
107-
}),
108-
},
109-
]}
110-
/>
111-
</Row>
112-
</Row>
113-
</Card>
114-
))}
115-
</>
116-
)
117-
}
118-
11919
const ReleasePipelinesPage = ({ match, router }: ReleasePipelinesPageType) => {
20+
const [page, setPage] = useState(1)
21+
const pageSize = 10
12022
const { projectId } = match.params
12123
const { data, isLoading } = useGetReleasePipelinesQuery({
24+
page,
25+
page_size: pageSize,
12226
projectId: Number(projectId),
12327
})
28+
12429
const hasReleasePipelines = !!data?.results?.length
12530

12631
return (
12732
<div className='app-container container'>
12833
<PageTitle
12934
title={'Release Pipelines'}
130-
cta={hasReleasePipelines && <Button>Create release pipeline</Button>}
35+
cta={
36+
hasReleasePipelines && (
37+
<Button
38+
onClick={() =>
39+
router.history.push(
40+
`/project/${projectId}/release-pipelines/create`,
41+
)
42+
}
43+
>
44+
Create Release Pipeline
45+
</Button>
46+
)
47+
}
13148
>
13249
{hasReleasePipelines &&
13350
'Define the stages your flags should go from development to launched. Learn more.'}
13451
</PageTitle>
135-
<ReleasePipelinesPageContent
136-
data={data?.results}
52+
<ReleasePipelinesList
53+
data={data}
13754
isLoading={isLoading}
13855
projectId={projectId}
13956
router={router}
57+
page={page}
58+
pageSize={pageSize}
59+
onPageChange={setPage}
14060
/>
14161
</div>
14262
)

0 commit comments

Comments
 (0)