Browse Source

Adds basic local usage tracking to aid discovery

main
Eric Amodio 2 years ago
parent
commit
d43ccecf3c
10 changed files with 135 additions and 0 deletions
  1. +10
    -0
      package.json
  2. +11
    -0
      src/commands/resets.ts
  3. +1
    -0
      src/constants.ts
  4. +7
    -0
      src/container.ts
  5. +1
    -0
      src/plus/webviews/graph/graphWebview.ts
  6. +2
    -0
      src/plus/webviews/timeline/timelineWebview.ts
  7. +2
    -0
      src/plus/webviews/timeline/timelineWebviewView.ts
  8. +2
    -0
      src/storage.ts
  9. +22
    -0
      src/system/object.ts
  10. +77
    -0
      src/usageTracker.ts

+ 10
- 0
package.json View File

@ -207,6 +207,7 @@
"onCommand:gitlens.externalDiffAll",
"onCommand:gitlens.resetAvatarCache",
"onCommand:gitlens.resetSuppressedWarnings",
"onCommand:gitlens.resetTrackedUsage",
"onCommand:gitlens.inviteToLiveShare",
"onCommand:gitlens.browseRepoAtRevision",
"onCommand:gitlens.browseRepoAtRevisionInNewWindow",
@ -4720,6 +4721,11 @@
"category": "GitLens"
},
{
"command": "gitlens.resetTrackedUsage",
"title": "Reset Tracked Usage",
"category": "GitLens"
},
{
"command": "gitlens.inviteToLiveShare",
"title": "Invite to Live Share",
"category": "GitLens",
@ -6760,6 +6766,10 @@
"when": "gitlens:enabled"
},
{
"command": "gitlens.resetTrackedUsage",
"when": "gitlens:enabled"
},
{
"command": "gitlens.inviteToLiveShare",
"when": "false"
},

+ 11
- 0
src/commands/resets.ts View File

@ -27,3 +27,14 @@ export class ResetSuppressedWarningsCommand extends Command {
await configuration.update('advanced.messages', undefined, ConfigurationTarget.Global);
}
}
@command()
export class ResetTrackedUsageCommand extends Command {
constructor(private readonly container: Container) {
super(Commands.ResetTrackedUsage);
}
async execute() {
await this.container.usage.reset();
}
}

+ 1
- 0
src/constants.ts View File

