├── 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 | Layer 1 -------------------------------------------------------------------------------- /resources/light/stage.svg: -------------------------------------------------------------------------------- 1 | Layer 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 | 2 | 3 | -------------------------------------------------------------------------------- /resources/light/open_external.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | 2 | 3 | -------------------------------------------------------------------------------- /resources/light/mention.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.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 | 2 | 3 | -------------------------------------------------------------------------------- /resources/dark/open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | background 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 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 | 2 | 3 | -------------------------------------------------------------------------------- /resources/light/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | ![Build Status](https://cs-extensions.visualstudio.com/Azure%20Boards%20VS%20Code/_apis/build/status/Azure%20Boards%20VS%20Code-CI?branchName=master) 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 | ![Screenshot](media/screenshot.png) 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 | --------------------------------------------------------------------------------