190 lines
4.8 KiB

import type { Disposable } from 'vscode';
import type { Container } from '../../container';
import type { GitRemote } from '../../git/models/remote';
import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider';
import { log } from '../../system/decorators/log';
import { Logger } from '../../system/logger';
import { getLogScope } from '../../system/logger.scope';
import type { ServerConnection } from '../gk/serverConnection';
import { ensureAccount, ensurePaidPlan } from '../utils';
export interface FocusItem {
type: EnrichedItemResponse['entityType'];
id: string;
remote: GitRemote<RichRemoteProvider>;
url: string;
}
export type EnrichedItem = {
id: string;
userId?: string;
type: EnrichedItemResponse['type'];
provider: EnrichedItemResponse['provider'];
entityType: EnrichedItemResponse['entityType'];
entityId: string;
entityUrl: string;
createdAt: number;
updatedAt: number;
};
type EnrichedItemRequest = {
provider: EnrichedItemResponse['provider'];
entityType: EnrichedItemResponse['entityType'];
entityId: string;
entityUrl: string;
};
type EnrichedItemResponse = {
id: string;
userId?: string;
type: 'pin' | 'snooze';
provider: 'azure' | 'bitbucket' | 'github' | 'gitlab' | 'gitkraken';
entityType: 'issue' | 'pr';
entityId: string;
entityUrl: string;
createdAt: number;
updatedAt: number;
};
export class FocusService implements Disposable {
constructor(
private readonly container: Container,
private readonly connection: ServerConnection,
) {}
dispose(): void {}
private async delete(id: string, context: 'unpin' | 'unsnooze'): Promise<void> {
const scope = getLogScope();
try {
const rsp = await this.connection.fetchGkDevApi(`v1/enrich-items/${id}`, { method: 'DELETE' });
if (!rsp.ok) throw new Error(`Unable to ${context} item '${id}': (${rsp.status}) ${rsp.statusText}`);
} catch (ex) {
Logger.error(ex, scope);
debugger;
throw ex;
}
}
@log()
async get(type?: EnrichedItemResponse['type']): Promise<EnrichedItem[]> {
const scope = getLogScope();
try {
type Result = { data: EnrichedItemResponse[] };
const rsp = await this.connection.fetchGkDevApi('v1/enrich-items', { method: 'GET' });
const result = (await rsp.json()) as Result;
return type == null ? result.data : result.data.filter(i => i.type === type);
} catch (ex) {
if (ex instanceof Error && ex.message === 'Authentication required') return [];
Logger.error(ex, scope);
debugger;
throw ex;
}
}
@log()
getPins(): Promise<EnrichedItem[]> {
return this.get('pin');
}
@log()
getSnoozed(): Promise<EnrichedItem[]> {
return this.get('snooze');
}
@log<FocusService['pinItem']>({ args: { 0: i => `${i.id} (${i.remote.provider.name} ${i.type})` } })
async pinItem(item: FocusItem): Promise<EnrichedItem> {
const scope = getLogScope();
try {
if (!(await ensureAccount('Pinning requires a GitKraken account.', this.container))) {
throw new Error('Unable to pin item: account required');
}
type Result = { data: EnrichedItemResponse };
const rq: EnrichedItemRequest = {
provider: item.remote.provider.id as EnrichedItemResponse['provider'],
entityType: item.type,
entityId: item.id,
entityUrl: item.url,
};
const rsp = await this.connection.fetchGkDevApi('v1/enrich-items/pin', {
method: 'POST',
body: JSON.stringify(rq),
});
if (!rsp.ok) {
throw new Error(
`Unable to pin item '${rq.provider}|${rq.entityUrl}#${item.id}': (${rsp.status}) ${rsp.statusText}`,
);
}
const result = (await rsp.json()) as Result;
return result.data;
} catch (ex) {
Logger.error(ex, scope);
debugger;
throw ex;
}
}
@log()
unpinItem(id: string): Promise<void> {
return this.delete(id, 'unpin');
}
@log<FocusService['snoozeItem']>({ args: { 0: i => `${i.id} (${i.remote.provider.name} ${i.type})` } })
async snoozeItem(item: FocusItem): Promise<EnrichedItem> {
const scope = getLogScope();
try {
if (!(await ensurePaidPlan('Snoozing requires a trial or paid plan', this.container))) {
throw new Error('Unable to snooze item: subscription required');
}
type Result = { data: EnrichedItemResponse };
const rq: EnrichedItemRequest = {
provider: item.remote.provider.id as EnrichedItemResponse['provider'],
entityType: item.type,
entityId: item.id,
entityUrl: item.url,
};
const rsp = await this.connection.fetchGkDevApi('v1/enrich-items/snooze', {
method: 'POST',
body: JSON.stringify(rq),
});
if (!rsp.ok) {
throw new Error(
`Unable to snooze item '${rq.provider}|${rq.entityUrl}#${item.id}': (${rsp.status}) ${rsp.statusText}`,
);
}
const result = (await rsp.json()) as Result;
return result.data;
} catch (ex) {
Logger.error(ex, scope);
debugger;
throw ex;
}
}
@log()
unsnoozeItem(id: string): Promise<void> {
return this.delete(id, 'unsnooze');
}
}