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
38 changes: 37 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,14 @@
"command": "atlascode.disableHelpExplorer",
"title": "Disable Help Explorer",
"category": "Atlassian"
},
{
"command": "atlascode.bitbucket.pullRequestsOverview.refresh",
"title": "Refresh",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
}
],
"viewsContainers": {
Expand All @@ -433,7 +441,12 @@
{
"id": "atlascode.views.bb.pullrequestsTreeView",
"name": "Bitbucket pull requests",
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled"
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled && config.atlascode.bitbucket.explorer.repositoryBasedPullRequestView.enabled"
},
{
"id": "atlascode.views.bb.pullRequestsOverviewTreeView",
"name": "Relevant Bitbucket workspace pull requests",
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled && config.atlascode.bitbucket.explorer.pullRequestsOverview.enabled"
},
{
"id": "atlascode.views.bb.pipelinesTreeView",
Expand Down Expand Up @@ -580,6 +593,11 @@
{
"command": "atlascode.disableHelpExplorer",
"when": "view == atlascode.views.helpTreeView"
},
{
"command": "atlascode.bitbucket.pullRequestsOverview.refresh",
"when": "view == atlascode.views.bb.pullRequestsOverviewTreeView",
"group": "navigation@1"
}
],
"view/item/context": [
Expand Down Expand Up @@ -1097,6 +1115,24 @@
"description": "Enables the Bitbucket Pull Request Explorer",
"scope": "window"
},
"atlascode.bitbucket.explorer.repositoryBasedPullRequestView.enabled": {
"type": "boolean",
"default": true,
"description": "Enable repository based pull requests tree view",
"scope": "window"
},
"atlascode.bitbucket.explorer.pullRequestsOverview.enabled": {
"type": "boolean",
"default": false,
"description": "Enable pull requests overview tree view",
"scope": "window"
},
"atlascode.bitbucket.explorer.pullRequestsOverview.workspace": {
"type": "string",
"default": "",
"description": "Workspace used for pull requests overview tree view",
"scope": "window"
},
"atlascode.bitbucket.explorer.nestFilesEnabled": {
"type": "boolean",
"default": true,
Expand Down
6 changes: 6 additions & 0 deletions src/atlclients/clientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ConfigurationChangeEvent, Disposable, ExtensionContext } from 'vscode';
import { commands, window } from 'vscode';

import { CloudPullRequestApi } from '../bitbucket/bitbucket-cloud/pullRequests';
import { PullRequestsOverviewApi } from '../bitbucket/bitbucket-cloud/pullRequestsOverview';
import { CloudRepositoriesApi } from '../bitbucket/bitbucket-cloud/repositories';
import { ServerPullRequestApi } from '../bitbucket/bitbucket-server/pullRequests';
import { ServerRepositoriesApi } from '../bitbucket/bitbucket-server/repositories';
Expand Down Expand Up @@ -124,6 +125,9 @@ export class ClientManager implements Disposable {
pullrequests: isOAuthInfo(info)
? new CloudPullRequestApi(this.createOAuthHTTPClient(site, info.access))
: undefined!,
pullrequestsOverview: isOAuthInfo(info)
? new PullRequestsOverviewApi(this.createOAuthHTTPClient(site, info.access))
: undefined!,
pipelines: isOAuthInfo(info)
? new PipelineApiImpl(this.createOAuthHTTPClient(site, info.access))
: undefined!,
Expand All @@ -138,6 +142,8 @@ export class ClientManager implements Disposable {
isBasicAuthInfo(info) || isPATAuthInfo(info)
? new ServerPullRequestApi(this.createHTTPClient(site, info))
: undefined!,
// Note: For now Internal Pull Requests are not supported for Server
pullrequestsOverview: undefined,
pipelines: undefined,
};
}
Expand Down
4 changes: 4 additions & 0 deletions src/bitbucket/bbContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CacheMap } from '../util/cachemap';
import { Time } from '../util/time';
import { PullRequestCommentController } from '../views/pullrequest/prCommentController';
import { PullRequestsExplorer } from '../views/pullrequest/pullRequestsExplorer';
import { PullRequestsOverviewExplorer } from '../views/pullrequest/PullRequestsOverviewExplorer';
import { clientForSite, getBitbucketCloudRemotes, getBitbucketRemotes, workspaceRepoFor } from './bbUtils';
import { BitbucketSite, PullRequest, User, WorkspaceRepo } from './model';