@ -155,6 +155,7 @@ export const enum Commands {
RefreshHover = 'gitlens.refreshHover',
ResetAvatarCache = 'gitlens.resetAvatarCache',
ResetSuppressedWarnings = 'gitlens.resetSuppressedWarnings',
ResetTrackedUsage = 'gitlens.resetTrackedUsage',
RevealCommitInView = 'gitlens.revealCommitInView',
SearchCommits = 'gitlens.showCommitSearch',
SearchCommitsInView = 'gitlens.views.searchAndCompare.searchCommits',

+ 7
- 0
src/container.ts View File

@ -32,6 +32,7 @@ import { memoize } from './system/decorators/memoize';
import { GitTerminalLinkProvider } from './terminal/linkProvider';
import { GitDocumentTracker } from './trackers/gitDocumentTracker';
import { GitLineTracker } from './trackers/gitLineTracker';
import { UsageTracker } from './usageTracker';
import { BranchesView } from './views/branchesView';
import { CommitsView } from './views/commitsView';
import { ContributorsView } from './views/contributorsView';
@ -144,6 +145,7 @@ export class Container {
this.ensureModeApplied();
context.subscriptions.push((this._storage = storage));
context.subscriptions.push((this._usage = new UsageTracker(storage)));
context.subscriptions.push(configuration.onWillChange(this.onConfigurationChanging, this));
@ -544,6 +546,11 @@ export class Container {
return this._tracker;
}
private readonly _usage: UsageTracker;
get usage(): UsageTracker {
return this._usage;
}
@memoize()
get version(): string {
return this.context.extension.packageJSON.version as string;

+ 1
- 0
src/plus/webviews/graph/graphWebview.ts View File

@ -62,6 +62,7 @@ export class GraphWebview extends WebviewWithConfigBase {
override async show(column: ViewColumn = ViewColumn.Active, ...args: any[]): Promise<void> {
if (!(await ensurePlusFeaturesEnabled())) return;
void this.container.usage.track('graphWebview:shown');
return super.show(column, ...args);
}

+ 2
- 0
src/plus/webviews/timeline/timelineWebview.ts View File

@ -64,6 +64,8 @@ export class TimelineWebview extends WebviewBase {
override async show(column: ViewColumn = ViewColumn.Beside, ...args: any[]): Promise<void> {
if (!(await ensurePlusFeaturesEnabled())) return;
void this.container.usage.track('timelineWebview:shown');
return super.show(column, ...args);
}

+ 2
- 0
src/plus/webviews/timeline/timelineWebviewView.ts View File

@ -57,6 +57,8 @@ export class TimelineWebviewView extends WebviewViewBase {
override async show(options?: { preserveFocus?: boolean | undefined }): Promise<void> {
if (!(await ensurePlusFeaturesEnabled())) return;
void this.container.usage.track('timelineView:shown');
return super.show(options);
}

+ 2
- 0
src/storage.ts View File

@ -3,6 +3,7 @@ import { EventEmitter } from 'vscode';
import type { GraphColumnConfig, ViewShowBranchComparison } from './config';
import type { SearchPattern } from './git/search';
import type { Subscription } from './subscription';
import type { TrackedUsage, TrackedUsageKeys } from './usageTracker';
import type { CompletedActions } from './webviews/home/protocol';
export type StorageChangeEvent =
@ -131,6 +132,7 @@ export interface GlobalStorage {
synced: {
version?: string;
};
usages?: Record<TrackedUsageKeys, TrackedUsage>;
version?: string;
views: {
welcome: {

+ 22
- 0
src/system/object.ts View File

@ -54,3 +54,25 @@ export function paths(o: Record, path?: string): string[] {
return results;
}
export function updateRecordValue<T>(
obj: Record<string, T> | undefined,
key: string,
value: T | undefined,
): Record<string, T> {
if (obj == null) {
obj = Object.create(null) as Record<string, T>;
}
if (value != null && (typeof value !== 'boolean' || value)) {
if (typeof value === 'object') {
obj[key] = { ...value };
} else {
obj[key] = value;
}
} else {
const { [key]: _, ...rest } = obj;
obj = rest;
}
return obj;
}

+ 77
- 0
src/usageTracker.ts View File

@ -0,0 +1,77 @@
import type { Disposable, Event } from 'vscode';
import { EventEmitter } from 'vscode';
import type { Storage } from './storage';
import { updateRecordValue } from './system/object';
export interface TrackedUsage {
count: number;
firstUsedAt: number;
lastUsedAt: number;
}
export type TrackedUsageKeys = 'graphWebview:shown' | 'timelineWebview:shown' | 'timelineView:shown';
export type UsageChangeEvent = {
/**
* The key of the action/event/feature who's usage was tracked
*/
readonly key: TrackedUsageKeys;
readonly usage?: TrackedUsage;
};
export class UsageTracker implements Disposable {
private _onDidChange = new EventEmitter<UsageChangeEvent | undefined>();
get onDidChange(): Event<UsageChangeEvent | undefined> {
return this._onDidChange.event;
}
constructor(private readonly storage: Storage) {}
dispose(): void {}
get(key: TrackedUsageKeys): TrackedUsage | undefined {
return this.storage.get('usages')?.[key];
}
async reset(key?: TrackedUsageKeys): Promise<void> {
let usages = this.storage.get('usages');
if (usages == null) return;
if (key == null) {
await this.storage.delete('usages');
this._onDidChange.fire(undefined);
return;
}
usages = updateRecordValue(usages, key, undefined);
await this.storage.store('usages', usages);
this._onDidChange.fire({ key: key, usage: undefined });
}
async track(key: TrackedUsageKeys): Promise<void> {
let usages = this.storage.get('usages');
if (usages == null) {
usages = Object.create(null) as NonNullable<typeof usages>;
}
const usedAt = Date.now();
let usage = usages[key];
if (usage == null) {
usage = {
count: 0,
firstUsedAt: usedAt,
lastUsedAt: usedAt,
};
usages[key] = usage;
} else {
usage.count++;
usage.lastUsedAt = usedAt;
}
await this.storage.store('usages', usages);
this._onDidChange.fire({ key: key, usage: usage });
}
}

Loading…
Cancel
Save