Skip to content
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
8 changes: 8 additions & 0 deletions src/codebucket/command/registerDecorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ExtensionContext, window } from 'vscode';

import { FileDecorationProvider } from '../../views/decorators/FileDecorationProvider';

export function activate(context: ExtensionContext) {
const decorationProvider = new FileDecorationProvider();
context.subscriptions.push(window.registerFileDecorationProvider(decorationProvider));
}
14 changes: 8 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { DetailedSiteInfo, ProductBitbucket, ProductJira } from './atlclients/au
import { startListening } from './atlclients/negotiate';
import { BitbucketContext } from './bitbucket/bbContext';
import { activate as activateCodebucket } from './codebucket/command/registerCommands';
// import { PipelinesYamlCompletionProvider } from './pipelines/yaml/pipelinesYamlCompletionProvider';
Copy link
Contributor

Choose a reason for hiding this comment

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

What are these comments moving from one place to another?

// import {
// activateYamlExtension,
// addPipelinesSchemaToYamlConfig,
// BB_PIPELINES_FILENAME,
// } from './pipelines/yaml/pipelinesYamlHelper';
import { activate as activateDecorators } from './codebucket/command/registerDecorators';
import { CommandContext, setCommandContext } from './commandContext';
import { registerCommands } from './commands';
import { Configuration, configuration, IConfig } from './config/configuration';
Expand All @@ -16,12 +23,6 @@ import { Container } from './container';
import { registerAnalyticsClient, registerErrorReporting, unregisterErrorReporting } from './errorReporting';
import { provideCodeLenses } from './jira/todoObserver';
import { Logger } from './logger';
// import { PipelinesYamlCompletionProvider } from './pipelines/yaml/pipelinesYamlCompletionProvider';
// import {
// activateYamlExtension,
// addPipelinesSchemaToYamlConfig,
// BB_PIPELINES_FILENAME,
// } from './pipelines/yaml/pipelinesYamlHelper';
import { registerResources } from './resources';
import { GitExtension } from './typings/git';
import { Experiments, FeatureFlagClient, Features } from './util/featureFlags';
Expand Down Expand Up @@ -52,6 +53,7 @@ export async function activate(context: ExtensionContext) {
activateErrorReporting();
registerCommands(context);
activateCodebucket(context);
activateDecorators(context);

setCommandContext(
CommandContext.IsJiraAuthenticated,
Expand Down
194 changes: 142 additions & 52 deletions src/react/atlascode/pullrequest/Reviewers.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,91 @@
import { Avatar, Badge, Box, CircularProgress, Grid, Tooltip, Typography } from '@material-ui/core';
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import { AvatarGroup } from '@material-ui/lab';
import {
Avatar,
Badge,
Box,
CircularProgress,
Grid,
IconButton,
makeStyles,
Tooltip,
Typography,
} from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import CheckCircleIcon from '@material-ui/icons/StopRounded';
import React, { useCallback, useEffect, useState } from 'react';

import { BitbucketSite, Reviewer, User } from '../../../bitbucket/model';
import StoppedIcon from '../icons/StoppedIcon';
import { AddReviewers } from './AddReviewers';

const useStyles = makeStyles({
reviewerContainer: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
padding: 0,
marginBottom: 8,
'&:hover .removeButton': {
opacity: 1,
},
},
reviewerInfo: {
display: 'flex',
alignItems: 'center',
flex: 1,
minWidth: 0,
},
avatarBadge: {
marginRight: 8,
borderWidth: 0,
paddingLeft: 0,
},
avatar: {
width: 24,
height: 24,
},
approvedIcon: {
fontSize: 14,
width: 14,
height: 14,
},
name: {
flex: 1,
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
removeButton: {
padding: 4,
opacity: 0,
transition: 'opacity 0.2s',
'&:hover': {
color: 'var(--vscode-errorForeground)',
},
},
});

type ReviewersProps = {
site: BitbucketSite;
onUpdateReviewers: (reviewers: User[]) => Promise<void>;
participants: Reviewer[];
isLoading: boolean;
};

export const Reviewers: React.FunctionComponent<ReviewersProps> = ({
site,
onUpdateReviewers,
participants,
isLoading,
}) => {
const classes = useStyles();
const [activeParticipants, setActiveParticipants] = useState<Reviewer[]>([]);
const [isFetchingReviewer, setIsFetchingReviewers] = useState<boolean>(false);

const handleUpdateReviewers = useCallback(
async (newReviewers: User[]) => {
setIsFetchingReviewers(true);

try {
await onUpdateReviewers(newReviewers);
} finally {
Expand All @@ -34,65 +95,94 @@ export const Reviewers: React.FunctionComponent<ReviewersProps> = ({
[onUpdateReviewers],
);

const handleRemoveReviewer = useCallback(
async (reviewerToRemove: Reviewer) => {
const newReviewers = activeParticipants
.filter((p) => p.accountId !== reviewerToRemove.accountId)
.map(({ accountId, displayName, avatarUrl, url, mention }) => ({
accountId,
displayName,
avatarUrl,
url,
mention,
}));
await handleUpdateReviewers(newReviewers);
},
[activeParticipants, handleUpdateReviewers],
);

useEffect(() => {
setActiveParticipants(
participants // always show reviewers & show non-reviewers if they have approved or marked needs work
participants
.filter((p) => p.status !== 'UNAPPROVED' || p.role === 'REVIEWER')
.sort((a, b) => (a.status < b.status ? 0 : 1)),
);
}, [participants]);

if (isLoading || isFetchingReviewer) {
return <CircularProgress />;
}

return (
<Grid container direction="row" spacing={2} alignItems={'center'}>
{isLoading || isFetchingReviewer ? (
<Grid item>
<CircularProgress />
</Grid>
<Grid container direction="column" style={{ padding: '0', width: '100%' }}>
{activeParticipants.length === 0 ? (
<Typography variant="body2">No reviewers!</Typography>
) : (
<Grid item>
{activeParticipants.length === 0 ? (
<Typography variant="body2">No reviewers!</Typography>
) : (
<AvatarGroup max={5}>
{activeParticipants.map((participant) => (
<Badge
style={{ borderWidth: '0px' }}
overlap="circle"
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
invisible={participant.status === 'UNAPPROVED'}
key={participant.accountId}
badgeContent={
participant.status === 'CHANGES_REQUESTED' ? (
<Tooltip title="Requested changes">
<Box bgcolor={'white'} borderRadius={'100%'}>
<StoppedIcon fontSize={'small'} htmlColor={'#FFAB00'} />
</Box>
</Tooltip>
) : (
<Tooltip title="Approved">
<Box bgcolor={'white'} borderRadius={'100%'}>
<CheckCircleIcon fontSize={'small'} htmlColor={'#07b82b'} />
</Box>
</Tooltip>
)
}
>
<Tooltip title={participant.displayName}>
<Avatar alt={participant.displayName} src={participant.avatarUrl} />
</Tooltip>
</Badge>
))}
</AvatarGroup>
)}
</Grid>
activeParticipants.map((participant) => (
<div key={participant.accountId} className={classes.reviewerContainer}>
<div className={classes.reviewerInfo}>
<Badge
className={classes.avatarBadge}
overlap="circle"
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
invisible={
participant.status !== 'APPROVED' && participant.status !== 'CHANGES_REQUESTED'
}
badgeContent={
participant.status === 'APPROVED' ? (
<Tooltip title="Approved">
<Box bgcolor={'white'} borderRadius={'100%'}>
<CheckCircleIcon
className={classes.approvedIcon}
htmlColor={'var(--ds-icon-success)'}
/>
</Box>
</Tooltip>
) : participant.status === 'CHANGES_REQUESTED' ? (
<Tooltip title="Changes requested">
<Box bgcolor={'white'} borderRadius={'100%'}>
<StoppedIcon fontSize={'small'} htmlColor={'#FFAB00'} />
</Box>
</Tooltip>
) : null
}
>
<Avatar
alt={participant.displayName}
src={participant.avatarUrl}
className={classes.avatar}
/>
</Badge>
<Typography className={classes.name}>{participant.displayName}</Typography>
</div>
<Tooltip title="Remove reviewer">
<IconButton
size="small"
onClick={() => handleRemoveReviewer(participant)}
className={`removeButton ${classes.removeButton}`}
>
<CloseIcon fontSize="small" />
</IconButton>
</Tooltip>
</div>
))
)}

<Grid style={{ width: '100%' }} item>
<Box mt={1}>
<AddReviewers site={site} reviewers={activeParticipants} updateReviewers={handleUpdateReviewers} />
</Grid>
</Box>
</Grid>
);
};
49 changes: 49 additions & 0 deletions src/views/decorators/FileDecorationProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as vscode from 'vscode';

import { FileStatus } from '../../bitbucket/model';

export class FileDecorationProvider implements vscode.FileDecorationProvider {
private _onDidChangeFileDecorations = new vscode.EventEmitter<vscode.Uri[]>();
readonly onDidChangeFileDecorations = this._onDidChangeFileDecorations.event;

provideFileDecoration(
uri: vscode.Uri,
_token: vscode.CancellationToken,
): vscode.ProviderResult<vscode.FileDecoration> {
try {
const params = JSON.parse(uri.query);
const status = params.status as FileStatus;
const hasComments = params.hasComments;
if (status) {
return {
badge: hasComments ? `💬${status}` : status,
color: this.getColor(status),
tooltip: hasComments ? `File has comments` : undefined,
propagate: false,
};
}
} catch (e) {
console.error('Error in provideFileDecoration:', e);
}
return undefined;
}

private getColor(status: FileStatus): vscode.ThemeColor {
switch (status) {
case FileStatus.MODIFIED:
return new vscode.ThemeColor('gitDecoration.modifiedResourceForeground');
case FileStatus.ADDED:
return new vscode.ThemeColor('gitDecoration.addedResourceForeground');
case FileStatus.DELETED:
return new vscode.ThemeColor('gitDecoration.deletedResourceForeground');
case FileStatus.RENAMED:
return new vscode.ThemeColor('gitDecoration.renamedResourceForeground');
case FileStatus.CONFLICT:
return new vscode.ThemeColor('gitDecoration.conflictingResourceForeground');
case FileStatus.COPIED:
return new vscode.ThemeColor('gitDecoration.addedResourceForeground');
default:
return new vscode.ThemeColor('gitDecoration.modifiedResourceForeground');
}
}
}
Loading
Loading