Expand All @@ -21,6 +22,7 @@ export class BitbucketContext extends Disposable {
private _gitApi: GitApi;
private _repoMap: Map<string, WorkspaceRepo> = new Map();
private _pullRequestsExplorer: PullRequestsExplorer;
private _pullRequestsOverviewExplorer: PullRequestsOverviewExplorer;
private _disposable: Disposable;
private _currentUsers: CacheMap;
private _pullRequestCache = new CacheMap();
Expand All @@ -31,6 +33,7 @@ export class BitbucketContext extends Disposable {
super(() => this.dispose());
this._gitApi = gitApi;
this._pullRequestsExplorer = new PullRequestsExplorer(this);
this._pullRequestsOverviewExplorer = new PullRequestsOverviewExplorer(this);
this._currentUsers = new CacheMap();

Container.context.subscriptions.push(
Expand All @@ -48,6 +51,7 @@ export class BitbucketContext extends Disposable {
this._gitApi.onDidOpenRepository(() => this.refreshRepos()),
this._gitApi.onDidCloseRepository(() => this.refreshRepos()),
this._pullRequestsExplorer,
this._pullRequestsOverviewExplorer,
this.prCommentController,
);

Expand Down
132 changes: 132 additions & 0 deletions src/bitbucket/bitbucket-cloud/pullRequestsOverview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { DetailedSiteInfo } from '../../atlclients/authInfo';
import { toISOString } from '../../react/atlascode/util/date-fns';
import { HTTPClient } from '../httpClient';
import { BitbucketSite, PullRequest, WorkspaceRepo } from '../model';
import { CloudPullRequestApi } from './pullRequests';

export interface OverviewViewState {
pullRequests: {
authored: PullRequest[];
reviewing: PullRequest[];
closed: PullRequest[];
};
}

function updatedAfter(minUpdatedTs?: string) {
return (pr: PullRequest) => {
if (!minUpdatedTs) {
return true;
}

const updatedTsIsoString = toISOString(pr.data.updatedTs);
if (!updatedTsIsoString) {
return true;
}

return updatedTsIsoString > minUpdatedTs;
};
}

export class PullRequestsOverviewApi {
constructor(private client: HTTPClient) {}

private extractSiteFromPRData(prData: any, site: DetailedSiteInfo): BitbucketSite {
// Extract site information from the PR data
const repoSlug = prData.destination.repository.full_name.split('/')[1];
const ownerSlug = prData.destination.repository.workspace.slug;

// Create a site object
return {
ownerSlug,
repoSlug,
details: site,
};
}

private createWorkspaceRepoFromPRData(prData: any, site: BitbucketSite): WorkspaceRepo {
// Create a minimal WorkspaceRepo object with required fields
return {
rootUri: prData.destination.repository.full_name,
mainSiteRemote: {
site: site,
remote: {
name: prData.destination.repository.name,
fetchUrl: `https://bitbucket.org/${prData.destination.repository.full_name}.git`,
pushUrl: `https://bitbucket.org/${prData.destination.repository.full_name}.git`,
isReadOnly: true,
},
},
siteRemotes: [],
};
}

async getOverviewViewState(
ownerSlug: string,
site: DetailedSiteInfo,
minUpdatedTs: string,
): Promise<OverviewViewState> {
const fields = [
'+pullRequests.*.author',
'+pullRequests.*.closed_on',
'+pullRequests.*.comment_count',
'+pullRequests.*.created_on',
'+pullRequests.*.destination.branch.name',
'+pullRequests.*.destination.commit.hash',
'+pullRequests.*.destination.repository.workspace',
'+pullRequests.*.id',
'+pullRequests.*.links.html',
'+pullRequests.*.links.self',
'+pullRequests.*.participants',
'+pullRequests.*.repository.links.avatar',
'+pullRequests.*.repository.links.html',
'+pullRequests.*.repository.full_name',
'+pullRequests.*.repository.name',
'+pullRequests.*.source.branch.name',
'+pullRequests.*.source.commit.hash',
'+pullRequests.*.source.repository.workspace',
'+pullRequests.*.state',
'+pullRequests.*.task_count',
'+pullRequests.*.title',
'+pullRequests.*.updated_on',
].join(',');

const { data } = await this.client.get(
`/workspaces/${ownerSlug}/overview-view-state/?fields=${encodeURIComponent(fields)}`,
);

const authored: PullRequest[] = data.pullRequests.authored
.map((pr: any) => {
const bbSite = this.extractSiteFromPRData(pr, site);
const workspaceRepo = this.createWorkspaceRepoFromPRData(pr, bbSite);

return CloudPullRequestApi.toPullRequestData(pr, bbSite, workspaceRepo);
})
.filter(updatedAfter(minUpdatedTs));

const reviewing: PullRequest[] = data.pullRequests.reviewing
.map((pr: any) => {
const bbSite = this.extractSiteFromPRData(pr, site);
const workspaceRepo = this.createWorkspaceRepoFromPRData(pr, bbSite);
return CloudPullRequestApi.toPullRequestData(pr, bbSite, workspaceRepo);
})
.filter(updatedAfter(minUpdatedTs));

const closed: PullRequest[] = data.pullRequests.closed
.map((pr: any) => {
const bbSite = this.extractSiteFromPRData(pr, site);
const workspaceRepo = this.createWorkspaceRepoFromPRData(pr, bbSite);
return CloudPullRequestApi.toPullRequestData(pr, bbSite, workspaceRepo);
})
.filter(updatedAfter(minUpdatedTs));

const response: OverviewViewState = {
pullRequests: {
authored,
reviewing,
closed,
},
};

return response;
}
}
13 changes: 13 additions & 0 deletions src/bitbucket/bitbucket-cloud/repositories.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CancelToken } from 'axios';

import { CacheMap } from '../../util/cachemap';
import { HTTPClient } from '../httpClient';
import { BitbucketBranchingModel, BitbucketSite, Commit, Repo, RepositoriesApi, UnknownUser } from '../model';
Expand Down Expand Up @@ -135,4 +137,15 @@ export class CloudRepositoriesApi implements RepositoriesApi {
issueTrackerEnabled: !!bbRepo.has_issues,
};
}

async fetchWorkspaces(query: string, cancelToken?: CancelToken): Promise<string[]> {
const { data } = await this.client.get(
`/user/permissions/workspaces`,
{
q: `workspace.slug ~ "${query}"`,
},
cancelToken,
);
return data.values.map((workspaceMembership: any) => workspaceMembership.workspace.slug);
}
}
6 changes: 6 additions & 0 deletions src/bitbucket/bitbucket-server/repositories.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CancelToken } from 'axios';

