Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions frontend/common/services/useReleasePipelines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { service } from 'common/service'
import { Req } from 'common/types/requests'
import { Res } from 'common/types/responses'
import Utils from 'common/utils/utils'

export const releasePipelinesService = service
.enhanceEndpoints({ addTagTypes: ['ReleasePipelines'] })
.injectEndpoints({
endpoints: (builder) => ({
createPipelineStages: builder.mutation<
Res['pipelineStage'],
Req['createPipelineStage']
>({
invalidatesTags: [{ id: 'LIST', type: 'ReleasePipelines' }],
query: (query: Req['createPipelineStage']) => ({
body: {
actions: query.actions,
environment: query.environment,
name: query.name,
order: query.order,
trigger: query.trigger,
},
method: 'POST',
url: `projects/${query.project}/release-pipelines/${query.pipeline}/stages/`,
}),
}),
createReleasePipeline: builder.mutation<
Res['releasePipeline'],
Req['createReleasePipeline']
>({
invalidatesTags: [{ id: 'LIST', type: 'ReleasePipelines' }],
query: (query: Req['createReleasePipeline']) => ({
body: { name: query.name },
method: 'POST',
url: `projects/${query.projectId}/release-pipelines/`,
}),
}),
deleteReleasePipeline: builder.mutation<{}, Req['deleteReleasePipeline']>(
{
invalidatesTags: [{ id: 'LIST', type: 'ReleasePipelines' }],
query: (query: Req['deleteReleasePipeline']) => ({
method: 'DELETE',
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/`,
}),
},
),
getPipelineStage: builder.query<
Res['pipelineStage'],
Req['getPipelineStage']
>({
query: (query: Req['getPipelineStage']) => ({
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/stages/${query.stageId}/`,
}),
}),
getPipelineStages: builder.query<
Res['pipelineStages'],
Req['getPipelineStages']
>({
query: ({
pipelineId,
projectId,
...rest
}: Req['getPipelineStages']) => ({
url: `projects/${projectId}/release-pipelines/${pipelineId}/stages/?${Utils.toParam(
rest,
)}`,
}),
}),
getReleasePipeline: builder.query<
Res['releasePipeline'],
Req['getReleasePipeline']
>({
query: (query: Req['getReleasePipeline']) => ({
url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/`,
}),
}),
getReleasePipelines: builder.query<
Res['releasePipelines'],
Req['getReleasePipelines']
>({
providesTags: [{ id: 'LIST', type: 'ReleasePipelines' }],
query: ({ projectId, ...rest }: Req['getReleasePipelines']) => ({
url: `projects/${projectId}/release-pipelines/?${Utils.toParam(
rest,
)}`,
}),
}),
// END OF ENDPOINTS
}),
})

export async function getReleasePipelines(
store: any,
data: Req['getReleasePipelines'],
options?: Parameters<
typeof releasePipelinesService.endpoints.getReleasePipelines.initiate
>[1],
) {
return store.dispatch(
releasePipelinesService.endpoints.getReleasePipelines.initiate(
data,
options,
),
)
}

export async function createReleasePipeline(
store: any,
data: Req['createReleasePipeline'],
options?: Parameters<
typeof releasePipelinesService.endpoints.createReleasePipeline.initiate
>[1],
) {
return store.dispatch(
releasePipelinesService.endpoints.createReleasePipeline.initiate(
data,
options,
),
)
}

export async function getPipelineStages(
store: any,
data: Req['getPipelineStages'],
options?: Parameters<
typeof releasePipelinesService.endpoints.getPipelineStages.initiate
>[1],
) {
return store.dispatch(
releasePipelinesService.endpoints.getPipelineStages.initiate(data, options),
)
}

export async function createPipelineStages(
store: any,
data: Req['createPipelineStage'],
options?: Parameters<
typeof releasePipelinesService.endpoints.createPipelineStages.initiate
>[1],
) {
return store.dispatch(
releasePipelinesService.endpoints.createPipelineStages.initiate(
data,
options,
),
)
}

// END OF FUNCTION_EXPORTS

export const {
useCreatePipelineStagesMutation,
useCreateReleasePipelineMutation,
useDeleteReleasePipelineMutation,
useGetPipelineStageQuery,
useGetPipelineStagesQuery,
useGetReleasePipelineQuery,
useGetReleasePipelinesQuery,
// END OF EXPORTS
} = releasePipelinesService

/* Usage examples:
const { data, isLoading } = useGetReleasePipelinesQuery({ id: 2 }, {}) //get hook
const [createReleasePipelines, { isLoading, data, isSuccess }] = useCreateReleasePipelinesMutation() //create hook
releasePipelinesService.endpoints.getReleasePipelines.select({id: 2})(store.getState()) //access data from any function
*/
38 changes: 38 additions & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {
RolePermission,
Webhook,
IdentityTrait,
StageTrigger,
PipelineStatus,
StageActionType,
} from './responses'

export type PagedRequest<T> = T & {
Expand Down Expand Up @@ -68,6 +71,12 @@ export type RegisterRequest = {
organisation_name?: string
marketing_consent_given?: boolean
}

export interface StageActionRequest {
action_type: StageActionType
action_body: { enabled: boolean; segment_id?: number }
}

export type Req = {
getSegments: PagedRequest<{
q?: string
Expand Down Expand Up @@ -673,5 +682,34 @@ export type Req = {
userId: number | undefined
level: PermissionLevel
}
getReleasePipelines: PagedRequest<{ projectId: number }>
getReleasePipeline: { projectId: number; pipelineId: number }
createReleasePipeline: {
projectId: number
name: string
status: PipelineStatus
}
getPipelineStages: PagedRequest<{
projectId: number
pipelineId: number
}>
getPipelineStage: {
projectId: number
pipelineId: number
stageId: number
}
createPipelineStage: {
name: string
project: number
environment: number
pipeline: number
order: number
trigger: StageTrigger
actions: StageActionRequest[]
}
deleteReleasePipeline: {
projectId: number
pipelineId: number
}
// END OF TYPES
}
52 changes: 52 additions & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface

import { StageActionRequest } from './requests'

export type EdgePagedResponse<T> = PagedResponse<T> & {
last_evaluated_key?: string
pages?: (string | undefined)[]
Expand Down Expand Up @@ -798,6 +800,52 @@ export type IdentityTrait = {
trait_value: FlagsmithValue
}

export enum PipelineStatus {
DRAFT = 'DRAFT',
ACTIVE = 'ACTIVE',
}

export type ReleasePipeline = {
id: number
status: PipelineStatus
stages_count: number
flags_count: number
name: string
project: number
}

export enum StageTriggerType {
ON_ENTER = 'ON_ENTER',
WAIT_FOR = 'WAIT_FOR',
}

export type StageTriggerBody = string | null

export type StageTrigger = {
trigger_type: StageTriggerType
trigger_body: StageTriggerBody
}

export enum StageActionType {
TOGGLE_FEATURE = 'TOGGLE_FEATURE',
TOGGLE_FEATURE_FOR_SEGMENT = 'TOGGLE_FEATURE_FOR_SEGMENT',
}
// TODO: Check if this is correct
export interface StageActionResponse
extends Omit<StageActionRequest, 'action_body'> {
action_body: string
}

export type PipelineStage = {
id: number
name: string
pipeline: number
environment: number
order: number
trigger: StageTrigger
actions: StageActionResponse[]
}

export type Res = {
segments: PagedResponse<Segment>
segment: Segment
Expand Down Expand Up @@ -936,5 +984,9 @@ export type Res = {
splitTest: PagedResponse<SplitTestResult>
onboardingSupportOptIn: { id: string }
userPermissions: UserPermissions
releasePipelines: PagedResponse<ReleasePipeline>
releasePipeline: ReleasePipeline
pipelineStages: PagedResponse<PipelineStage>
pipelineStage: PipelineStage
// END OF TYPES
}
8 changes: 8 additions & 0 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,14 @@ const Utils = Object.assign({}, require('./base/_utils'), {
.replace(/[\s_]+/g, '-')
.toLowerCase(),

toSelectedValue: (
value: string,
options: { label: string; value: string }[],
defaultValue?: string,
) => {
return options?.find((option) => option.value === value) ?? defaultValue
},

validateMetadataType(type: string, value: any) {
switch (type) {
case 'int': {
Expand Down
21 changes: 21 additions & 0 deletions frontend/web/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,27 @@ const App = class extends Component {
)
}
</Permission>
{Utils.getFlagsmithHasFeature(
'release_pipelines',
) && (
<Permission
level='project'
permission='ADMIN'
id={projectId}
>
{({ permission }) =>
permission && (
<NavSubLink
icon={<Icon name='flash' />}
id='release-pipelines-link'
to={`/project/${projectId}/release-pipelines`}
>
Release Pipelines
</NavSubLink>
)
}
</Permission>
)}
</>
) : (
!!AccountStore.getOrganisation() && (
Expand Down
Loading
Loading