Rework code base; #17

This commit is contained in:
IJustDev 2020-08-16 20:26:14 +02:00
parent cb4a963dfc
commit 858b8b7cda
10 changed files with 236 additions and 292 deletions

View File

@ -69,9 +69,8 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
- Emojis in treeview
## [Unreleased]
- Initial release
- Rework code base
[1]: https://github.com/IJustDev/Gitea-VSCode/issues/1
[2]: https://github.com/IJustDev/Gitea-VSCode/issues/2
[2]: https://github.com/IJustDev/Gitea-VSCode/issues/2

View File

@ -3,7 +3,7 @@
"displayName": "Gitea-VSCode",
"description": "Gitea Issue Tracker for vs-code",
"publisher": "IJustDev",
"version": "1.0.1",
"version": "1.1.0",
"engines": {
"vscode": "^1.32.0"
},
@ -119,8 +119,7 @@
"vscode": "^1.1.28"
},
"dependencies": {
"axios": "^0.18.1",
"marked": "^0.6.2"
"axios": "^0.18.1"
},
"repository": {
"type": "github",

View File

@ -1,80 +0,0 @@
import { workspace, window } from 'vscode';
interface ConfigStorage {
token: string;
instanceURL: string;
owner: string;
repo: string;
sslVerify: boolean;
}
export interface ConfigTypes extends ConfigStorage {
readonly repoApiUrl: string;
}
export class Config implements ConfigTypes {
private get storage() {
return workspace.getConfiguration('gitea', null);
}
private loadConfigValue<T extends keyof ConfigStorage>(configKey: T, type: 'string' | 'boolean' | 'number', acceptDetault = false): ConfigStorage[T] {
if (!acceptDetault && !this.storage.has(configKey)) {
window.showErrorMessage("Gitea-VSCode didn't find a required configuration value: " + configKey);
throw new Error(`Failed to load configuration: "${configKey}"`);
}
const value = this.storage.has(configKey)
? (this.storage.get(configKey) as ConfigStorage[T])
: (this.storage.inspect(configKey) as { defaultValue: ConfigStorage[T]; key: string }).defaultValue;
if (typeof value === type && (type !== 'string' || (value as string).length > 0)) {
return value as ConfigStorage[T];
}
window.showErrorMessage('Gitea-VSCode failed to load a configuration value that is needed: ' + configKey);
throw new Error(`Failed to load configuration: "gitea.${configKey}"`);
}
public get token() {
return this.loadConfigValue('token', 'string');
}
public set token(value) {
this.storage.update('token', value);
}
public set instanceUrl(value: string) {
this.storage.update('instanceURL', value);
}
public get instanceURL(): any {
return this.loadConfigValue('instanceURL', 'string');
}
public get owner() {
return this.loadConfigValue('owner', 'string');
}
public set owner(value) {
this.storage.update('owner', value);
}
public get repo() {
return this.loadConfigValue('repo', 'string');
}
public set repo(value) {
this.storage.update('repo', value);
}
public get repoApiUrl() {
return this.instanceURL + '/api/v1/repos/' + this.owner + '/' + this.repo + '/issues';
}
public set sslVerify(value){
this.storage.update('sslVerify', value);
}
public get sslVerify(){
return this.loadConfigValue('sslVerify', 'boolean')
}
}

3
src/IGiteaResponse.ts Normal file
View File

@ -0,0 +1,3 @@
export interface IGiteaResponse {
data: any[];
}

81
src/config.ts Normal file
View File

@ -0,0 +1,81 @@
import { workspace, window } from 'vscode';
interface ConfigStorage {
token: string;
instanceURL: string;
owner: string;
repo: string;
sslVerify: boolean;
}
export interface ConfigTypes extends ConfigStorage {
readonly repoApiUrl: string;
}
export class Config implements ConfigTypes {
private get storage() {
return workspace.getConfiguration('gitea', null);
}
private loadConfigValue<T extends keyof ConfigStorage>(configKey: T, type: 'string' | 'boolean' | 'number', acceptDetault = false): ConfigStorage[T] {
if (!acceptDetault && !this.storage.has(configKey)) {
window.showErrorMessage("Gitea-VSCode didn't find a required configuration value: " + configKey);
throw new Error(`Failed to load configuration: "${configKey}"`);
}
const value = this.storage.has(configKey)
? (this.storage.get(configKey) as ConfigStorage[T])
: (this.storage.inspect(configKey) as { defaultValue: ConfigStorage[T]; key: string }).defaultValue;
if (typeof value === type && (type !== 'string' || (value as string).length > 0)) {
return value as ConfigStorage[T];
}
window.showErrorMessage('Gitea-VSCode failed to load a configuration value that is needed: ' + configKey);
throw new Error(`Failed to load configuration: "gitea.${configKey}"`);
}
public get token() {
return this.loadConfigValue('token', 'string');
}
public set token(value) {
this.storage.update('token', value);
}
public set instanceUrl(value: string) {
this.storage.update('instanceURL', value);
}
public get instanceURL(): any {
return this.loadConfigValue('instanceURL', 'string');
}
public get owner() {
return this.loadConfigValue('owner', 'string');
}
public set owner(value) {
this.storage.update('owner', value);
}
public get repo() {
return this.loadConfigValue('repo', 'string');
}
public set repo(value) {
this.storage.update('repo', value);
}
public get repoApiUrl() {
return this.instanceURL.replace(/\/$/, "") + '/api/v1/repos/' + this.owner + '/' + this.repo + '/issues';
}
public set sslVerify(value) {
this.storage.update('sslVerify', value);
}
public get sslVerify() {
return this.loadConfigValue('sslVerify', 'boolean')
}
}

View File

@ -2,46 +2,40 @@ import * as vscode from 'vscode';
import { showIssueHTML } from './template.html';
import { Issue } from './issue';
import { OpenIssuesProvider, ClosedIssuesProvider } from './issueProvider';
import { IssueProvider } from './issueProvider';
export function activate(context: vscode.ExtensionContext) {
let openIssues: Array<Issue> = [];
const openIssuesProvider = new OpenIssuesProvider();
const closedIssuesProvider = new ClosedIssuesProvider();
vscode.window.registerTreeDataProvider('open-issues', openIssuesProvider);
vscode.window.registerTreeDataProvider('closed-issues', closedIssuesProvider);
// TODO: Implement in next version.
// vscode.commands.registerCommand('giteaIssues.createIssue', async () => {
// const panel = vscode.window.createWebviewPanel('createIssue', 'Create an new Issue', vscode.ViewColumn.Active, {});
// panel.webview.html = "";
// });
vscode.commands.registerCommand('giteaIssues.openIssue', (issue: Issue) => {
for (let i = 0; i !== openIssues.length; i++) {
let openIssue = openIssues[i];
if (openIssue.issueId === issue.issueId) {
return;
}
}
export function showIssueInWebPanel(issue: Issue) {
const panel = vscode.window.createWebviewPanel('issue', issue.label, vscode.ViewColumn.Active, {});
panel.webview.html = showIssueHTML(issue);
openIssues.push(issue);
panel.onDidDispose((event) => {
for (let i = 0; i !== openIssues.length; i++) {
let openIssue = openIssues[i];
if (openIssue.issueId === issue.issueId) {
openIssues.splice(openIssues.indexOf(issue), 1);
}
}
});
});
return panel;
}
vscode.commands.registerCommand('giteaIssues.refreshIssues', () => {
openIssuesProvider.refresh();
closedIssuesProvider.refresh();
});
export function activate(context: vscode.ExtensionContext) {
// Array of issues; This is used to determine whether a issue is already open
// in a tab or not.
let openIssues: Array<Issue> = [];
const openIssuesProvider = new IssueProvider("open");
const closedIssuesProvider = new IssueProvider("closed");
vscode.window.registerTreeDataProvider('open-issues', openIssuesProvider);
vscode.window.registerTreeDataProvider('closed-issues', closedIssuesProvider);
vscode.commands.registerCommand('giteaIssues.openIssue', (issue: Issue) => {
const issueOpenable = openIssues.find((c) => c.issueId === issue.issueId) === undefined;
if (issueOpenable) {
const panel = showIssueInWebPanel(issue);
openIssues.push(issue);
panel.onDidDispose((event) => {
openIssues.splice(openIssues.indexOf(issue), 1);
});
}
});
vscode.commands.registerCommand('giteaIssues.refreshIssues', () => {
openIssuesProvider.refresh();
closedIssuesProvider.refresh();
});
}
export function deactivate() {}

50
src/giteaConnector.ts Normal file
View File

@ -0,0 +1,50 @@
import * as vscode from 'vscode';
import * as https from 'https';
import axios from 'axios';
import { IGiteaResponse } from './IGiteaResponse';
export class GiteaConnector {
private authToken: string;
private ssl: boolean;
public constructor(authToken: string, ssl: boolean = false) {
this.authToken = authToken;
this.ssl = ssl;
}
public async getIssues(repoUri: string, state: string, page: number = 0): Promise<IGiteaResponse> {
return this.getEndpoint(`${repoUri}?state=${state}&page=${page}`);
}
private async getEndpoint(url: string): Promise<IGiteaResponse> {
return new Promise<IGiteaResponse>((resolve, reject) => {
return axios.get(url, this.requestOptions).then((data) => {
resolve(data);
}).catch((err) => {
this.displayErrorMessage(err);
reject(err);
});
});
}
private async postEndpoint(url: string): Promise<IGiteaResponse> {
return new Promise<IGiteaResponse>((resolve, reject) => {
return axios.post(url, this.requestOptions);
});
}
private get requestOptions(): object {
const agent = new https.Agent({
rejectUnauthorized: this.ssl,
});
return {
headers: {Authorization: 'token ' + this.authToken},
httpsAgent: agent,
};
}
private displayErrorMessage(err: string) {
vscode.window.showErrorMessage("Error occoured. " + err);
}
}

View File

@ -12,12 +12,12 @@ export class Issue extends TreeItem {
public readonly label: string,
public issueId: number,
public body: string,
public issueState: string,
public state: string,
public assignee: string,
public creator: string,
public labels: Label[],
public collapsibleState: TreeItemCollapsibleState,
public readonly command?: Command
public command?: Command
) {
super(label, collapsibleState);
}

View File

@ -1,181 +1,79 @@
import axios from 'axios';
import * as vscode from 'vscode';
import * as https from 'https';
const marked = require('marked');
import { Issue } from './issue';
import { Config } from './Config';
import { Config } from './config';
import { GiteaConnector } from './giteaConnector';
export class OpenIssuesProvider implements vscode.TreeDataProvider<Issue> {
private _onDidChangeTreeData: vscode.EventEmitter<Issue | undefined> = new vscode.EventEmitter<Issue | undefined>();
readonly onDidChangeTreeData: vscode.Event<Issue | undefined> = this._onDidChangeTreeData.event;
export class IssueProvider implements vscode.TreeDataProvider<Issue> {
private _onDidChangeTreeData: vscode.EventEmitter<Issue | undefined> = new vscode.EventEmitter<Issue | undefined>();
issueList: Issue[] = [];
readonly onDidChangeTreeData: vscode.Event<Issue | undefined> = this._onDidChangeTreeData.event;
async refresh() {
await this.getChildrenAsync();
this._onDidChangeTreeData.fire();
}
private state: string;
private issueList: Issue[] = [];
constructor() {
// Auto update the issuelist after 10 minutes
setInterval(() => {
this.refresh();
}, 10 * 60 * 1000);
}
constructor(state: string) {
this.state = state;
}
getTreeItem(element: Issue): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element;
}
public getTreeItem(element: Issue): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element;
}
/**
* Returns a list of all open issues;
*/
async getChildrenAsync() {
this.issueList = [];
const config = new Config();
const repoUri = config.repoApiUrl;
const token = config.token;
let stop = false;
public async getIssuesAsync() {
this.issueList = [];
const config = new Config();
const giteaConnector = new GiteaConnector(config.token, config.sslVerify);
const agent = new https.Agent({
// if true, stop when can't verify ssl
rejectUnauthorized: config.sslVerify,
});
for (let i = 0; i !== 10; i++) {
await axios
.get(repoUri + '?page=' + i, { headers: { Authorization: 'token ' + token }, httpsAgent: agent })
.then((res) => {
if (res.data.length === 0) {
stop = true;
return;
}
parseToIssues(res, this.issueList);
})
.catch((err) => {
stop = true;
vscode.window.showErrorMessage("An error occoured. Please check your repository url: " + repoUri);
return;
const issues = [];
let page = 0;
while (page < 11) {
const issuesOfPage = (await giteaConnector.getIssues(config.repoApiUrl, this.state, page)).data;
issues.push(...issuesOfPage);
issuesOfPage.forEach((c) => {
c.label = c.title;
c.issueId = c.number;
c.assignee = c.assignee === null ? 'Nobody' : c.assignee;
c.creator = c.user.login;
});
page++;
if (issues.length < 10) {
break;
}
}
this.issueList = issues as Issue[];
this.issueList.forEach((issue: Issue) => {
issue.command = {
command: 'giteaIssues.openIssue',
title: '',
arguments: [issue],
};
issue.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
});
if (stop) {
return;
}
}
}
getChildren(element?: Issue): vscode.ProviderResult<any[]> {
return getChildren(element, this.issueList);
}
public async refresh() {
await this.getIssuesAsync();
this._onDidChangeTreeData.fire();
}
public getChildren(element?: Issue): vscode.ProviderResult<any[]> {
return this.createChildNodes(element, this.issueList);
}
private createChildNodes(element: Issue | undefined, issues: Issue[]) {
for (const issue of issues) {
if (element === issue) {
let childItems: vscode.TreeItem[] = [
new vscode.TreeItem('Assignee - ' + element.assignee, vscode.TreeItemCollapsibleState.None),
new vscode.TreeItem('State - ' + element.state, vscode.TreeItemCollapsibleState.None),
new vscode.TreeItem('ID - ' + element.issueId, vscode.TreeItemCollapsibleState.None),
new vscode.TreeItem('From - ' + element.creator, vscode.TreeItemCollapsibleState.None),
];
return Promise.resolve(childItems);
}
}
return issues;
}
}
export class ClosedIssuesProvider implements vscode.TreeDataProvider<Issue> {
private _onDidChangeTreeData: vscode.EventEmitter<Issue | undefined> = new vscode.EventEmitter<Issue | undefined>();
readonly onDidChangeTreeData: vscode.Event<Issue | undefined> = this._onDidChangeTreeData.event;
issueList: Issue[] = [];
async refresh() {
await this.getChildrenAsync();
this._onDidChangeTreeData.fire();
}
constructor() {
setInterval(() => {
this.refresh();
}, 10 * 60 * 1000);
}
getTreeItem(element: Issue): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element;
}
async getChildrenAsync() {
this.issueList = [];
const config = new Config();
const repoUri = config.repoApiUrl;
const token = config.token;
let stop = false;
const agent = new https.Agent({
// if true, stop when can't verify ssl
rejectUnauthorized: config.sslVerify,
});
for (let i = 0; i !== 10; i++) {
await axios
.get(repoUri + '?state=closed&page=' + i, { headers: { Authorization: 'token ' + token } , httpsAgent: agent} )
.then((res) => {
console.log(res.data);
if (res.data.length === 0) {
stop = true;
return;
}
parseToIssues(res, this.issueList);
})
.catch(() => {
stop = true;
vscode.window.showErrorMessage("An error occoured. Please check your repository url: " + repoUri);
return;
});
if (stop) {
return;
}
}
}
getChildren(element?: Issue): vscode.ProviderResult<any[]> {
return getChildren(element, this.issueList);
}
}
export function getChildren(element: Issue | undefined, issueList: Issue[]) {
for (const issue of issueList) {
if (element === issue) {
let childItems: vscode.TreeItem[] = [
new vscode.TreeItem('👷 Assignee - ' + element.assignee, vscode.TreeItemCollapsibleState.None),
new vscode.TreeItem('🚥 State - ' + element.issueState, vscode.TreeItemCollapsibleState.None),
new vscode.TreeItem('🆔 ID - ' + element.issueId, vscode.TreeItemCollapsibleState.None),
new vscode.TreeItem('✏️ From - ' + element.creator, vscode.TreeItemCollapsibleState.None),
];
return Promise.resolve(childItems);
}
}
return issueList;
}
export function parseToIssues(res: any, issueList: Issue[], collapsibleState: vscode.TreeItemCollapsibleState = vscode.TreeItemCollapsibleState.Collapsed) {
for (const issue of res.data) {
const id = issue['number'];
let isAlreadyInList = false;
issueList.forEach((issueOfList) => {
if (id === issueOfList.issueId) {
isAlreadyInList = true;
}
});
if (isAlreadyInList) {
continue;
}
const title = issue['title'];
const body = marked(issue['body']);
const state = issue['state'];
const assignee = issue['assignee'] === null ? 'None' : issue['assignee']['username'];
const labels = issue['labels'];
const creator = issue['user']['username'];
const tmpIssue = new Issue('#' + id + ' - ' + title, id, body, state, assignee, creator, labels, collapsibleState);
const issueForList = new Issue(
tmpIssue.label,
tmpIssue.issueId,
tmpIssue.body,
tmpIssue.issueState,
tmpIssue.assignee,
tmpIssue.creator,
tmpIssue.labels,
tmpIssue.collapsibleState,
{
command: 'giteaIssues.openIssue',
title: '',
arguments: [tmpIssue],
}
);
issueList.push(issueForList);
}
}

View File

@ -35,7 +35,7 @@ export function showIssueHTML(issue: Issue) {
</body>
`
.replace('{{label}}', issue.label)
.replace('{{state}}', issue.issueState)
.replace('{{state}}', issue.state)
.replace('{{assignee}}', issue.assignee)
.replace('{{description}}', issue.body)
.replace('{{label}}', issue.label);