Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new MR cards and some updates to other tables #581

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions packages/gitlab/src/api/GitlabCIApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export type GitlabCIApi = {
getMergeRequestsSummary(
projectID: string | number
): Promise<MergeRequestSchema[] | undefined>;
getMergeRequestsAssignedToMe(): Promise<MergeRequestSchema[] | undefined>;
getMergeRequestsForGroup(groupName: string): Promise<MergeRequestSchema[] | undefined>;
getMergeRequestsAssignedToMeAsReviewer(): Promise<MergeRequestSchema[] | undefined>;
getPipelinesForMergeRequest(projectId: number | string, mergeRequestIid: number): Promise<PipelineSchema[] | undefined>;
getMergeRequestsStatusSummary(
projectID: string | number,
count: number
Expand Down
114 changes: 113 additions & 1 deletion packages/gitlab/src/api/GitlabCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export class GitlabCIClient implements GitlabCIApi {
...headers,
},
};
const fullUrl = `${apiUrl}${path ? `/${path}` : ''}?${new URLSearchParams(query).toString()}`;

//console.log('Making request to:', fullUrl);

const response = await fetch(
`${apiUrl}${path ? `/${path}` : ''}?${new URLSearchParams(
Expand Down Expand Up @@ -163,6 +166,105 @@ export class GitlabCIClient implements GitlabCIApi {
return pipelineObjects || undefined;
}


async getMergeRequestsAssignedToMe(): Promise<MergeRequestSchema[] | undefined> {
try {
const assigneeUsername = await this.getCurrentUser();

if (!assigneeUsername) {
throw new Error('Current user ID is undefined');
}


const assignedMergeRequests = await this.callApi<MergeRequestSchema[]>(
'merge_requests',
{ assignee_username: assigneeUsername, state: 'opened' }
);

if (!assignedMergeRequests || assignedMergeRequests.length === 0) {
console.log('No merge requests assigned to the current user.');
return undefined;
}

return assignedMergeRequests;
} catch (error) {
console.error('Error fetching assigned merge requests:', error);
return undefined;
}
}


async getMergeRequestsAssignedToMeAsReviewer(): Promise<MergeRequestSchema[] | undefined> {
try {
const reviewerId = await this.getCurrentUser();

if (!reviewerId) {
throw new Error('Current user ID is undefined');
}

const assignedReviewRequests = await this.callApi<MergeRequestSchema[]>(
'merge_requests',
{ reviewer_username: reviewerId, state: 'opened' }
);

if (!assignedReviewRequests || assignedReviewRequests.length === 0) {
console.log('No merge requests assigned to the current user as a reviewer.');
return undefined;
}

return assignedReviewRequests;
} catch (error) {
console.error('Error fetching assigned review requests:', error);
return undefined;
}
}


async getMergeRequestsForGroup(groupName: string): Promise<MergeRequestSchema[] | undefined> {
try {

const mergeRequests = await this.callApi<MergeRequestSchema[]>(
`groups/${encodeURIComponent(groupName)}/merge_requests`,
{ state: 'opened' }
);

if (!mergeRequests || mergeRequests.length === 0) {
console.log(`No merge requests found for group ${groupName}.`);
return undefined;
}

return mergeRequests;
} catch (error) {
console.error(`Error fetching merge requests for group ${groupName}:`, error);
return undefined;
}
}

async getCurrentUser(): Promise<string | undefined> {
try {
const identity = await this.identityApi.getBackstageIdentity();

if (!identity || !identity.userEntityRef) {
throw new Error('Failed to retrieve the current Backstage user identity');
}

// userEntityRef format is 'User:default/<user-id>', we split it to get the user ID
const userEntityRef = identity.userEntityRef;
const userId = userEntityRef.split('/').pop();

if (!userId) {
throw new Error('User ID could not be extracted');
}
console.log(`Backstage UserID: ${userId}`)
return userId;
} catch (error) {
console.error('Error fetching current Backstage user:', error);
return undefined;
}
}



async getIssuesSummary(
projectId: string | number
): Promise<IssueSchema[] | undefined> {
Expand All @@ -189,7 +291,6 @@ export class GitlabCIClient implements GitlabCIApi {
return projectObj?.name;
}

//TODO: Merge with getUserDetail
private async getUserProfilesData(
contributorsData: RepositoryContributorSchema[]
): Promise<ContributorsSummary> {
Expand Down Expand Up @@ -387,6 +488,17 @@ export class GitlabCIClient implements GitlabCIApi {
return owners;
}

async getPipelinesForMergeRequest(
projectId: string | number,
mergeRequestIid: number
): Promise<PipelineSchema[] | undefined> {
return this.callApi<PipelineSchema[]>(
`projects/${projectId}/merge_requests/${mergeRequestIid}/pipelines`,
{}
);
}


async getReadme(
projectID: string | number,
branch = 'HEAD',
Expand Down
12 changes: 12 additions & 0 deletions packages/gitlab/src/components/GitlabCI/GitlabCI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
ReleasesCard,
ReleasesCardProps,
CoverageCard,
MergeRequestsAssignedToMeCard,
MergeRequestsAssignedToReviewCard,
MergeRequestsForTeamBoard,
} from '../widgets';

export type GitlabPageProps = {
Expand Down Expand Up @@ -39,6 +42,15 @@ export const GitlabCI = (props: GitlabPageProps) => (
<Grid item md={12}>
<MergeRequestsTable />
</Grid>
<Grid item md={12}>
<MergeRequestsForTeamBoard />
</Grid>
<Grid item sm={12} md={3} lg={3}>
<MergeRequestsAssignedToMeCard />
</Grid>
<Grid item sm={12} md={3} lg={3}>
<MergeRequestsAssignedToReviewCard />
</Grid>
<Grid item md={12}>
<IssuesTable />
</Grid>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';
import { makeStyles, Tooltip } from '@material-ui/core';
import type { MergeRequestSchema } from '@gitbeaker/rest';
const useStyles = makeStyles(() => ({
open: {
fill: '#22863a',
},
closed: {
fill: '#cb2431',
},
merged: {
fill: '#6f42c1',
},
draft: {
fill: '#6a737d',
},
}));

const StatusOpen = () => {
const classes = useStyles();
return (
<svg
width="16"
height="16"
className={classes.open}
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"
/>
</svg>
);
};


const StatusClosed = () => {
const classes = useStyles();
return (
<svg
width="16"
height="16"
className={classes.closed}
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"
/>
</svg>
);
};

const StatusMerged = () => {
const classes = useStyles();
return (
<svg
width="16"
height="16"
className={classes.merged}
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
d="M5 3.254V3.25v.005a.75.75 0 110-.005v.004zm.45 1.9a2.25 2.25 0 10-1.95.218v5.256a2.25 2.25 0 101.5 0V7.123A5.735 5.735 0 009.25 9h1.378a2.251 2.251 0 100-1.5H9.25a4.25 4.25 0 01-3.8-2.346zM12.75 9a.75.75 0 100-1.5.75.75 0 000 1.5zm-8.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5z"
/>
</svg>
);
};

export const getStatusIconType = (row: MergeRequestSchema) => {
switch (true) {
case row.state === 'opened':
return (
<Tooltip title="Open">
<span>
<StatusOpen />
</span>
</Tooltip>
);
case row.state === 'locked':
return (
<Tooltip title="Open">
<span>
<StatusOpen />
</span>
</Tooltip>
);
case row.state === 'merged':
return (
<Tooltip title="Merged">
<span>
<StatusMerged />
</span>
</Tooltip>
);
case row.state === 'closed':
return (
<Tooltip title="Closed">
<span>
<StatusClosed />
</span>
</Tooltip>
);
default:
return null;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useState, useCallback } from 'react';
import { InfoCard, Progress, Link } from '@backstage/core-components';
import Alert from '@material-ui/lab/Alert';
import { useAsync } from 'react-use';
import { GitlabCIApiRef } from '../../../api';
import { useApi } from '@backstage/core-plugin-api';
import { getElapsedTime } from '../../utils';
import { List, Typography, Box, useTheme, Button } from '@material-ui/core';
import { getStatusIconType } from './Icons';
// import { gitlabInstance } from '../../gitlabAppData';

const MergeRequestsAssignedToMeCard = () => {
const theme = useTheme();
const gitlab_instance = 'gitlab.${internal-gitlab-domain}.com';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this... it should be taken by the Backstage entity

const GitlabCIAPI = useApi(GitlabCIApiRef).build(gitlab_instance);

const { value, loading, error } = useAsync(async () => {
const mergeRequests = await GitlabCIAPI.getMergeRequestsAssignedToMe();
return mergeRequests || [];
}, []);

const [collapsed, setCollapsed] = useState(true);


const onClick = useCallback(() => setCollapsed((prev) => !prev), []);

if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
} else if (!value || value.length === 0) {
return (
<InfoCard title="MR Assigned">
<Typography>No open merge requests are currently assigned to you.</Typography>
</InfoCard>
);
}

const displayedMergeRequests = collapsed ? value.slice(0, 3) : value;

return (
<InfoCard title="MR Assigned">
<List dense>
{displayedMergeRequests.map((mergeRequest) => (
<Box
key={mergeRequest.id}
border={1}
borderRadius={4}
padding={1}
marginBottom={1}
style={{
borderColor: theme.palette.divider,
backgroundColor: theme.palette.background.paper,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Box style={{ display: 'flex', alignItems: 'center', marginRight: '12px' }}>
<Box
style={{
display: 'flex',
alignItems: 'center',
height: '24px',
width: '24px',
}}
>
{getStatusIconType(mergeRequest)}
</Box>
<Link to={mergeRequest.web_url} target="_blank" style={{ fontSize: '1rem', lineHeight: '24px' }}>
{mergeRequest.title}
</Link>
</Box>


<Typography
variant="body2"
style={{
color: theme.palette.text.secondary,
backgroundColor: theme.palette.action.hover,
padding: '2px 6px',
borderRadius: '4px',
fontSize: '0.8rem',
lineHeight: '24px',
}}
>
{getElapsedTime(mergeRequest.created_at)}
</Typography>
</Box>
))}
</List>

{/* View More / View Less Button */}
{value.length > 3 && (
<Box style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '16px' }}>
<Button variant="text" onClick={onClick}>
{collapsed ? 'View More' : 'View Less'}
</Button>
</Box>
)}
</InfoCard>
);
};

export default MergeRequestsAssignedToMeCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as MergeRequestsAssignedToMeCard } from './MergeRequestsAssignedToMeCard';
Loading
Loading