Skip to content
12 changes: 12 additions & 0 deletions frontend/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,18 @@ const Constants = {
resourceType: 'pulls',
type: 'GITHUB',
},
GITLAB_ISSUE: {
id: 3,
label: 'Issue',
resourceType: 'issue',
type: 'GITLAB',
},
GITLAB_MR: {
id: 4,
label: 'Merge Request',
resourceType: 'merge_request',
type: 'GITLAB',
},
},
roles: {
'ADMIN': 'Organisation Administrator',
Expand Down
9 changes: 9 additions & 0 deletions frontend/common/hooks/useHasGitLabIntegration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useGetGitLabConfigurationQuery } from 'common/services/useGitlabConfiguration'

export function useHasGitLabIntegration(projectId: number) {
const { data } = useGetGitLabConfigurationQuery(
{ project_id: projectId },
{ skip: !projectId },
)
return { hasIntegration: !!data?.length }
}
Comment thread
Zaimwa9 marked this conversation as resolved.
98 changes: 98 additions & 0 deletions frontend/common/services/useGitlab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import Utils from 'common/utils/utils'

export const gitlabService = service
.enhanceEndpoints({ addTagTypes: ['GitLab'] })
.injectEndpoints({
endpoints: (builder) => ({
getGitLabIssues: builder.query<
Res['gitlabIssues'],
Req['getGitLabIssues']
>({
providesTags: [{ id: 'LIST', type: 'GitLab' }],
query: (query: Req['getGitLabIssues']) => ({
url: `projects/${query.project_id}/gitlab/issues/?${Utils.toParam({
gitlab_project_id: query.gitlab_project_id,
page: query.page ?? 1,
page_size: query.page_size ?? 100,
search_text: query.q || undefined,
state: 'opened', // Only open items are linkable to feature flags.
})}`,
}),
}),
getGitLabMergeRequests: builder.query<
Res['gitlabMergeRequests'],
Req['getGitLabMergeRequests']
>({
providesTags: [{ id: 'LIST', type: 'GitLab' }],
query: (query: Req['getGitLabMergeRequests']) => ({
url: `projects/${
query.project_id
}/gitlab/merge-requests/?${Utils.toParam({
gitlab_project_id: query.gitlab_project_id,
page: query.page ?? 1,
page_size: query.page_size ?? 100,
search_text: query.q || undefined,
state: 'opened', // Only open items are linkable to feature flags.
})}`,
}),
}),
getGitLabProjects: builder.query<
Res['gitlabProjects'],
Req['getGitLabProjects']
>({
providesTags: [{ id: 'LIST', type: 'GitLab' }],
query: (query: Req['getGitLabProjects']) => ({
url: `projects/${query.project_id}/gitlab/projects/?${Utils.toParam({
page: query.page ?? 1,
page_size: query.page_size ?? 100,
})}`,
}),
}),
// END OF ENDPOINTS
}),
})

export async function getGitLabProjects(
store: any,
data: Req['getGitLabProjects'],
options?: Parameters<
typeof gitlabService.endpoints.getGitLabProjects.initiate
>[1],
) {
return store.dispatch(
gitlabService.endpoints.getGitLabProjects.initiate(data, options),
)
}
export async function getGitLabIssues(
store: any,
data: Req['getGitLabIssues'],
options?: Parameters<
typeof gitlabService.endpoints.getGitLabIssues.initiate
>[1],
) {
return store.dispatch(
gitlabService.endpoints.getGitLabIssues.initiate(data, options),
)
}
export async function getGitLabMergeRequests(
store: any,
data: Req['getGitLabMergeRequests'],
options?: Parameters<
typeof gitlabService.endpoints.getGitLabMergeRequests.initiate
>[1],
) {
return store.dispatch(
gitlabService.endpoints.getGitLabMergeRequests.initiate(data, options),
)
}
// END OF FUNCTION_EXPORTS

export const {
useGetGitLabIssuesQuery,
useGetGitLabMergeRequestsQuery,
useGetGitLabProjectsQuery,
// END OF EXPORTS
} = gitlabService
46 changes: 46 additions & 0 deletions frontend/common/services/useGitlabConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'

export const gitlabConfigurationService = service
.enhanceEndpoints({ addTagTypes: ['GitLabConfiguration'] })
.injectEndpoints({
endpoints: (builder) => ({
getGitLabConfiguration: builder.query<
Res['gitlabConfiguration'],
Req['getGitLabConfiguration']
>({
providesTags: [{ id: 'LIST', type: 'GitLabConfiguration' }],
query: (query: Req['getGitLabConfiguration']) => ({
url: `projects/${query.project_id}/integrations/gitlab/`,
}),
}),
// END OF ENDPOINTS
}),
})

export async function getGitLabConfiguration(
store: any,
data: Req['getGitLabConfiguration'],
options?: Parameters<
typeof gitlabConfigurationService.endpoints.getGitLabConfiguration.initiate
>[1],
) {
return store.dispatch(
gitlabConfigurationService.endpoints.getGitLabConfiguration.initiate(
data,
options,
),
)
}
// END OF FUNCTION_EXPORTS

export const {
useGetGitLabConfigurationQuery,
// END OF EXPORTS
} = gitlabConfigurationService

