Skip to content

Commit

Permalink
[ch23712] Hotreload configuration options on change (#5)
Browse files Browse the repository at this point in the history
* fix default settings options to make configuration easier

* Refactor flag processing and language providers into a FlagManager class

* Update readme

* Prepare 2.0.1

* test/flags.ts -> test/flags.test.ts
  • Loading branch information
Arnold Trakhtenberg authored Oct 2, 2018
1 parent cff04bd commit 50389c7
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 248 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

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

## [2.0.1] - 2018-09-28

### Fixed

- Configuration settings no longer require manually editing the json settings file
- The extension no longer requires a restart to apply configuration changes

## [2.0.0] - 2018-09-27

### Added
Expand Down
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ This extension contributes the following settings:
| `launchdarkly.env` | Your LaunchDarkly environment key, should match the provided SDK key. | first environment |
| `launchdarkly.baseUri` | The LaunchDarkly base uri to be used. Optional. | `https://app.launchdarkly.com` |
| `launchdarkly.streamUri` | The LaunchDarkly stream uri to be used. Optional. | `https://stream.launchdarkly.com` |
| `launchdarkly.enableHover` | Enables flag info to be displayed on hover of a valid flag key. | `https://app.launchdarkly.com` |
| `launchdarkly.enableAutocomplete` | Enable flag key autocompletion. | `https://stream.launchdarkly.com` |

Changing settings requires a VSCode window reload.
| `launchdarkly.enableHover` | Enables flag info to be displayed on hover of a valid flag key. | true |
| `launchdarkly.enableAutocomplete` | Enable flag key autocompletion. | true |

**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.

Expand Down
16 changes: 6 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,22 @@
"properties": {
"launchdarkly.accessToken": {
"type": "string",
"default": "",
"description": "LaunchDarkly API access token"
},
"launchdarkly.sdkKey": {
"type": "string",
"default": "",
"description": "LaunchDarkly SDK key"
},
"launchdarkly.project": {
"type": [
"string",
"null"
],
"default": null,
"type": "string",
"default": "",
"description": "LaunchDarkly project key"
},
"launchdarkly.env": {
"type": [
"string",
"null"
],
"default": null,
"type": "string",
"default": "",
"description": "LaunchDarkly environment key"
},
"launchdarkly.baseUri": {
Expand Down
70 changes: 70 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as vscode from 'vscode';

export const DEFAULT_BASE_URI = 'https://app.launchdarkly.com';
export const DEFAULT_STREAM_URI = 'https://stream.launchdarkly.com';

export interface IConfiguration {
/**
* Your LaunchDarkly API access token with reader-level permissions. Required.
*/
accessToken: string;

/**
* Your LaunchDarkly SDK key. Required.
*/
sdkKey: string;

/**
* Your LaunchDarkly project key, should match the provided SDK key. Required.
*/
project: string;

/**
* Your LaunchDarkly environment key, should match the provided SDK key.
*/
env: string;

/**
* Enables flag info to be displayed on hover of a valid flag key.
*/
enableHover: boolean;

/**
* Enable flag key autocompletion.
*/
enableAutocomplete: boolean;

/**
* The LaunchDarkly base uri to be used. Optional.
*/
baseUri: string;

/**
* The LaunchDarkly stream uri to be used. Optional.
*/
streamUri: string;
}

class Configuration implements IConfiguration {
constructor() {
this.reload();
}

reload() {
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('launchdarkly');
for (const option in this) {
this[option] = config[option];
}
}

accessToken = '';
sdkKey = '';
project = '';
env = '';
enableHover = true;
enableAutocomplete = true;
baseUri = DEFAULT_BASE_URI;
streamUri = DEFAULT_STREAM_URI;
}

export const configuration = new Configuration();
149 changes: 12 additions & 137 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,150 +1,25 @@
'use strict';

import * as vscode from 'vscode';
import * as url from 'url';
import opn = require('opn');
import { kebabCase } from 'lodash';

import { LDStreamProcessor, LDFlagValue, LDFeatureStore } from 'ldclient-node';
import InMemoryFeatureStore = require('ldclient-node/feature_store');
import StreamProcessor = require('ldclient-node/streaming');
import Requestor = require('ldclient-node/requestor');
import { LDFlagManager } from './flags';
import { configuration as settings } from './configuration';

import * as utils from './utils';
import package_json = require('../package.json');

const DATA_KIND = { namespace: 'features' };
const LD_MODE: vscode.DocumentFilter = {
scheme: 'file',
};

let store: LDFeatureStore;
let updateProcessor: LDStreamProcessor;

class LaunchDarklyCompletionItemProvider implements vscode.CompletionItemProvider {
public provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
): Thenable<vscode.CompletionItem[]> {
if (utils.isPrecedingCharStringDelimeter(document, position)) {
return new Promise(resolve => {
store.all(DATA_KIND, flags => {
resolve(
Object.keys(flags).map(flag => {
return new vscode.CompletionItem(flag, vscode.CompletionItemKind.Field);
}),
);
});
});
}
}
}

class LaunchDarklyHoverProvider implements vscode.HoverProvider {
public provideHover(document: vscode.TextDocument, position: vscode.Position): Thenable<vscode.Hover> {
return new Promise(resolve => {
getFlagKeyAtCurrentPosition(document, position, flag => {
flag ? resolve(new vscode.Hover(utils.generateHoverString(flag))) : resolve();
});
});
}
}
// Handles changes in vscode configuration and registration of commands/providers
let flagManager: LDFlagManager;

export function activate(ctx: vscode.ExtensionContext) {
let settings = vscode.workspace.getConfiguration('launchdarkly');
let sdkKey = settings.get<string>('sdkKey');
let enableHover = settings.get<boolean>('enableHover');
let enableAutocomplete = settings.get<boolean>('enableAutocomplete');
if (sdkKey) {
if (enableHover || enableAutocomplete) {
let baseUri = settings.get<string>('baseUri');
let streamUri = settings.get<string>('streamUri');
store = InMemoryFeatureStore();
let config = {
timeout: 5,
baseUri: baseUri,
streamUri: streamUri,
featureStore: store,
// noop logger for debug calls
logger: { debug: () => {} },
userAgent: 'VSCodeExtension/' + package_json.version,
};

updateProcessor = StreamProcessor(sdkKey, config, Requestor(sdkKey, config));
updateProcessor.start(function(err) {
if (err) {
console.log(err);
let errMsg = `[LaunchDarkly] Unexpected error retrieving flags.${baseUri != 'https://app.launchdarkly.com' ||
streamUri != 'https://stream.launchdarkly.com'
? ' Please make sure your configured base and stream URIs are correct'
: ''}`;
vscode.window.showErrorMessage(errMsg);
} else {
process.nextTick(function() {
if (enableAutocomplete) {
ctx.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
LD_MODE,
new LaunchDarklyCompletionItemProvider(),
"'",
'"',
),
);
}
if (enableHover) {
ctx.subscriptions.push(vscode.languages.registerHoverProvider(LD_MODE, new LaunchDarklyHoverProvider()));
}
});
}
});
vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => {
if (e.affectsConfiguration('launchdarkly')) {
settings.reload();
flagManager.reload(settings);
}
} else {
vscode.window.showWarningMessage('[LaunchDarkly] sdkKey is not set. LaunchDarkly language support is unavailable.');
}

ctx.subscriptions.push(
vscode.commands.registerTextEditorCommand('extension.openInLaunchDarkly', editor => {
let flagKey = editor.document.getText(
editor.document.getWordRangeAtPosition(editor.selection.anchor, utils.FLAG_KEY_REGEX),
);
if (flagKey === '') {
vscode.window.showErrorMessage(
'[LaunchDarkly] Error retrieving flag (current cursor position is not a feature flag).',
);
return;
}

if (!settings.get('accessToken')) {
vscode.window.showErrorMessage('[LaunchDarkly] accessToken is not set.');
return;
}

let project = utils.getProject(settings);
if (!project) {
vscode.window.showErrorMessage('[LaunchDarkly] project is not set.');
return;
}
});

utils.getFeatureFlag(settings, flagKey, (flag: LDFlagValue) => {
let baseUri = settings.get<string>('baseUri');
let env = utils.getEnvironment(settings);
if (env === '') {
opn(url.resolve(baseUri, flag.environments[Object.keys(flag.environments)[0]]._site.href));
} else {
opn(url.resolve(baseUri, flag.environments[env]._site.href));
}
});
}),
);
flagManager = new LDFlagManager(ctx, settings);
flagManager.registerProviders(ctx, settings);
}

export function deactivate() {
updateProcessor.stop();
}

function getFlagKeyAtCurrentPosition(document: vscode.TextDocument, position: vscode.Position, cb: Function) {
store.all(DATA_KIND, flags => {
let candidate = document.getText(document.getWordRangeAtPosition(position, utils.FLAG_KEY_REGEX));
cb(flags[candidate] || flags[kebabCase(candidate)]);
});
flagManager.updateProcessor.stop();
}
Loading

0 comments on commit 50389c7

Please sign in to comment.