Skip to content

Commit 3b5e6d7

Browse files
author
Arnold Trakhtenberg
authored
Release 2.2.0 (#30)
1 parent 60bc966 commit 3b5e6d7

14 files changed

+1007
-368
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
All notable changes to the "launchdarkly" extension will be documented in this file.
44

5+
## [2.2.0] - 2020-01-06
6+
7+
### Added
8+
9+
- The extension now contributes a `LaunchDarkly: Configure` command to configure or reconfigure the extension. The extension will prompt users to configure on installation or update, or on obsolete configurations (see Changed section)
10+
11+
### Changed
12+
13+
- It is now possible to configure the extension without storing secrets in `settings.json`. Use the `LaunchDarkly: Configure` command to configure the extension. With this change, the `accessToken` configuration option is now deprecated, and will be automatically cleared when the `LaunchDarkly: Configure` is ran and completed.
14+
- The `sdkKey` configuration option is now obsolete. The SDK key will now be inferred from the configured project and environment.
15+
516
## [2.1.2] - 2019-12-26
617

718
### Fixed

README.md

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,33 @@ The LaunchDarkly VSCode extension provides some quality-of-life enhancements for
88
- Flag name autocomplete
99
- Open feature flags in LaunchDarkly (Default keybind: `ctrl+alt+g`/`⌘+alt+g`)
1010

11-
## Extension Settings
11+
## Installation and configuration
1212

1313
This extension contributes the following settings:
14-
15-
| Setting | Description | Default value |
16-
| --------------------------------- |:-------------------------------------------------------------------------------:| --------------------------------: |
17-
| `launchdarkly.sdkKey` | Your LaunchDarkly SDK key. Required. | `undefined` |
18-
| `launchdarkly.accessToken` | Your LaunchDarkly API access token. Required. | `undefined` |
19-
| `launchdarkly.project` | Your LaunchDarkly project key, should match the provided SDK key. Required. | `undefined` |
20-
| `launchdarkly.env` | Your LaunchDarkly environment key, should match the provided SDK key. | first environment |
21-
| `launchdarkly.baseUri` | The LaunchDarkly base uri to be used. Optional. | `https://app.launchdarkly.com` |
22-
| `launchdarkly.streamUri` | The LaunchDarkly stream uri to be used. Optional. | `https://stream.launchdarkly.com` |
23-
| `launchdarkly.enableHover` | Enables flag info to be displayed on hover of a valid flag key. | `true` |
24-
| `launchdarkly.enableAutocomplete` | Enable flag key autocompletion. | `true` |
14+
On installation of the LaunchDarkly extension, VSCode will prompt you to configure the extension, selecting a LaunchDarkly project and environment for your workspace. To reconfigure the extension, run the "LaunchDarkly: Configure" command from your command pallete.
15+
This extension contributes the following additional settings:
16+
17+
| Setting | Description | Default value |
18+
| --------------------------------- | :------------------------------------------------------------------------------------------------: | --------------------------------: |
19+
| `launchdarkly.project` | Your LaunchDarkly project key. Automatically configured by "LaunchDarkly: Configure". | `undefined` |
20+
| `launchdarkly.env` | Your LaunchDarkly environment key. Automatically configured by "LaunchDarkly: Configure". | `undefined` |
21+
| `launchdarkly.baseUri` | The LaunchDarkly base uri to be used. Optional. | `https://app.launchdarkly.com` |
22+
| `launchdarkly.streamUri` | The LaunchDarkly stream uri to be used. Optional. | `https://stream.launchdarkly.com` |
23+
| `launchdarkly.enableHover` | Enables flag info to be displayed on hover of a valid flag key. | `true` |
24+
| `launchdarkly.enableAutocomplete` | Enable flag key autocompletion. | `true` |
25+
| `launchdarkly.sdkKey` | Your LaunchDarkly SDK key. OBSOLETE: Run the 'LaunchDarkly: Configure' command instead. | `undefined` |
26+
| `launchdarkly.accessToken` | Your LaunchDarkly API access token. DEPRECATED: Run the 'LaunchDarkly: Configure' command instead. | `undefined` |
2527

2628
**Note:** If you use quick suggestions to autocomplete words, LaunchDarkly autocomplete functionality requires the `editor.quickSuggestions.strings` setting to be enabled. Otherwise, you'll need to press `Ctrl+Space` (default binding) to see your flag key suggestions.
2729

28-
Here's an example setting configuration with quick suggestions enabled:
30+
Here's an example configuration with quick suggestions enabled:
2931

30-
```javascript
32+
```json
3133
{
32-
"launchdarkly.accessToken": "api-xxx",
33-
"launchdarkly.sdkKey": "sdk-xxx",
34-
"launchdarkly.project": "default",
35-
"launchdarkly.env": "production",
36-
"editor.quickSuggestions": {
37-
"other": true,
38-
"comments": false,
39-
"strings": true
40-
},
34+
"editor.quickSuggestions": {
35+
"other": true,
36+
"comments": false,
37+
"strings": true
38+
}
4139
}
4240
```

package.json

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "launchdarkly",
33
"displayName": "LaunchDarkly",
44
"description": "View LaunchDarkly feature flags in your editor.",
5-
"version": "2.1.2",
5+
"version": "2.2.0",
66
"publisher": "launchdarkly",
77
"engines": {
88
"vscode": "^1.34.0"
@@ -22,16 +22,6 @@
2222
"type": "object",
2323
"title": "LaunchDarkly",
2424
"properties": {
25-
"launchdarkly.accessToken": {
26-
"type": "string",
27-
"default": "",
28-
"description": "LaunchDarkly API access token"
29-
},
30-
"launchdarkly.sdkKey": {
31-
"type": "string",
32-
"default": "",
33-
"description": "LaunchDarkly SDK key"
34-
},
3525
"launchdarkly.project": {
3626
"type": "string",
3727
"default": "",
@@ -61,6 +51,16 @@
6151
"type": "boolean",
6252
"default": true,
6353
"description": "Enable flag key autocompletion"
54+
},
55+
"launchdarkly.accessToken": {
56+
"type": "string",
57+
"default": "",
58+
"description": "LaunchDarkly API access token. DEPRECATED: Run the 'LaunchDarkly: Configure' command instead."
59+
},
60+
"launchdarkly.sdkKey": {
61+
"type": "string",
62+
"default": "",
63+
"description": "LaunchDarkly SDK key. OBSOLETE: Run the 'LaunchDarkly: Configure' command instead."
6464
}
6565
}
6666
},
@@ -69,6 +69,10 @@
6969
"command": "extension.openInLaunchDarkly",
7070
"title": "LaunchDarkly: Open in LaunchDarkly",
7171
"when": "editorTextFocus"
72+
},
73+
{
74+
"command": "extension.configureLaunchDarkly",
75+
"title": "LaunchDarkly: Configure"
7276
}
7377
],
7478
"menus": {
@@ -117,9 +121,10 @@
117121
"@types/opn": "5.1.0",
118122
"eventsource": "^1.0.5",
119123
"launchdarkly-node-server-sdk": "5.10.0",
124+
"lodash.debounce": "4.0.8",
120125
"lodash.kebabcase": "4.1.1",
121126
"opn": "5.3.0",
122-
"request": "2.88.0"
127+
"request-promise-native": "1.0.8"
123128
},
124129
"resolutions": {
125130
"node.extend": "^1.1.7",

src/api.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as rp from 'request-promise-native';
2+
import * as url from 'url';
3+
4+
import { Configuration } from './configuration';
5+
import { Resource, Project, Environment, Flag } from './models';
6+
7+
// LaunchDarklyAPI is a wrapper around request-promise-native for requesting data from LaunchDarkly's REST API. The caller is expected to catch all exceptions.
8+
export class LaunchDarklyAPI {
9+
private readonly config: Configuration;
10+
11+
constructor(config: Configuration) {
12+
this.config = config;
13+
}
14+
15+
async getAccount() {
16+
const options = this.createOptions('account');
17+
const account = await rp(options);
18+
return JSON.parse(account);
19+
}
20+
21+
async getProjects(): Promise<Array<Project>> {
22+
const options = this.createOptions('projects');
23+
const data = await rp(options);
24+
const projects = JSON.parse(data).items;
25+
projects.forEach((proj: Project) => {
26+
proj.environments = proj.environments.sort(sortNameCaseInsensitive);
27+
return proj;
28+
});
29+
return projects.sort(sortNameCaseInsensitive);
30+
}
31+
32+
async getEnvironment(projectKey: string, envKey: string): Promise<Environment> {
33+
const options = this.createOptions(`projects/${projectKey}/environments/${envKey}`);
34+
const data = await rp(options);
35+
return JSON.parse(data);
36+
}
37+
38+
async getFeatureFlag(projectKey: string, flagKey: string, envKey?: string): Promise<Flag> {
39+
const envParam = envKey ? '?env=' + envKey : '';
40+
const options = this.createOptions(`flags/${projectKey}/${flagKey + envParam}`);
41+
const data = await rp(options);
42+
return JSON.parse(data);
43+
}
44+
45+
private createOptions(path: string) {
46+
return {
47+
url: url.resolve(this.config.baseUri, `api/v2/${path}`),
48+
headers: {
49+
Authorization: this.config.accessToken,
50+
},
51+
};
52+
}
53+
}
54+
55+
const sortNameCaseInsensitive = (a: Resource, b: Resource) => {
56+
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
57+
};

src/configuration.ts

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,82 @@
1-
import * as vscode from 'vscode';
1+
import { WorkspaceConfiguration, workspace, ExtensionContext } from 'vscode';
22

3-
export const DEFAULT_BASE_URI = 'https://app.launchdarkly.com';
4-
export const DEFAULT_STREAM_URI = 'https://stream.launchdarkly.com';
3+
const package_json = require('../package.json');
54

6-
export interface IConfiguration {
7-
/**
8-
* Your LaunchDarkly API access token with reader-level permissions. Required.
9-
*/
10-
accessToken: string;
5+
const DEFAULT_BASE_URI = 'https://app.launchdarkly.com';
6+
const DEFAULT_STREAM_URI = 'https://stream.launchdarkly.com';
117

12-
/**
13-
* Your LaunchDarkly SDK key. Required.
14-
*/
15-
sdkKey: string;
8+
export class Configuration {
9+
private readonly ctx: ExtensionContext;
10+
accessToken = '';
11+
sdkKey = '';
12+
project = '';
13+
env = '';
14+
enableHover = true;
15+
enableAutocomplete = true;
16+
baseUri = DEFAULT_BASE_URI;
17+
streamUri = DEFAULT_STREAM_URI;
1618

17-
/**
18-
* Your LaunchDarkly project key, should match the provided SDK key. Required.
19-
*/
20-
project: string;
19+
constructor(ctx: ExtensionContext) {
20+
this.ctx = ctx;
21+
this.reload();
22+
}
2123

22-
/**
23-
* Your LaunchDarkly environment key, should match the provided SDK key.
24-
*/
25-
env: string;
24+
reload() {
25+
const config = workspace.getConfiguration('launchdarkly');
26+
for (const option in this) {
27+
if (option === 'ctx') {
28+
continue;
29+
}
30+
this[option] = config.get(option);
31+
}
2632

27-
/**
28-
* Enables flag info to be displayed on hover of a valid flag key.
29-
*/
30-
enableHover: boolean;
33+
// If accessToken is configured in state, use it. Otherwise, fall back to the legacy access token.
34+
this.accessToken = this.getState('accessToken') || this.accessToken;
35+
}
3136

32-
/**
33-
* Enable flag key autocompletion.
34-
*/
35-
enableAutocomplete: boolean;
37+
async update(key: string, value: string | boolean, global: boolean) {
38+
if (typeof this[key] !== typeof value) {
39+
return;
40+
}
3641

37-
/**
38-
* The LaunchDarkly base uri to be used. Optional.
39-
*/
40-
baseUri: string;
42+
let config: WorkspaceConfiguration = workspace.getConfiguration('launchdarkly');
43+
if (key === 'accessToken') {
44+
const ctxState = global ? this.ctx.globalState : this.ctx.workspaceState;
45+
await ctxState.update(key, value);
46+
await config.update(key, '', global);
47+
return;
48+
}
4149

42-
/**
43-
* The LaunchDarkly stream uri to be used. Optional.
44-
*/
45-
streamUri: string;
46-
}
50+
await config.update(key, value, global);
51+
config = workspace.getConfiguration('launchdarkly');
4752

48-
class Configuration implements IConfiguration {
49-
constructor() {
50-
this.reload();
53+
this[key] = value;
54+
process.nextTick(function() {});
5155
}
5256

53-
reload() {
54-
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('launchdarkly');
55-
for (const option in this) {
56-
this[option] = config[option];
57+
validate(): string {
58+
const version = package_json.version;
59+
const ctx = this.ctx;
60+
ctx.globalState.update('version', undefined);
61+
const storedVersion = ctx.globalState.get('version');
62+
63+
if (version !== storedVersion) {
64+
ctx.globalState.update('version', version);
65+
}
66+
67+
const legacyConfiguration = !!this.sdkKey;
68+
if (legacyConfiguration && !ctx.globalState.get('legacyNotificationDismissed')) {
69+
return 'legacy';
70+
}
71+
72+
// Only recommend configuring the extension on install and update
73+
const configured = !!this.accessToken;
74+
if (version != storedVersion && !configured) {
75+
return 'unconfigured';
5776
}
5877
}
5978

60-
accessToken = '';
61-
sdkKey = '';
62-
project = '';
63-
env = '';
64-
enableHover = true;
65-
enableAutocomplete = true;
66-
baseUri = DEFAULT_BASE_URI;
67-
streamUri = DEFAULT_STREAM_URI;
79+
getState(key: string): string {
80+
return this.ctx.workspaceState.get(key) || this.ctx.globalState.get(key);
81+
}
6882
}
69-
70-
export const configuration = new Configuration();

0 commit comments

Comments
 (0)