/* Usage examples:
const { data, isLoading } = useGetGitLabConfigurationQuery({ project_id: 2 }, {}) //get hook
gitlabConfigurationService.endpoints.getGitLabConfiguration.select({project_id: 2})(store.getState()) //access data from any function
*/
10 changes: 10 additions & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,5 +936,15 @@ export type Req = {
code_challenge_method: string
state?: string
}
getGitLabConfiguration: { project_id: number }
getGitLabProjects: PagedRequest<{ project_id: number }>
getGitLabIssues: PagedRequest<{
project_id: number
gitlab_project_id: number
}>
getGitLabMergeRequests: PagedRequest<{
project_id: number
gitlab_project_id: number
}>
// END OF TYPES
}
35 changes: 35 additions & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,37 @@ export type GithubResource = {
draft: boolean
}

export type GitLabConfiguration = {
id: number
gitlab_instance_url: string
}

export type GitLabProject = {
id: number
name: string
path_with_namespace: string
}

export type GitLabIssue = {
web_url: string
id: number
title: string
iid: number
state: string
}

export type GitLabMergeRequest = {
web_url: string
id: number
title: string
iid: number
state: string
merged: boolean
draft: boolean
}

export type GitLabLinkType = 'issue' | 'merge_request'

export type GithubPaginatedRepos<T> = {
total_count: number
repository_selection: string
Expand Down Expand Up @@ -1275,5 +1306,9 @@ export type Res = {
processOAuthConsent: {
redirect_uri: string
}
gitlabConfiguration: GitLabConfiguration[]
gitlabProjects: PagedResponse<GitLabProject>
gitlabIssues: PagedResponse<GitLabIssue>
gitlabMergeRequests: PagedResponse<GitLabMergeRequest>
// END OF TYPES
}
99 changes: 99 additions & 0 deletions frontend/web/components/GitLabLinkSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { FC, useState } from 'react'
import Constants from 'common/constants'
import ErrorMessage from './ErrorMessage'
import GitLabProjectSelect from './GitLabProjectSelect'
import GitLabSearchSelect from './GitLabSearchSelect'
import { useGetGitLabProjectsQuery } from 'common/services/useGitlab'
import type {
GitLabIssue,
GitLabLinkType,
GitLabMergeRequest,
} from 'common/types/responses'

type GitLabLinkSectionProps = {
projectId: number
linkedUrls: string[]
}

const GitLabLinkSection: FC<GitLabLinkSectionProps> = ({
linkedUrls,
projectId,
}) => {
const gitlabTypes = Object.values(Constants.resourceTypes).filter(
(v) => v.type === 'GITLAB',
)

const [gitlabProjectId, setGitlabProjectId] = useState<number | null>(null)
const [linkType, setLinkType] = useState<GitLabLinkType>('issue')
const [selectedItem, setSelectedItem] = useState<
GitLabIssue | GitLabMergeRequest | null
>(null)

const {
data: projectsData,
isError: isProjectsError,
isLoading: isProjectsLoading,
} = useGetGitLabProjectsQuery({
page: 1,
page_size: 100,
project_id: projectId,
})
const projects = projectsData?.results ?? []

return (
<div>
<label className='cols-sm-2 control-label'>
Link GitLab issue or merge request
</label>
<div className='d-flex gap-2 mb-2'>
<GitLabProjectSelect
projects={projects}
isLoading={isProjectsLoading}
isDisabled={isProjectsError}
value={gitlabProjectId}
onChange={setGitlabProjectId}
/>
<div style={{ width: 200 }}>
<Select
autoSelect
className='w-100 react-select'
size='select-md'
placeholder='Select type'
value={gitlabTypes.find((v) => v.resourceType === linkType)}
onChange={(v: { resourceType: GitLabLinkType }) => {
setLinkType(v.resourceType)
setSelectedItem(null)
}}
options={gitlabTypes.map((e) => ({
label: e.label,
resourceType: e.resourceType,
value: e.id,
}))}
/>
</div>
</div>
{isProjectsError && (
<ErrorMessage error='Failed to load GitLab projects' />
)}
Comment thread
Zaimwa9 marked this conversation as resolved.
{gitlabProjectId != null && (
<>
<GitLabSearchSelect
projectId={projectId}
gitlabProjectId={gitlabProjectId}
linkType={linkType}
value={selectedItem}
onChange={(item) => setSelectedItem(item)}
linkedUrls={linkedUrls}
/>
<div className='text-right mt-2'>
<Button disabled theme='primary'>
Link
</Button>
</div>
</>
)}
</div>
)
}

export default GitLabLinkSection
40 changes: 40 additions & 0 deletions frontend/web/components/GitLabProjectSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { FC } from 'react'
import type { GitLabProject } from 'common/types/responses'

type GitLabProjectSelectProps = {
projects: GitLabProject[]
isLoading: boolean
isDisabled: boolean
value: number | null
onChange: (id: number) => void
}

const GitLabProjectSelect: FC<GitLabProjectSelectProps> = ({
isDisabled,
isLoading,
onChange,
projects,
value,
}) => {
const options = projects.map((p) => ({
label: p.path_with_namespace,
value: p.id,
}))

return (
<div style={{ minWidth: 250 }}>
<Select
className='w-100 react-select'
size='select-md'
placeholder={isLoading ? 'Loading...' : 'Select GitLab Project'}
value={options.find((o) => o.value === value) ?? null}
onChange={(v: { value: number }) => onChange(v.value)}
options={options}
isLoading={isLoading}
isDisabled={isDisabled}
/>
</div>
)
}

export default GitLabProjectSelect
Loading
Loading