import { CacheMap } from '../../util/cachemap';
import { HTTPClient } from '../httpClient';
import { BitbucketBranchingModel, BitbucketSite, Commit, Repo, RepositoriesApi } from '../model';
Expand Down Expand Up @@ -179,4 +181,8 @@ export class ServerRepositoriesApi implements RepositoriesApi {
issueTrackerEnabled: false,
};
}

async fetchWorkspaces(query: string, cancelToken?: CancelToken): Promise<string[]> {
throw new Error("Workspaces don't exist in Bitbucket DC");
}
}
7 changes: 7 additions & 0 deletions src/bitbucket/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DetailedSiteInfo, emptySiteInfo } from '../atlclients/authInfo';
import { PipelineApiImpl } from '../pipelines/pipelines';
import { Remote, Repository } from '../typings/git';
import { FileDiffQueryParams } from '../views/pullrequest/diffViewHelper';
import { OverviewViewState } from './bitbucket-cloud/pullRequestsOverview';

export type BitbucketSite = {
details: DetailedSiteInfo;
Expand Down Expand Up @@ -369,6 +370,10 @@ export interface PullRequestApi {
getFileContent(site: BitbucketSite, commitHash: string, path: string): Promise<string>;
}

export interface PullRequestsOverviewApi {
getOverviewViewState(ownerSlug: string, site: DetailedSiteInfo, minUpdatedTs: string): Promise<OverviewViewState>;
}

export interface RepositoriesApi {
getMirrorHosts(): Promise<string[]>;
get(site: BitbucketSite): Promise<Repo>;
Expand All @@ -378,11 +383,13 @@ export interface RepositoriesApi {
getCommitsForRefs(site: BitbucketSite, includeRef: string, excludeRef: string): Promise<Commit[]>;
getPullRequestIdsForCommit(site: BitbucketSite, commitHash: string): Promise<string[]>;
fetchImage(url: string): Promise<string>;
fetchWorkspaces(query: string, cancelToken?: CancelToken): Promise<string[]>;
}

export interface BitbucketApi {
repositories: RepositoriesApi;
pullrequests: PullRequestApi;
pullrequestsOverview?: PullRequestsOverviewApi;
pipelines?: PipelineApiImpl;
}

Expand Down
2 changes: 2 additions & 0 deletions src/commandContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export enum CommandContext {
JiraLoginTree = 'atlascode:jiraLoginTreeEnabled',
IsJiraAuthenticated = 'atlascode:isJiraAuthenticated',
IsBBAuthenticated = 'atlascode:isBBAuthenticated',
PullRequestOverviewEnabled = 'atlascode:bitbucketPullRequestOverviewEnabled',
RepositoryBasedPullRequestViewEnabled = 'atlascode:bitbucketRepositoryBasedPullRequestViewEnabled',
}

export function setCommandContext(key: CommandContext | string, value: any) {
Expand Down
2 changes: 2 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { PipelineNode } from './views/pipelines/PipelinesTree';

export enum Commands {
BitbucketSelectContainer = 'atlascode.bb.selectContainer',
BitbucketPullRequestsOverviewRefresh = 'atlascode.bitbucket.pullRequestsOverview.refresh',
BitbucketPullRequestsOverviewFocus = 'atlascode.views.bb.pullRequestsOverviewTreeView.focus',
BitbucketFetchPullRequests = 'atlascode.bb.fetchPullRequests',
BitbucketRefreshPullRequests = 'atlascode.bb.refreshPullRequests',
BitbucketToggleFileNesting = 'atlascode.bb.toggleFileNesting',
Expand Down
14 changes: 14 additions & 0 deletions src/config/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ export interface BitbucketExplorer {
enabled: boolean;
nestFilesEnabled: boolean;
refreshInterval: number;
repositoryBasedPullRequestView: {
enabled: boolean;
};
pullRequestsOverview: {
enabled: boolean;
workspace: string;
};
relatedJiraIssues: BitbucketRelatedJiraIssues;
relatedBitbucketIssues: BitbucketRelatedBitbucketIssues;
notifications: BitbucketNotifications;
Expand Down Expand Up @@ -205,6 +212,13 @@ const emptyBitbucketNotfications: BitbucketNotifications = {

const emptyBitbucketExplorer: BitbucketExplorer = {
enabled: true,
repositoryBasedPullRequestView: {
enabled: true,
},
pullRequestsOverview: {
enabled: false,
workspace: '',
},
nestFilesEnabled: true,
refreshInterval: 5,
relatedJiraIssues: emptyRelatedJiraIssues,
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ export const JiraEnabledKey = 'jira.enabled';
export const BitbucketEnabledKey = 'bitbucket.enabled';
export const JiraHoverProviderConfigurationKey = 'jira.hover.enabled';
export const AssignedJiraItemsViewId = 'atlascode.views.jira.assignedWorkItemsTreeView';
export const BitbucketPullRequestsOverviewConfigurationKey = 'bitbucket.explorer.pullRequestsOverview.enabled';
export const BitbucketPullRequestsOverviewWorkspaceConfigurationKey =
'bitbucket.explorer.pullRequestsOverview.workspace';
export const BitbucketRepositoryBasedPullRequestsConfigurationKey =
'bitbucket.explorer.repositoryBasedPullRequestView.enabled';
export const PullRequestTreeViewId = 'atlascode.views.bb.pullrequestsTreeView';
export const PullRequestsOverviewTreeViewId = 'atlascode.views.bb.pullRequestsOverviewTreeView';
export const PipelinesTreeViewId = 'atlascode.views.bb.pipelinesTreeView';
export const BitbucketIssuesTreeViewId = 'atlascode.views.bb.issuesTreeView';
export const HelpTreeViewId = 'atlascode.views.helpTreeView';
Expand Down
Loading