├── tsconfig.prod.json
├── .gitignore
├── .gitattributes
├── media
├── screenshot.png
├── logo.svg
└── azure-boards-logo.svg
├── .vscodeignore
├── resources
├── dark
│ ├── pencil.svg
│ ├── stage.svg
│ ├── open_external.svg
│ ├── mention.svg
│ ├── open.svg
│ ├── refresh.svg
│ └── globe.svg
└── light
│ ├── pencil.svg
│ ├── stage.svg
│ ├── open_external.svg
│ ├── mention.svg
│ ├── open.svg
│ ├── refresh.svg
│ └── globe.svg
├── public
└── index.html
├── .vscode
├── extensions.json
├── settings.json
├── tasks.json
└── launch.json
├── .azure-pipelines
└── ci.yaml
├── tslint.json
├── src
├── configuration
│ ├── url.ts
│ ├── token.ts
│ ├── auth.ts
│ ├── configuration.ts
│ └── commands.ts
├── util
│ ├── open.ts
│ └── telemetry.ts
├── connection.ts
├── resources.ts
├── extension.ts
├── views
│ ├── workitems
│ │ ├── workitem.ts
│ │ ├── workitem.search.ts
│ │ ├── workitem.mywork.ts
│ │ └── workitem.tree.ts
│ └── pending-work-items
│ │ └── pendingWorkItems.ts
├── workitems
│ └── workitem.icons.ts
├── commands
│ └── commands.ts
└── externals
│ └── git.d.ts
├── tsconfig.json
├── LICENSE
├── CONTRIBUTING.md
├── README.md
├── package.json
└── ThirdPartyNotices.txt
/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | build
3 | node_modules
4 | .vscode-test/
5 | *.vsix
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behavior to automatically normalize line endings.
2 | * text=auto
3 |
4 |
--------------------------------------------------------------------------------
/media/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/azure-boards-vscode/HEAD/media/screenshot.png
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | out/**/*.map
5 | src/**
6 | .gitignore
7 | tsconfig.json
8 | vsc-extension-quickstart.md
9 | tslint.json
--------------------------------------------------------------------------------
/resources/dark/pencil.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/pencil.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": ["eg2.tslint", "esbenp.prettier-vscode"]
5 | }
6 |
--------------------------------------------------------------------------------
/resources/dark/stage.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/stage.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.azure-pipelines/ci.yaml:
--------------------------------------------------------------------------------
1 | pool:
2 | name: Hosted VS2017
3 | demands: npm
4 |
5 | pr:
6 | - master
7 |
8 | steps:
9 | - task: Npm@1
10 | displayName: "npm install"
11 | inputs:
12 | verbose: false
13 |
14 | - script: npm run compile
15 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-string-throw": true,
4 | "no-unused-expression": true,
5 | "no-duplicate-variable": true,
6 | "curly": true,
7 | "class-name": true,
8 | "semicolon": [
9 | true,
10 | "always"
11 | ],
12 | "triple-equals": true
13 | },
14 | "defaultSeverity": "warning"
15 | }
--------------------------------------------------------------------------------
/resources/dark/open_external.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/open_external.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/configuration/url.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | export function isValidAzureBoardsUrl(url: string): boolean {
5 | url = url.toLocaleLowerCase();
6 |
7 | return (
8 | url.startsWith("https://dev.azure.com/") ||
9 | (url.startsWith("https://") && url.indexOf(".visualstudio.com") >= 0)
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/util/open.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as vscode from "vscode";
5 |
6 | export function openUrl(url: string): void {
7 | if ((vscode.env).openExternal) {
8 | (vscode.env).openExternal(url);
9 | return;
10 | }
11 |
12 | vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(url));
13 | }
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | "editor.formatOnSave": true
12 | }
13 |
--------------------------------------------------------------------------------
/resources/dark/mention.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/mention.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/resources/light/open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/dark/open.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "module": "commonjs",
5 | "target": "es6",
6 | "outDir": "build/extension",
7 | "lib": ["es6", "dom"],
8 | "jsx": "react",
9 | "sourceMap": true,
10 | "rootDir": "src",
11 | /* Strict Type-Checking Option */
12 | "strict": true /* enable all strict type-checking options */,
13 | /* Additional Checks */
14 | "noUnusedLocals": true /* Report errors on unused locals. */,
15 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
16 | "noUnusedParameters": true /* Report errors on unused parameters. */
17 | },
18 | "exclude": ["node_modules", ".vscode-test"]
19 | }
20 |
--------------------------------------------------------------------------------
/media/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/connection.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as DevOpsClient from "azure-devops-node-api";
5 | import { IOrganization } from "./configuration/configuration";
6 | import { getTokenForOrganization } from "./configuration/token";
7 |
8 | export async function getWebApiForOrganization(
9 | organization: IOrganization
10 | ): Promise {
11 | const token = await getTokenForOrganization(organization);
12 | if (!token) {
13 | throw new Error("Cannot get token for organization");
14 | }
15 |
16 | const handler = DevOpsClient.getHandlerFromToken(token);
17 | return new DevOpsClient.WebApi(organization.uri, handler);
18 | }
19 |
--------------------------------------------------------------------------------
/media/azure-boards-logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/dark/refresh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/refresh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/configuration/token.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { getPassword, setPassword, deletePassword } from "keytar";
5 | import { IOrganization } from "./configuration";
6 |
7 | const ServiceName = "Azure Boards VS Code";
8 |
9 | export async function getTokenForOrganization(
10 | organization: IOrganization
11 | ): Promise {
12 | return getPassword(ServiceName, organization.uri.toLocaleLowerCase());
13 | }
14 |
15 | export async function storeTokenForOrganization(
16 | organization: IOrganization,
17 | token: string
18 | ): Promise {
19 | await setPassword(ServiceName, organization.uri.toLocaleLowerCase(), token);
20 | }
21 |
22 | export async function removeTokenForOrganization(
23 | organization: IOrganization
24 | ): Promise {
25 | await deletePassword(ServiceName, organization.uri.toLocaleLowerCase());
26 | }
27 |
--------------------------------------------------------------------------------
/src/resources.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | const enum Resources {
5 | Configuration_AddedOrganization = "Successfully added Azure Boards organization",
6 | Configuration_DeviceFlowCopyCode = "Copy this code and then press Enter to start the authentication process",
7 | Configuration_Authenticating = "Authenticating to Azure DevOps Services (%s)...",
8 | Configuration_OrganizationUriPrompt = "Enter organization uri",
9 | Configuration_InvalidUri = "%s is not a valid Azure Boards url",
10 | Configuration_OrganizationExists = "Azure Boards organization {0} is already configured",
11 | Configuration_SelectOrganization = "Select an organization...",
12 | Configuration_RemovedOrganization = "Removed Azure Boards organization.",
13 | Configuration_SelectProject = "Select project...",
14 | Configuration_LoadingProjects = "Loading projects...",
15 | Configuration_ClickToConnect = "Click here to connect to Azure Boards",
16 | Configuration_NoOpenFolder = "You have not yet opened a folder."
17 | }
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/util/telemetry.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { getCurrentOrganization } from "../configuration/configuration";
5 |
6 | const _appInsights = require("applicationinsights");
7 | const _instrumentationKey = "4055bdd0-39f8-45ef-a48f-cf563234638d";
8 |
9 | export function startTelemetry() {
10 | _appInsights.setup(_instrumentationKey).start();
11 | }
12 |
13 | export function trackTelemetryEvent(name: string) {
14 | const currentOrganization = getCurrentOrganization();
15 | const organizationUri = currentOrganization ? currentOrganization.uri : "";
16 |
17 | let client = _appInsights.defaultClient;
18 | client.trackEvent({
19 | name: name,
20 | properties: { organization: organizationUri }
21 | });
22 | }
23 |
24 | export function trackTelemetryException(error: Error) {
25 | const currentOrganization = getCurrentOrganization();
26 | const organizationUri = currentOrganization ? currentOrganization.uri : "";
27 |
28 | let client = _appInsights.defaultClient;
29 | client.trackException({
30 | exception: new Error(error.message),
31 | properties: { organization: organizationUri }
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": [
14 | "--extensionDevelopmentPath=${workspaceFolder}"
15 | ],
16 | "outFiles": [
17 | "${workspaceFolder}/out/**/*.js"
18 | ],
19 | "preLaunchTask": "npm: watch"
20 | },
21 | {
22 | "name": "Extension Tests",
23 | "type": "extensionHost",
24 | "request": "launch",
25 | "runtimeExecutable": "${execPath}",
26 | "args": [
27 | "--extensionDevelopmentPath=${workspaceFolder}",
28 | "--extensionTestsPath=${workspaceFolder}/out/test"
29 | ],
30 | "outFiles": [
31 | "${workspaceFolder}/out/test/**/*.js"
32 | ],
33 | "preLaunchTask": "npm: watch"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as vscode from "vscode";
5 | import { Commands, registerGlobalCommands } from "./commands/commands";
6 | import { registerConfigurationCommands } from "./configuration/commands";
7 | import { WorkItemTreeNodeProvider } from "./views/workitems/workitem.tree";
8 | import { startTelemetry, trackTelemetryEvent } from "./util/telemetry";
9 |
10 | export function activate(context: vscode.ExtensionContext) {
11 | startTelemetry();
12 | trackTelemetryEvent("Loading Azure Boards Extension");
13 |
14 | registerTreeView(context);
15 |
16 | registerGlobalCommands(context);
17 |
18 | registerConfigurationCommands(context);
19 | }
20 |
21 | /**
22 | * Register work items tree view
23 | */
24 | export function registerTreeView(context: vscode.ExtensionContext): void {
25 | const treeDataProvider = new WorkItemTreeNodeProvider();
26 |
27 | context.subscriptions.push(
28 | vscode.window.createTreeView("work-items", {
29 | treeDataProvider
30 | })
31 | );
32 |
33 | // context.subscriptions.push(
34 | // vscode.window.createTreeView("pending-work-items", {
35 | // treeDataProvider: new PendingWorkItemTreeNodeProvider()
36 | // })
37 | // );
38 |
39 | context.subscriptions.push(
40 | vscode.commands.registerCommand(Commands.Refresh, () => {
41 | treeDataProvider.refresh();
42 | })
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/views/workitems/workitem.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { WorkItem } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces";
5 | import { WorkItemTypeIcon } from "../../workitems/workitem.icons";
6 |
7 | export class WorkItemComposite {
8 | public readonly workItemType: string;
9 | public readonly workItemId: number;
10 | public readonly workItemTitle: string;
11 | public readonly workItemIcon: string;
12 | public readonly url: string;
13 |
14 | private readonly _fallBackIconUrl =
15 | "https://tfsprodcus3.visualstudio.com/_apis/wit/workItemIcons/icon_book?color=009CCC&v=2";
16 |
17 | constructor(
18 | workItem: WorkItem,
19 | workItemTypeIcons: WorkItemTypeIcon[] | null
20 | ) {
21 | this.workItemType = workItem.fields
22 | ? workItem.fields["System.WorkItemType"]
23 | : "";
24 | this.workItemId = workItem.fields ? workItem.fields["System.Id"] : -1;
25 | this.workItemTitle = workItem.fields ? workItem.fields["System.Title"] : "";
26 |
27 | //get index of icon from list of available icons for the work item type
28 | let i = workItemTypeIcons
29 | ? workItemTypeIcons.findIndex(x => x.type === this.workItemType)
30 | : 0;
31 |
32 | this.workItemIcon = workItemTypeIcons
33 | ? workItemTypeIcons[i].url.toString()
34 | : this._fallBackIconUrl;
35 | this.url = workItem._links.html.href;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/views/pending-work-items/pendingWorkItems.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as vscode from "vscode";
5 | import { GitExtension } from "../../externals/git";
6 |
7 | export class PendingWorkItemTreeNodeProvider
8 | implements vscode.TreeDataProvider {
9 | private _onDidChangeTreeData: vscode.EventEmitter<
10 | vscode.TreeItem | undefined
11 | > = new vscode.EventEmitter();
12 |
13 | readonly onDidChangeTreeData: vscode.Event = this
14 | ._onDidChangeTreeData.event;
15 |
16 | private workItemIds: number[] = [];
17 |
18 | constructor() {
19 | const gitExtension = vscode.extensions.getExtension(
20 | "vscode.git"
21 | );
22 | if (gitExtension) {
23 | const git = gitExtension.exports.getAPI(1);
24 | setInterval(() => {
25 | const value =
26 | (git &&
27 | git.repositories &&
28 | git.repositories.length > 0 &&
29 | git.repositories[0].inputBox.value) ||
30 | "";
31 |
32 | const matches = value.match(/(#(\d+))/gm);
33 | const newIds =
34 | (matches && matches.map(x => parseInt(x.replace(/[^\d]/, "")))) || [];
35 | if (newIds.some((v, i) => this.workItemIds[i] !== v)) {
36 | this.workItemIds = newIds;
37 | this._onDidChangeTreeData.fire();
38 | }
39 | }, 2000);
40 | }
41 | }
42 |
43 | getChildren(
44 | element?: vscode.TreeItem | undefined
45 | ): vscode.ProviderResult {
46 | if (!element) {
47 | return this.workItemIds.map(id => new vscode.TreeItem(`#${id}`));
48 | }
49 |
50 | return [];
51 | }
52 |
53 | getTreeItem(
54 | element: vscode.TreeItem
55 | ): vscode.TreeItem | Thenable {
56 | return element;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/workitems/workitem.icons.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { WorkItemType } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces";
5 | import {
6 | getCurrentOrganization,
7 | getCurrentProject
8 | } from "../configuration/configuration";
9 | import { getWebApiForOrganization } from "../connection";
10 |
11 | export class WorkItemTypeProvider {
12 | private _iconPromise: Promise | [];
13 |
14 | constructor() {
15 | this._iconPromise = this._getIcons();
16 | }
17 |
18 | public async getIcons(): Promise {
19 | if (!this._iconPromise) {
20 | this._iconPromise = this._getIcons();
21 | }
22 |
23 | return this._iconPromise;
24 | }
25 |
26 | private async _getIcons(): Promise {
27 | const organization = getCurrentOrganization();
28 | if (!organization) {
29 | return [];
30 | }
31 |
32 | const project = await getCurrentProject();
33 | if (!project) {
34 | return [];
35 | }
36 |
37 | const witApi = await (await getWebApiForOrganization(
38 | organization
39 | )).getWorkItemTrackingApi();
40 |
41 | // Get icons
42 | const workItemTypes = await witApi.getWorkItemTypes(project.id);
43 | const icons =
44 | workItemTypes !== null
45 | ? workItemTypes.map(x => new WorkItemTypeIcon(x))
46 | : [];
47 |
48 | return icons;
49 | }
50 | }
51 |
52 | export class WorkItemTypeIcon {
53 | public readonly type: string = "";
54 | public readonly icon: string = "";
55 | public readonly url: string = "";
56 |
57 | constructor(workItemType: WorkItemType) {
58 | this.type = workItemType.name ? workItemType.name : "";
59 | this.icon =
60 | workItemType.icon && workItemType.icon.id ? workItemType.icon.id : "";
61 | this.url =
62 | workItemType.icon && workItemType.icon.url ? workItemType.icon.url : "";
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/resources/dark/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/views/workitems/workitem.search.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as DevOpsClient from "azure-devops-node-api";
5 | import { IHttpClientResponse } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces";
6 |
7 | export class SearchProvider {
8 | private _searchUrl: string = "https://almsearch.dev.azure.com/";
9 | private _api: DevOpsClient.WebApi;
10 |
11 | constructor(private readonly org: string, webApi: DevOpsClient.WebApi) {
12 | this._searchUrl +=
13 | this.org +
14 | "/_apis/search/workitemsearchresults?api-version=5.0-preview.1";
15 | this._api = webApi;
16 | }
17 |
18 | async searchWorkItems(data: object): Promise {
19 | const client = this._api.rest.client;
20 |
21 | let res: IHttpClientResponse = await client.post(
22 | this._searchUrl,
23 | JSON.stringify(data),
24 | {
25 | Accept: "application/json",
26 | "Content-Type": "application/json"
27 | }
28 | );
29 |
30 | let body: string = await res.readBody();
31 | let jsonObject: ISearchRoot = JSON.parse(body);
32 |
33 | const workitems: WorkItem[] = jsonObject.results.map(x => new WorkItem(x));
34 |
35 | return workitems;
36 | }
37 | }
38 |
39 | export interface ISearchRoot {
40 | count: number;
41 | results: ISearchResult[];
42 | }
43 |
44 | interface ISearchResult {
45 | fields: {
46 | [fieldRefName: string]: string | number | boolean | Date;
47 | };
48 | }
49 |
50 | export class WorkItem {
51 | public readonly id: string;
52 | public readonly assignedTo: string;
53 | public readonly state: string;
54 | public readonly title: string;
55 | public readonly workItemType: string;
56 |
57 | constructor(results: ISearchResult) {
58 | this.id = results.fields ? results.fields["system.id"].toString() : "-1";
59 | this.assignedTo = results.fields
60 | ? results.fields["system.assignedto"].toString()
61 | : "";
62 | this.state = results.fields
63 | ? results.fields["system.state"].toString()
64 | : "";
65 | this.title = results.fields
66 | ? results.fields["system.title"].toString()
67 | : "";
68 | this.workItemType = results.fields
69 | ? results.fields["system.workitemtype"].toString()
70 | : "";
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/views/workitems/workitem.mywork.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { IHttpClientResponse } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces";
5 | import {
6 | WorkItem,
7 | WorkItemExpand
8 | } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces";
9 | import {
10 | getCurrentOrganization,
11 | getCurrentProject
12 | } from "../../configuration/configuration";
13 | import { getWebApiForOrganization } from "../../connection";
14 | import { WorkItemComposite } from "./workitem";
15 | import { WorkItemTypeProvider } from "../../workitems/workitem.icons";
16 | import { trackTelemetryEvent } from "../../util/telemetry";
17 |
18 | export class MyWorkProvider {
19 | private workItemTypeProvider = new WorkItemTypeProvider();
20 |
21 | async getMyWorkItems(type: string): Promise {
22 | const currentOrganization = getCurrentOrganization();
23 | if (!currentOrganization) {
24 | return [];
25 | }
26 |
27 | const currentProject = getCurrentProject();
28 | if (!currentProject) {
29 | return [];
30 | }
31 |
32 | const webApi = await getWebApiForOrganization(currentOrganization);
33 | const client = webApi.rest.client;
34 |
35 | const baseUrl =
36 | currentOrganization.uri +
37 | "/" +
38 | currentProject.id +
39 | "/_apis/work/predefinedQueries/";
40 |
41 | const url = baseUrl + type + "?$top=50&includeCompleted=false";
42 |
43 | const res: IHttpClientResponse = await client.get(url); //needed to call basic client api
44 | const witApi = await webApi.getWorkItemTrackingApi(); //needed to call wit api
45 |
46 | const body: string = await res.readBody();
47 | const myWorkResponse: IMyWorkResponse = JSON.parse(body);
48 |
49 | // get work item icons from work item provider
50 | const icons = this.workItemTypeProvider
51 | ? await this.workItemTypeProvider.getIcons()
52 | : null;
53 |
54 | // get id's
55 | const workItemIds =
56 | myWorkResponse.results !== null
57 | ? myWorkResponse.results.map(x => x.id)
58 | : [];
59 |
60 | // get work items from id's
61 | const workItems: WorkItem[] =
62 | (await witApi.getWorkItems(
63 | workItemIds,
64 | ["System.Id", "System.Title", "System.WorkItemType"],
65 | undefined,
66 | WorkItemExpand.Links
67 | )) || [];
68 |
69 | // loop through work items list and map it to temp map collection
70 | const workItemsMap: { [workItemId: number]: WorkItem } = {};
71 | workItems.forEach(wi => (workItemsMap[wi.id ? wi.id : -1] = wi));
72 |
73 | // set the order of workitems to match that of returned id's
74 | const orderedWorkItems: WorkItem[] = workItemIds.map(
75 | workItemId => workItemsMap[workItemId]
76 | );
77 |
78 | // track telemetry event
79 | trackTelemetryEvent(type);
80 |
81 | // map orderedWorkItems into our composite to include the right icon
82 | return orderedWorkItems.map(wi => new WorkItemComposite(wi, icons));
83 | }
84 | }
85 |
86 | export interface IMyWorkResponse {
87 | results: IMyWorkResult[];
88 | }
89 |
90 | interface IMyWorkResult {
91 | id: number;
92 | }
93 |
--------------------------------------------------------------------------------
/src/configuration/auth.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { openUrl } from "../util/open";
5 | import * as util from "util";
6 | import * as vscode from "vscode";
7 | import {
8 | DeviceFlowAuthenticator,
9 | DeviceFlowDetails,
10 | IDeviceFlowAuthenticationOptions,
11 | IDeviceFlowTokenOptions
12 | } from "vsts-device-flow-auth";
13 | import { IOrganization } from "./configuration";
14 |
15 | const ClientId = "97877f11-0fc6-4aee-b1ff-febb0519dd00";
16 | const RedirectUri = "https://java.visualstudio.com/";
17 |
18 | export async function getTokenUsingDeviceFlow(
19 | organization: IOrganization
20 | ): Promise {
21 | const authOptions: IDeviceFlowAuthenticationOptions = {
22 | clientId: ClientId,
23 | redirectUri: RedirectUri
24 | };
25 | const tokenOptions: IDeviceFlowTokenOptions = {
26 | tokenDescription: `Azure Boards VSCode extension: ${organization.uri}`,
27 | tokenScope: "vso.project vso.work"
28 | };
29 | const dfa: DeviceFlowAuthenticator = new DeviceFlowAuthenticator(
30 | organization.uri,
31 | authOptions,
32 | tokenOptions
33 | );
34 | const details: DeviceFlowDetails = await dfa.GetDeviceFlowDetails();
35 |
36 | // To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code F3VXCTH2L to authenticate.
37 | const value = await vscode.window.showInputBox({
38 | value: details.UserCode,
39 | prompt: `${Resources.Configuration_DeviceFlowCopyCode} (${
40 | details.VerificationUrl
41 | })`,
42 | placeHolder: undefined,
43 | password: false
44 | });
45 | if (value) {
46 | // At this point, user has no way to cancel until our timeout expires. Before this point, they could
47 | // cancel out of the showInputBox. After that, they will need to wait for the automatic cancel to occur.
48 | openUrl(details.VerificationUrl);
49 |
50 | // FUTURE: Could we display a message that allows the user to cancel the authentication? If they escape from the
51 | // message or click Close, they wouldn't have that chance any longer. If they leave the message displaying, they
52 | // have an opportunity to cancel. However, once authenticated, we no longer have an ability to close the message
53 | // automatically or change the message that's displayed. :-/
54 |
55 | // FUTURE: Add a 'button' on the status bar that can be used to cancel the authentication
56 |
57 | // Wait for up to 5 minutes before we cancel the status polling (Azure's default is 900s/15 minutes)
58 | const timeout: number = 5 * 60 * 1000;
59 | /* tslint:disable:align */
60 | const timer: NodeJS.Timer = setTimeout(() => {
61 | dfa.Cancel(true); // throw on canceling
62 | }, timeout);
63 |
64 | // We need to await on withProgress here because we need a token before continuing forward
65 | const title: string = util.format(
66 | Resources.Configuration_Authenticating,
67 | details.UserCode
68 | );
69 | const token: string = await vscode.window.withProgress(
70 | { location: vscode.ProgressLocation.Window, title: title },
71 | async () => {
72 | const accessToken: string = await dfa.WaitForPersonalAccessToken();
73 | // Since we will cancel automatically after timeout, if we _do_ get an accessToken then we need to call clearTimeout
74 | if (accessToken) {
75 | clearTimeout(timer);
76 | }
77 | return accessToken;
78 | }
79 | );
80 |
81 | return token;
82 | }
83 |
84 | return undefined;
85 | }
86 |
--------------------------------------------------------------------------------
/src/configuration/configuration.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as vscode from "vscode";
5 | import { storeTokenForOrganization, removeTokenForOrganization } from "./token";
6 |
7 | export interface IOrganization {
8 | uri: string;
9 | }
10 |
11 | export interface IConfiguration {
12 | currentOrganization: IOrganization | undefined;
13 | currentProject: IProject | undefined;
14 |
15 | organizations: IOrganization[];
16 | }
17 |
18 | export interface IProject {
19 | id: string;
20 | name: string;
21 | }
22 |
23 | const ConfigKey = "azure-boards";
24 |
25 | function getConfig(): vscode.WorkspaceConfiguration {
26 | return vscode.workspace.getConfiguration(ConfigKey);
27 | }
28 |
29 | export function getConfiguration(): IConfiguration {
30 | const config = getConfig();
31 |
32 | return {
33 | organizations: config.get("organizations", []),
34 | currentOrganization: config.get("current-organization", undefined),
35 | currentProject: config.get("current-project", undefined)
36 | };
37 | }
38 |
39 | /**
40 | * Add a new organization
41 | * @param organization
42 | */
43 | export async function addOrganization(
44 | organization: IOrganization,
45 | token: string
46 | ): Promise {
47 | const config = getConfig();
48 |
49 | let organizations: IOrganization[] = [];
50 | if (config.has("organizations")) {
51 | organizations = config.get("organizations", []);
52 | }
53 |
54 | organizations.push(organization);
55 |
56 | // Store token
57 | await storeTokenForOrganization(organization, token);
58 |
59 | await config.update(
60 | "organizations",
61 | organizations,
62 | vscode.ConfigurationTarget.Global
63 | );
64 | }
65 |
66 | export async function removeOrganization(
67 | organization: IOrganization
68 | ): Promise {
69 | const config = getConfig();
70 | if (config.has("organizations")) {
71 | const organizations: IOrganization[] = config.get("organizations", []);
72 | const idx = organizations.findIndex(
73 | x => x.uri.toLocaleLowerCase() === organization.uri.toLocaleLowerCase()
74 | );
75 | if (idx >= 0) {
76 | organizations.splice(idx, 1);
77 | }
78 |
79 | await removeTokenForOrganization(organization);
80 |
81 | await config.update(
82 | "organizations",
83 | organizations,
84 | vscode.ConfigurationTarget.Global
85 | );
86 | }
87 | }
88 |
89 | export function organizationExists(organization: IOrganization): boolean {
90 | const config = getConfiguration();
91 |
92 | return config.organizations.some(
93 | a => a.uri.toLocaleLowerCase() === organization.uri.toLocaleLowerCase()
94 | );
95 | }
96 |
97 | /**
98 | * Set the current organization
99 | */
100 | export async function setCurrentOrganization(
101 | organization: IOrganization | undefined
102 | ): Promise {
103 | await getConfig().update("current-organization", organization);
104 | }
105 |
106 | /**
107 | * Get the current organization
108 | */
109 | export function getCurrentOrganization(): IOrganization | undefined {
110 | return getConfiguration().currentOrganization;
111 | }
112 |
113 | /**
114 | * Set the current project
115 | */
116 | export async function setCurrentProject(
117 | project: IProject | undefined
118 | ): Promise {
119 | await getConfig().update("current-project", project);
120 | }
121 |
122 | /**
123 | * Get the current project
124 | */
125 | export function getCurrentProject(): IProject | undefined {
126 | return getConfiguration().currentProject;
127 | }
128 |
129 | export function compareOrganizations(
130 | orgA: IOrganization,
131 | orgB: IOrganization
132 | ): number {
133 | return orgA.uri.localeCompare(orgB.uri);
134 | }
135 |
136 | export function compareProjects(projA: IProject, projB: IProject): number {
137 | return projA.name.localeCompare(projB.name);
138 | }
139 |
--------------------------------------------------------------------------------
/src/commands/commands.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as vscode from "vscode";
5 | import { GitExtension, Remote } from "../externals/git";
6 | import { trackTelemetryEvent } from "../util/telemetry";
7 |
8 | export const enum Commands {
9 | WorkItemOpen = "azure-boards.open-work-item",
10 | Refresh = "azure-boards.refresh",
11 | WorkItemMention = "azure-boards.mention-work-item",
12 | SettingsShow = "azure-boards.settings.show"
13 | }
14 |
15 | export function registerGlobalCommands(context: vscode.ExtensionContext) {
16 | context.subscriptions.push(
17 | vscode.commands.registerCommand(Commands.WorkItemOpen, args => {
18 | const editUrl = args.editUrl || args;
19 |
20 | //track edit work item telemetry event
21 | trackTelemetryEvent(Commands.WorkItemOpen);
22 |
23 | vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(editUrl));
24 | })
25 | );
26 |
27 | vscode.commands.registerCommand(Commands.WorkItemMention, args => {
28 | const workItemId = args.workItemId || args;
29 | const gitExtension = vscode.extensions.getExtension(
30 | "vscode.git"
31 | );
32 |
33 | //track mention work item telemetry event
34 | trackTelemetryEvent(Commands.WorkItemMention);
35 |
36 | mentionWorkItem(gitExtension, workItemId);
37 | });
38 |
39 | //
40 | // Configuration
41 | //
42 | function mentionWorkItem(
43 | gitExtension: vscode.Extension | undefined,
44 | workItemId: number
45 | ) {
46 | if (gitExtension) {
47 | const git = gitExtension.exports.getAPI(1);
48 | if (git.repositories.length) {
49 | // Determine whether source control is GitHub, if so, prefix mention ID syntax with "AB"
50 | let mentionSyntaxPrefix: string = ``;
51 | const activeRemotes: Remote[] = [];
52 | const originRemotes = git.repositories[0].state.remotes.find(
53 | remote => remote.name === "origin"
54 | );
55 | if (originRemotes) {
56 | activeRemotes.push(originRemotes);
57 | const remoteUrl =
58 | activeRemotes[0].fetchUrl || activeRemotes[0].pushUrl || "";
59 | mentionSyntaxPrefix = determineMentionSyntaxPrefix(
60 | remoteUrl,
61 | mentionSyntaxPrefix
62 | );
63 | } else {
64 | vscode.window.showInformationMessage(
65 | "No Git source control origin remotes found."
66 | );
67 | }
68 |
69 | // Add work item mention to new line if existing commit message, otherwise start with Fix mention
70 | const existingCommitMessage: string =
71 | git.repositories[0].inputBox.value;
72 | let mentionText: string = ``;
73 | if (existingCommitMessage) {
74 | mentionText =
75 | `\n` + `Fixes ` + mentionSyntaxPrefix + `#${workItemId}`;
76 | } else {
77 | mentionText = `Fix ` + mentionSyntaxPrefix + `#${workItemId} `;
78 | }
79 | git.repositories[0].inputBox.value += mentionText;
80 |
81 | // Navigate to the Source Control view
82 | vscode.commands.executeCommand("workbench.view.scm");
83 | } else {
84 | vscode.window.showInformationMessage(
85 | "No Git source control repositories found."
86 | );
87 | }
88 | } else {
89 | vscode.window.showInformationMessage(
90 | "No Git source control extension found."
91 | );
92 | }
93 |
94 | function determineMentionSyntaxPrefix(
95 | remoteUrl: string,
96 | mentionSyntaxPrefix: string
97 | ) {
98 | // TODO: Determine if GitHub Enterprise (non "github.com" host)
99 | const remoteUri = vscode.Uri.parse(remoteUrl);
100 | const authority = remoteUri.authority;
101 | const matches = /^(?:.*:?@)?([^:]*)(?::.*)?$/.exec(authority);
102 | if (
103 | matches &&
104 | matches.length >= 2 &&
105 | matches[1].toLowerCase() === "github.com"
106 | ) {
107 | mentionSyntaxPrefix = `AB`;
108 | }
109 | return mentionSyntaxPrefix;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Azure Boards Extension Contributor Guide
2 |
3 | The instructions below will help you set up your development environment to contribute to this repository.
4 | Make sure you've already cloned the repo. :smile:
5 |
6 | ## Ways to Contribute
7 |
8 | Interested in contributing to the azure-boards-vscode project? There are plenty of ways to contribute, all of which help make the project better.
9 |
10 | - Submit a [bug report](https://github.com/microsoft/azure-boards-vscode/issues/new) or [feature request](https://github.com/microsoft/azure-boards-vscode/issues/new) through the Issue Tracker
11 | - Review the [source code changes](https://github.com/microsoft/azure-boards-vscode/pulls)
12 | - Submit a code fix for a bug (see [Submitting Pull Requests](#submitting-pull-requests) below)
13 | - Participate in [discussions](https://github.com/microsoft/azure-boards-vscode/issues)
14 |
15 | ## Set up Node, npm and gulp
16 |
17 | ### Node and npm
18 |
19 | **Windows and Mac OSX**: Download and install node from [nodejs.org](http://nodejs.org/)
20 |
21 | **Linux**: Install [using package manager](https://nodejs.org/en/download/package-manager/)
22 |
23 | From a terminal ensure at least node 8.11.3 and npm 6.9.0:
24 |
25 | ```bash
26 | $ node -v && npm -v
27 | v8.11.3
28 | 6.9.0
29 | ```
30 |
31 | **Note**: To get npm version 6.9.0, you may need to update npm after installing node. To do that:
32 |
33 | ```bash
34 | [sudo] npm install npm -g
35 | ```
36 |
37 | From the root of the repo, install all of the build dependencies:
38 |
39 | ```bash
40 | [sudo] npm install --greedy
41 | ```
42 |
43 | ## Build and run locally
44 |
45 | Hit F5 in VS Code.
46 |
47 | ## Code Structure
48 |
49 | The code is structured between the Visual Studio Code extension file, the Azure Boards extension object, and the clients, contexts, helpers and services.
50 |
51 | ### Visual Studio Code Extension file
52 |
53 | This is the file with the code called by Visual Studio Code to bootstrap the extension. **extension.ts** should be thin and delegate to the Azure Boards Extension object.
54 |
55 | ### Azure Boards Extension object
56 |
57 | This is the object intended to have small methods that call to the feature-specific clients that manipulate the UI and make calls to Azure DevOps via the service objects. When adding new commands, the functions that are called should be defined here.
58 |
59 | ## Debugging
60 |
61 | To debug the extension, make sure you've installed all of the npm packages as instructed earlier. Then, open the root of the repository in Visual Studio Code and press F5. If you have the extension already installed, you'll need to uninstall it via the Command Palette and try again.
62 |
63 | ## Code Styles
64 |
65 | 1. Build commands will run `tslint` and flag any errors. Please ensure that the code stays clean.
66 | 2. All source files must have the following lines at the top:
67 |
68 | ```
69 | // Copyright (c) Microsoft Corporation. All rights reserved.
70 | // Licensed under the MIT License. See LICENSE in the project root for license information.
71 | ```
72 |
73 | 3. We keep LF line-endings on the server. Please set the `core.safecrlf` git config property to true.
74 |
75 | ```
76 | git config core.safecrlf true
77 | ```
78 |
79 | ## Packaging and releasing
80 |
81 | 1. Increment the extension version in `package.json`
82 | 2. Run `npm install -g vsce`
83 | 3. Run `vsce package`
84 | 4. Create and upload the package to a new GitHub release on the repo
85 |
86 | See the [Publishing Extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) Visual Studio Code documentation for more information.
87 |
88 | ## Contribution License Agreement
89 |
90 | In order to contribute, you will need to sign a [Contributor License Agreement](https://cla.microsoft.com/).
91 |
92 | ## Code of Conduct
93 |
94 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
95 |
96 | ## Submitting Pull Requests
97 |
98 | We welcome pull requests! Fork this repo and send us your contributions. Go [here](https://help.github.com/articles/using-pull-requests/) to get familiar with GitHub pull requests.
99 |
--------------------------------------------------------------------------------
/src/views/workitems/workitem.tree.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import * as vscode from "vscode";
5 | import { WorkItemComposite } from "./workitem";
6 | import { MyWorkProvider } from "./workitem.mywork";
7 | import { getCurrentOrganization } from "../../configuration/configuration";
8 | import { ConfigurationCommands } from "../../configuration/commands";
9 | import { Commands } from "../../commands/commands";
10 | import { trackTelemetryException } from "../../util/telemetry";
11 |
12 | export class WorkItemTreeNodeProvider
13 | implements vscode.TreeDataProvider {
14 | private _onDidChangeTreeData: vscode.EventEmitter<
15 | TreeNodeParent | undefined
16 | > = new vscode.EventEmitter();
17 |
18 | readonly onDidChangeTreeData: vscode.Event = this
19 | ._onDidChangeTreeData.event;
20 |
21 | getChildren(
22 | element?: TreeNodeParent | undefined
23 | ): vscode.ProviderResult {
24 | if (!element) {
25 | if (!vscode.workspace.workspaceFolders) {
26 | return [new NoOpenFolderNode()];
27 | }
28 |
29 | if (!getCurrentOrganization()) {
30 | return [new NoConnectionNode()];
31 | }
32 |
33 | return [
34 | new TreeNodeChildWorkItem("Assigned to me", "AssignedToMe"),
35 | new TreeNodeChildWorkItem("My activity", "MyActivity"),
36 | new TreeNodeChildWorkItem("Mentioned", "Mentioned"),
37 | new TreeNodeChildWorkItem("Following", "Following")
38 | ];
39 | }
40 |
41 | return element.getWorkItemsForNode();
42 | }
43 |
44 | getTreeItem(
45 | element: TreeNodeParent
46 | ): vscode.TreeItem | Thenable {
47 | return element;
48 | }
49 |
50 | refresh(): void {
51 | // Cause view to refresh
52 | this._onDidChangeTreeData.fire();
53 | }
54 | }
55 |
56 | export class TreeNodeParent extends vscode.TreeItem {
57 | constructor(
58 | public readonly label: string,
59 | collapsibleState: vscode.TreeItemCollapsibleState = vscode
60 | .TreeItemCollapsibleState.None
61 | ) {
62 | super(label, collapsibleState);
63 | }
64 |
65 | async getWorkItemsForNode(): Promise {
66 | return [];
67 | }
68 | }
69 |
70 | class NoOpenFolderNode extends TreeNodeParent {
71 | constructor() {
72 | super(Resources.Configuration_NoOpenFolder);
73 |
74 | this.contextValue = "no-folder";
75 | this.iconPath = undefined;
76 | }
77 | }
78 |
79 | class NoConnectionNode extends TreeNodeParent {
80 | constructor() {
81 | super(Resources.Configuration_ClickToConnect);
82 |
83 | this.contextValue = "no-connection";
84 | this.iconPath = undefined;
85 | this.command = {
86 | title: "Connect",
87 | command: ConfigurationCommands.SelectOrganization
88 | };
89 | }
90 | }
91 |
92 | export class TreeNodeChildWorkItem extends TreeNodeParent {
93 | constructor(label: string, private readonly type: string) {
94 | super(label, vscode.TreeItemCollapsibleState.Collapsed);
95 | }
96 |
97 | async getWorkItemsForNode(): Promise {
98 | try {
99 | //go get the work items from the mywork provider
100 | const myWorkProvider: MyWorkProvider = new MyWorkProvider();
101 |
102 | //get mashed list of workitems from the myworkprovider
103 | const workItems = await myWorkProvider.getMyWorkItems(this.type);
104 |
105 | return workItems.map(wi => new WorkItemNode(wi));
106 | } catch (e) {
107 | // track telemetry exception
108 | trackTelemetryException(e);
109 |
110 | console.error(e);
111 | }
112 |
113 | return [];
114 | }
115 | }
116 |
117 | export class WorkItemNode extends TreeNodeParent {
118 | public readonly workItemId: number;
119 | public readonly workItemType: string;
120 | public readonly iconPath: vscode.Uri;
121 | public readonly editUrl: string;
122 |
123 | constructor(workItemComposite: WorkItemComposite) {
124 | super(`${workItemComposite.workItemId} ${workItemComposite.workItemTitle}`);
125 |
126 | this.iconPath = vscode.Uri.parse(workItemComposite.workItemIcon);
127 | this.workItemId = +workItemComposite.workItemId;
128 | this.workItemType = workItemComposite.workItemType;
129 | this.editUrl = workItemComposite.url;
130 | this.contextValue = "work-item";
131 | this.tooltip = "Open work item in Azure Boards";
132 |
133 | this.command = {
134 | command: Commands.WorkItemOpen,
135 | arguments: [this.editUrl],
136 | title: "Open work item in Azure Boards"
137 | };
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Azure Boards Extension for Visual Studio Code
2 |
3 | 
4 |
5 | This extension provides easy access to your Azure Boards work items, directly from within Visual Studio Code. See the work assigned to you, work you've recently had activity on, work you've been mentioned on, and work you're following. You can open a work item to gather more context or make an edit. Once you're ready to commit, starting a commit message that includes a work item mention is just a click away.
6 |
7 | 
8 |
9 | ## Prerequisites
10 |
11 | ### Azure DevOps Services
12 |
13 | If you are using the extension with Azure DevOps Services, ensure you have an Azure DevOps Services organization. If you do
14 | not have one, [sign up for Azure DevOps Services](https://aka.ms/SignupAzureDevOps/?campaign=azure~boards~vscode~readme).
15 |
16 | ### Azure DevOps Server / Team Foundation Server
17 |
18 | Azure DevOps Server and Team Foundation Server are not yet supported.
19 |
20 | ## Installation
21 |
22 | First, you will need to install [Visual Studio Code](https://code.visualstudio.com/download) `1.30.0` or later.
23 |
24 | To install the extension with the latest version of Visual Studio Code (version 1.30.0 is the latest as of this writing), bring up the Visual Studio Code Command Palette (`F1`), type `install` and choose `Extensions: Install Extensions`. In the `Search Extensions in Marketplace` text box, type `azure boards`. Find the `Azure Boards` extension published by _Microsoft_ and click the `Install` button. Restart Visual Studio Code.
25 |
26 | ## Mentioning work items in a commit message
27 |
28 | Clicking the mention icon on a work item within the extension automatically switches over to Source Control and adds the work item ID mention to the commit message.
29 |
30 | - Azure Repos: #[work item id]
31 | - GitHub.com: AB#[work item id]
32 |
33 | Note: GitHub Enterprise Server support for prefixing with AB# is [on the backlog](https://github.com/microsoft/azure-boards-vscode/issues/53).
34 |
35 | ## Further development and roadmap
36 |
37 | This extension is in active development. While we believe the feature scenarios are fairly limited for this extension, if you do have a feature suggestion, go ahead and [log it in our issue list](https://github.com/microsoft/azure-boards-vscode/issues/new) or participate in [discussions](https://github.com/microsoft/azure-boards-vscode/issues) about existing suggestions.
38 |
39 | Check our [issue list](https://github.com/microsoft/azure-boards-vscode/issues) for enhancements we plan to make. You'll notice that some are set to a project, which indicates our intent to deliver.
40 |
41 | ## Support
42 |
43 | Support for this extension is provided on our [GitHub Issue Tracker](https://github.com/microsoft/azure-boards-vscode/issues). You
44 | can submit a [bug report](https://github.com/microsoft/azure-boards-vscode/issues/new), a [feature suggestion](https://github.com/microsoft/azure-boards-vscode/issues/new)
45 | or participate in [discussions](https://github.com/microsoft/azure-boards-vscode/issues).
46 |
47 | ## Contributing to the Extension
48 |
49 | See the [developer documentation](CONTRIBUTING.md) for details on how to contribute to this extension.
50 |
51 | ## Code of Conduct
52 |
53 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
54 |
55 | ## Privacy Statement
56 |
57 | The [Microsoft Privacy Statement](http://go.microsoft.com/fwlink/?LinkId=528096&clcid=0x409)
58 | describes the privacy statement of this software.
59 |
60 | ### Data/Telemetry
61 | This project collects usage data and sends it to Microsoft to help improve our products and services. Read Microsoft's [privacy statement to learn more](http://go.microsoft.com/fwlink/?LinkId=521839).
62 |
63 | ## Reporting Security Issues
64 |
65 | Security issues and bugs should be reported privately, via email, to the Microsoft Security
66 | Response Center (MSRC) at [secure@microsoft.com](mailto:secure@microsoft.com). You should
67 | receive a response within 24 hours. If for some reason you do not, please follow up via
68 | email to ensure we received your original message. Further information, including the
69 | [MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in
70 | the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
71 |
72 | ## License
73 |
74 | This extension is [licensed under the MIT License](LICENSE). Please see the [third-party notices](ThirdPartyNotices.txt)
75 | file for additional copyright notices and license terms applicable to portions of the software.
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "azure-boards",
3 | "displayName": "Azure Boards",
4 | "description": "",
5 | "version": "0.1.0",
6 | "license": "MIT",
7 | "publisher": "microsoft",
8 | "repository": {
9 | "url": "https://github.com/microsoft/azure-boards-vscode"
10 | },
11 | "engines": {
12 | "vscode": "^1.30.0"
13 | },
14 | "categories": [
15 | "Other"
16 | ],
17 | "activationEvents": [
18 | "onView:work-items",
19 | "onCommand:azure-boards.add-organization",
20 | "onCommand:azure-boards.remove-organization",
21 | "onCommand:azure-boards.select-organization"
22 | ],
23 | "extensionDependencies": [
24 | "vscode.git"
25 | ],
26 | "main": "./build/extension/extension",
27 | "contributes": {
28 | "viewsContainers": {
29 | "activitybar": [
30 | {
31 | "id": "azure-boards-container",
32 | "title": "Azure Boards",
33 | "icon": "media/azure-boards-logo.svg"
34 | }
35 | ]
36 | },
37 | "views": {
38 | "azure-boards-container": [
39 | {
40 | "id": "work-items",
41 | "name": "My work items"
42 | }
43 | ]
44 | },
45 | "commands": [
46 | {
47 | "command": "azure-boards.refresh",
48 | "title": "Refresh",
49 | "icon": {
50 | "light": "resources/light/refresh.svg",
51 | "dark": "resources/dark/refresh.svg"
52 | }
53 | },
54 | {
55 | "command": "azure-boards.open-work-item",
56 | "title": "Open work item in Azure Boards",
57 | "icon": {
58 | "light": "resources/light/globe.svg",
59 | "dark": "resources/dark/globe.svg"
60 | }
61 | },
62 | {
63 | "command": "azure-boards.mention-work-item",
64 | "title": "Mention work item in commit message",
65 | "icon": {
66 | "light": "resources/light/mention.svg",
67 | "dark": "resources/dark/mention.svg"
68 | }
69 | },
70 | {
71 | "command": "azure-boards.add-organization",
72 | "title": "Azure Boards: Connect to an organization"
73 | },
74 | {
75 | "command": "azure-boards.remove-organization",
76 | "title": "Azure Boards: Remove a connected organization"
77 | },
78 | {
79 | "command": "azure-boards.select-organization",
80 | "title": "Azure Boards: Select preferred organization"
81 | }
82 | ],
83 | "menus": {
84 | "view/title": [
85 | {
86 | "command": "azure-boards.refresh",
87 | "when": "view == work-items",
88 | "group": "navigation"
89 | }
90 | ],
91 | "view/item/context": [
92 | {
93 | "command": "azure-boards.open-work-item",
94 | "when": "view == work-items && viewItem == work-item",
95 | "group": "inline"
96 | },
97 | {
98 | "command": "azure-boards.mention-work-item",
99 | "when": "view == work-items && viewItem == work-item && config.git.enabled && gitOpenRepositoryCount != 0",
100 | "group": "inline@+1"
101 | }
102 | ]
103 | },
104 | "configuration": {
105 | "type": "object",
106 | "title": "Azure Boards",
107 | "properties": {
108 | "azure-boards.current-organization": {
109 | "title": "Current organization",
110 | "description": "Current Azure Boards organization",
111 | "scope": "window"
112 | },
113 | "azure-boards.current-project": {
114 | "title": "Current project",
115 | "description": "Current Azure Boards projects",
116 | "scope": "window"
117 | },
118 | "azure-boards.organizations": {
119 | "type": "array",
120 | "items": {
121 | "type": "object",
122 | "properties": {
123 | "uri": {
124 | "type": "string",
125 | "default": ""
126 | }
127 | }
128 | },
129 | "description": "Azure Boards organizations to connect to",
130 | "scope": "application"
131 | }
132 | }
133 | }
134 | },
135 | "scripts": {
136 | "vscode:prepublish": "npm run compile",
137 | "compile": "tsc -p ./",
138 | "watch": "tsc -watch -p ./",
139 | "postinstall": "node ./node_modules/vscode/bin/install",
140 | "test": "npm run compile && node ./node_modules/vscode/bin/test",
141 | "package": "npm version patch --no-git-tag-version && vsce package"
142 | },
143 | "devDependencies": {
144 | "@types/keytar": "^4.4.0",
145 | "@types/mocha": "^2.2.42",
146 | "@types/node": "^8.10.25",
147 | "tslint": "5.15.0",
148 | "typescript": "3.3.4000",
149 | "vsce": "^1.62.0",
150 | "vscode": "1.1.33"
151 | },
152 | "dependencies": {
153 | "applicationinsights": "^1.3.1",
154 | "azure-devops-node-api": "^7.0.0",
155 | "keytar": "^4.6.0",
156 | "vsts-device-flow-auth": "^1.136.0"
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/ThirdPartyNotices.txt:
--------------------------------------------------------------------------------
1 |
2 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
3 | Do Not Translate or Localize
4 |
5 | Azure Boards Extension for Visual Studio Code incorporates third party material
6 | (and other Microsoft material) from the projects listed below. The original copyright notice
7 | and the license under which Microsoft received such third party material are set forth below.
8 | Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or
9 | otherwise.
10 |
11 |
12 | 1. Application Insights for Node.js (https://github.com/microsoft/ApplicationInsights-node.js)
13 | 2. azure-devops-node-api (https://github.com/microsoft/azure-devops-node-api)
14 | 3. keytar (https://github.com/atom/node-keytar)
15 | 4. vsts-device-flow-auth (https://github.com/microsoft/vsts-device-flow-auth)
16 |
17 |
18 | %% Application Insights for Node.js NOTICES, INFORMATION, AND LICENSE BEGIN HERE
19 | =========================================
20 | MIT License
21 |
22 | Copyright (c) Microsoft Corporation. All rights reserved.
23 |
24 | Permission is hereby granted, free of charge, to any person obtaining a copy
25 | of this software and associated documentation files (the "Software"), to deal
26 | in the Software without restriction, including without limitation the rights
27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28 | copies of the Software, and to permit persons to whom the Software is
29 | furnished to do so, subject to the following conditions:
30 |
31 | The above copyright notice and this permission notice shall be included in all
32 | copies or substantial portions of the Software.
33 |
34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40 | SOFTWARE
41 | =========================================
42 | END OF Application Insights for Node.js NOTICES, INFORMATION, AND LICENSE
43 |
44 | %% azure-devops-node-api NOTICES, INFORMATION, AND LICENSE BEGIN HERE
45 | =========================================
46 | Visual Studio Team Services Client for Node.js
47 |
48 | Copyright (c) Microsoft Corporation
49 |
50 | All rights reserved.
51 |
52 | MIT License
53 |
54 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
55 | associated documentation files (the "Software"), to deal in the Software without restriction,
56 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
57 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
58 | subject to the following conditions:
59 |
60 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
61 |
62 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
63 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
64 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
65 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
67 | =========================================
68 | END OF azure-devops-node-api NOTICES, INFORMATION, AND LICENSE
69 |
70 | %% keytar NOTICES, INFORMATION, AND LICENSE BEGIN HERE
71 | =========================================
72 | Copyright (c) 2013 GitHub Inc.
73 |
74 | Permission is hereby granted, free of charge, to any person obtaining
75 | a copy of this software and associated documentation files (the
76 | "Software"), to deal in the Software without restriction, including
77 | without limitation the rights to use, copy, modify, merge, publish,
78 | distribute, sublicense, and/or sell copies of the Software, and to
79 | permit persons to whom the Software is furnished to do so, subject to
80 | the following conditions:
81 |
82 | The above copyright notice and this permission notice shall be
83 | included in all copies or substantial portions of the Software.
84 |
85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
88 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
89 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
90 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
91 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
92 | =========================================
93 | END OF keytar NOTICES, INFORMATION, AND LICENSE
94 |
95 | %% vsts-device-flow-auth NOTICES, INFORMATION, AND LICENSE BEGIN HERE
96 | =========================================
97 | MIT License
98 |
99 | Copyright (c) Microsoft Corporation. All rights reserved.
100 |
101 | Permission is hereby granted, free of charge, to any person obtaining a copy
102 | of this software and associated documentation files (the "Software"), to deal
103 | in the Software without restriction, including without limitation the rights
104 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
105 | copies of the Software, and to permit persons to whom the Software is
106 | furnished to do so, subject to the following conditions:
107 |
108 | The above copyright notice and this permission notice shall be included in all
109 | copies or substantial portions of the Software.
110 |
111 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
112 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
113 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
117 | SOFTWARE
118 | =========================================
119 | END OF vsts-device-flow-auth NOTICES, INFORMATION, AND LICENSE
--------------------------------------------------------------------------------
/src/configuration/commands.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { format } from "util";
5 | import * as vscode from "vscode";
6 | import { Commands } from "../commands/commands";
7 | import { getWebApiForOrganization } from "../connection";
8 | import { getTokenUsingDeviceFlow } from "./auth";
9 | import {
10 | addOrganization,
11 | getConfiguration,
12 | getCurrentOrganization,
13 | IOrganization,
14 | IProject,
15 | organizationExists,
16 | removeOrganization,
17 | setCurrentOrganization,
18 | setCurrentProject,
19 | compareOrganizations,
20 | compareProjects
21 | } from "./configuration";
22 | import { isValidAzureBoardsUrl } from "./url";
23 |
24 | export const enum ConfigurationCommands {
25 | AddOrganization = "azure-boards.add-organization",
26 | RemoveOrganization = "azure-boards.remove-organization",
27 | SelectOrganization = "azure-boards.select-organization"
28 | }
29 |
30 | export function registerConfigurationCommands(
31 | context: vscode.ExtensionContext
32 | ): void {
33 | context.subscriptions.push(
34 | vscode.commands.registerCommand(
35 | ConfigurationCommands.AddOrganization,
36 | async () => {
37 | const organizationUri = await vscode.window.showInputBox({
38 | prompt: Resources.Configuration_OrganizationUriPrompt,
39 | placeHolder: "https://dev.azure.com/"
40 | });
41 |
42 | if (!organizationUri) {
43 | return;
44 | }
45 |
46 | if (!isValidAzureBoardsUrl(organizationUri)) {
47 | vscode.window.showErrorMessage(
48 | format(Resources.Configuration_InvalidUri, organizationUri)
49 | );
50 |
51 | return;
52 | }
53 |
54 | const organization = { uri: organizationUri };
55 |
56 | if (organizationExists(organization)) {
57 | vscode.window.showErrorMessage(
58 | format(Resources.Configuration_OrganizationExists, organizationUri)
59 | );
60 |
61 | return;
62 | }
63 |
64 | const token = await getTokenUsingDeviceFlow(organization);
65 | if (!token) {
66 | return;
67 | }
68 |
69 | // Store this organization and token as known
70 | await addOrganization(organization, token);
71 |
72 | vscode.window.showInformationMessage(
73 | `${Resources.Configuration_AddedOrganization} ${organizationUri}`
74 | );
75 | }
76 | )
77 | );
78 |
79 | context.subscriptions.push(
80 | vscode.commands.registerCommand(
81 | ConfigurationCommands.RemoveOrganization,
82 | async () => {
83 | const organization = await selectOrganization();
84 | if (!organization || organization === "add") {
85 | return;
86 | }
87 |
88 | await removeOrganization(organization);
89 |
90 | // If the removed organization was the current one, remove that as well
91 | const currentOrganization = getCurrentOrganization();
92 | if (
93 | currentOrganization &&
94 | organization.uri.toLocaleLowerCase() ===
95 | currentOrganization.uri.toLocaleLowerCase()
96 | ) {
97 | setCurrentOrganization(undefined);
98 | setCurrentProject(undefined);
99 | }
100 |
101 | vscode.window.showInformationMessage(
102 | Resources.Configuration_RemovedOrganization
103 | );
104 | }
105 | )
106 | );
107 |
108 | context.subscriptions.push(
109 | vscode.commands.registerCommand(
110 | ConfigurationCommands.SelectOrganization,
111 | async () => {
112 | const organization = await selectOrganization(true);
113 | if (!organization) {
114 | return;
115 | }
116 |
117 | if (organization === "add") {
118 | // Add new organization, then restart selection
119 | await vscode.commands.executeCommand(
120 | ConfigurationCommands.AddOrganization
121 | );
122 | await vscode.commands.executeCommand(
123 | ConfigurationCommands.SelectOrganization
124 | );
125 | } else {
126 | const project = await selectProject(organization);
127 | if (project) {
128 | await setCurrentOrganization(organization);
129 | await setCurrentProject(project);
130 | }
131 |
132 | // Refresh view now that we have a connection
133 | await vscode.commands.executeCommand(Commands.Refresh);
134 | }
135 | }
136 | )
137 | );
138 | }
139 |
140 | interface IOrganizationQuickPickItem extends vscode.QuickPickItem {
141 | organization?: IOrganization;
142 | }
143 |
144 | async function selectOrganization(
145 | allowAdd?: boolean
146 | ): Promise {
147 | const { organizations } = getConfiguration();
148 | const AddOrganizationItem: IOrganizationQuickPickItem = {
149 | label: "➕ Add organization"
150 | };
151 |
152 | const organizationOptions = organizations.sort(compareOrganizations).map(
153 | organization =>
154 | ({
155 | label: organization.uri,
156 | organization
157 | } as IOrganizationQuickPickItem)
158 | );
159 | if (allowAdd) {
160 | organizationOptions.unshift(AddOrganizationItem);
161 | }
162 |
163 | const selection = await vscode.window.showQuickPick(organizationOptions, {
164 | placeHolder: Resources.Configuration_SelectOrganization
165 | });
166 | if (selection) {
167 | if (selection === AddOrganizationItem) {
168 | return "add";
169 | }
170 |
171 | return selection.organization;
172 | }
173 |
174 | return undefined;
175 | }
176 |
177 | interface IProjectQuickPickItem extends vscode.QuickPickItem {
178 | project: IProject;
179 | }
180 |
181 | async function selectProject(
182 | organization: IOrganization
183 | ): Promise {
184 | const projects = await vscode.window.withProgress(
185 | {
186 | location: vscode.ProgressLocation.Window,
187 | title: Resources.Configuration_LoadingProjects
188 | },
189 | async () => {
190 | const webApi = await getWebApiForOrganization(organization);
191 | const coreApi = await webApi.getCoreApi(organization.uri);
192 | const projects = await coreApi.getProjects();
193 | return projects.map(
194 | p =>
195 | ({
196 | id: p.id,
197 | name: p.name
198 | } as IProject)
199 | );
200 | }
201 | );
202 |
203 | const selection = await vscode.window.showQuickPick(
204 | projects.sort(compareProjects).map(
205 | p =>
206 | ({
207 | label: p.name,
208 | project: p
209 | } as IProjectQuickPickItem)
210 | ),
211 | {
212 | placeHolder: Resources.Configuration_SelectProject
213 | }
214 | );
215 | if (!selection) {
216 | return undefined;
217 | }
218 |
219 | return selection.project;
220 | }
221 |
--------------------------------------------------------------------------------
/src/externals/git.d.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License. See LICENSE in the project root for license information.
3 |
4 | import { Uri, SourceControlInputBox, Event, CancellationToken } from "vscode";
5 |
6 | export interface Git {
7 | readonly path: string;
8 | }
9 |
10 | export interface InputBox {
11 | value: string;
12 | }
13 |
14 | export const enum RefType {
15 | Head,
16 | RemoteHead,
17 | Tag
18 | }
19 |
20 | export interface Ref {
21 | readonly type: RefType;
22 | readonly name?: string;
23 | readonly commit?: string;
24 | readonly remote?: string;
25 | }
26 |
27 | export interface UpstreamRef {
28 | readonly remote: string;
29 | readonly name: string;
30 | }
31 |
32 | export interface Branch extends Ref {
33 | readonly upstream?: UpstreamRef;
34 | readonly ahead?: number;
35 | readonly behind?: number;
36 | }
37 |
38 | export interface Commit {
39 | readonly hash: string;
40 | readonly message: string;
41 | readonly parents: string[];
42 | readonly authorEmail?: string | undefined;
43 | }
44 |
45 | export interface Submodule {
46 | readonly name: string;
47 | readonly path: string;
48 | readonly url: string;
49 | }
50 |
51 | export interface Remote {
52 | readonly name: string;
53 | readonly fetchUrl?: string;
54 | readonly pushUrl?: string;
55 | readonly isReadOnly: boolean;
56 | }
57 |
58 | export const enum Status {
59 | INDEX_MODIFIED,
60 | INDEX_ADDED,
61 | INDEX_DELETED,
62 | INDEX_RENAMED,
63 | INDEX_COPIED,
64 |
65 | MODIFIED,
66 | DELETED,
67 | UNTRACKED,
68 | IGNORED,
69 | INTENT_TO_ADD,
70 |
71 | ADDED_BY_US,
72 | ADDED_BY_THEM,
73 | DELETED_BY_US,
74 | DELETED_BY_THEM,
75 | BOTH_ADDED,
76 | BOTH_DELETED,
77 | BOTH_MODIFIED
78 | }
79 |
80 | export interface Change {
81 | /**
82 | * Returns either `originalUri` or `renameUri`, depending
83 | * on whether this change is a rename change. When
84 | * in doubt always use `uri` over the other two alternatives.
85 | */
86 | readonly uri: Uri;
87 | readonly originalUri: Uri;
88 | readonly renameUri: Uri | undefined;
89 | readonly status: Status;
90 | }
91 |
92 | export interface RepositoryState {
93 | readonly HEAD: Branch | undefined;
94 | readonly refs: Ref[];
95 | readonly remotes: Remote[];
96 | readonly submodules: Submodule[];
97 | readonly rebaseCommit: Commit | undefined;
98 |
99 | readonly mergeChanges: Change[];
100 | readonly indexChanges: Change[];
101 | readonly workingTreeChanges: Change[];
102 |
103 | readonly onDidChange: Event;
104 | }
105 |
106 | export interface RepositoryUIState {
107 | readonly selected: boolean;
108 | readonly onDidChange: Event;
109 | }
110 |
111 | /**
112 | * Log options.
113 | */
114 | export interface LogOptions {
115 | /** Max number of log entries to retrieve. If not specified, the default is 32. */
116 | readonly maxEntries?: number;
117 | }
118 |
119 | export interface Repository {
120 | readonly rootUri: Uri;
121 | readonly inputBox: InputBox;
122 | readonly state: RepositoryState;
123 | readonly ui: RepositoryUIState;
124 |
125 | getConfigs(): Promise<{ key: string; value: string }[]>;
126 | getConfig(key: string): Promise;
127 | setConfig(key: string, value: string): Promise;
128 | getGlobalConfig(key: string): Promise;
129 |
130 | getObjectDetails(
131 | treeish: string,
132 | path: string
133 | ): Promise<{ mode: string; object: string; size: number }>;
134 | detectObjectType(
135 | object: string
136 | ): Promise<{ mimetype: string; encoding?: string }>;
137 | buffer(ref: string, path: string): Promise;
138 | show(ref: string, path: string): Promise;
139 | getCommit(ref: string): Promise;
140 |
141 | clean(paths: string[]): Promise;
142 |
143 | apply(patch: string, reverse?: boolean): Promise;
144 | diff(cached?: boolean): Promise;
145 | diffWithHEAD(): Promise;
146 | diffWithHEAD(path: string): Promise;
147 | diffWith(ref: string): Promise;
148 | diffWith(ref: string, path: string): Promise;
149 | diffIndexWithHEAD(): Promise;
150 | diffIndexWithHEAD(path: string): Promise;
151 | diffIndexWith(ref: string): Promise;
152 | diffIndexWith(ref: string, path: string): Promise;
153 | diffBlobs(object1: string, object2: string): Promise;
154 | diffBetween(ref1: string, ref2: string): Promise;
155 | diffBetween(ref1: string, ref2: string, path: string): Promise;
156 |
157 | hashObject(data: string): Promise;
158 |
159 | createBranch(name: string, checkout: boolean, ref?: string): Promise;
160 | deleteBranch(name: string, force?: boolean): Promise;
161 | getBranch(name: string): Promise;
162 | setBranchUpstream(name: string, upstream: string): Promise;
163 |
164 | getMergeBase(ref1: string, ref2: string): Promise;
165 |
166 | status(): Promise;
167 | checkout(treeish: string): Promise;
168 |
169 | addRemote(name: string, url: string): Promise;
170 | removeRemote(name: string): Promise;
171 |
172 | fetch(remote?: string, ref?: string, depth?: number): Promise;
173 | pull(unshallow?: boolean): Promise;
174 | push(
175 | remoteName?: string,
176 | branchName?: string,
177 | setUpstream?: boolean
178 | ): Promise;
179 |
180 | blame(path: string): Promise;
181 | log(options?: LogOptions): Promise;
182 | }
183 |
184 | export interface API {
185 | readonly git: Git;
186 | readonly repositories: Repository[];
187 | readonly onDidOpenRepository: Event;
188 | readonly onDidCloseRepository: Event;
189 | }
190 |
191 | export interface GitExtension {
192 | readonly enabled: boolean;
193 | readonly onDidChangeEnablement: Event;
194 |
195 | /**
196 | * Returns a specific API version.
197 | *
198 | * Throws error if git extension is disabled. You can listed to the
199 | * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event
200 | * to know when the extension becomes enabled/disabled.
201 | *
202 | * @param version Version number.
203 | * @returns API instance
204 | */
205 | getAPI(version: 1): API;
206 | }
207 |
208 | export const enum GitErrorCodes {
209 | BadConfigFile = "BadConfigFile",
210 | AuthenticationFailed = "AuthenticationFailed",
211 | NoUserNameConfigured = "NoUserNameConfigured",
212 | NoUserEmailConfigured = "NoUserEmailConfigured",
213 | NoRemoteRepositorySpecified = "NoRemoteRepositorySpecified",
214 | NotAGitRepository = "NotAGitRepository",
215 | NotAtRepositoryRoot = "NotAtRepositoryRoot",
216 | Conflict = "Conflict",
217 | StashConflict = "StashConflict",
218 | UnmergedChanges = "UnmergedChanges",
219 | PushRejected = "PushRejected",
220 | RemoteConnectionError = "RemoteConnectionError",
221 | DirtyWorkTree = "DirtyWorkTree",
222 | CantOpenResource = "CantOpenResource",
223 | GitNotFound = "GitNotFound",
224 | CantCreatePipe = "CantCreatePipe",
225 | CantAccessRemote = "CantAccessRemote",
226 | RepositoryNotFound = "RepositoryNotFound",
227 | RepositoryIsLocked = "RepositoryIsLocked",
228 | BranchNotFullyMerged = "BranchNotFullyMerged",
229 | NoRemoteReference = "NoRemoteReference",
230 | InvalidBranchName = "InvalidBranchName",
231 | BranchAlreadyExists = "BranchAlreadyExists",
232 | NoLocalChanges = "NoLocalChanges",
233 | NoStashFound = "NoStashFound",
234 | LocalChangesOverwritten = "LocalChangesOverwritten",
235 | NoUpstreamBranch = "NoUpstreamBranch",
236 | IsInSubmodule = "IsInSubmodule",
237 | WrongCase = "WrongCase",
238 | CantLockRef = "CantLockRef",
239 | CantRebaseMultipleBranches = "CantRebaseMultipleBranches",
240 | PatchDoesNotApply = "PatchDoesNotApply",
241 | NoPathFound = "NoPathFound"
242 | }
243 |
--------------------------------------------------------------------------------