mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 05:05:53 +00:00
Partial implementation of github bridging
This commit is contained in:
parent
6aaf7db831
commit
0c65acedd3
103
src/api/dimension/DimensionHookshotGithubService.ts
Normal file
103
src/api/dimension/DimensionHookshotGithubService.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { Context, DELETE, GET, Path, PathParam, POST, Security, ServiceContext } from "typescript-rest";
|
||||
import { ApiError } from "../ApiError";
|
||||
import { LogService } from "matrix-bot-sdk";
|
||||
import { BridgedChannel, SlackBridge } from "../../bridges/SlackBridge";
|
||||
import { SlackChannel, SlackTeam } from "../../bridges/models/slack";
|
||||
import { ROLE_USER } from "../security/MatrixSecurity";
|
||||
import {
|
||||
HookshotConnection, HookshotGithubOrg, HookshotGithubRepo,
|
||||
HookshotJiraInstance,
|
||||
HookshotJiraProject,
|
||||
HookshotJiraRoomConfig
|
||||
} from "../../bridges/models/hookshot";
|
||||
import { HookshotGithubBridge } from "../../bridges/HookshotGithubBridge";
|
||||
|
||||
interface BridgeRoomRequest {
|
||||
orgId: string;
|
||||
repoId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* API for interacting with the Hookshot/Github bridge
|
||||
*/
|
||||
@Path("/api/v1/dimension/hookshot/github")
|
||||
export class DimensionHookshotGithubService {
|
||||
|
||||
@Context
|
||||
private context: ServiceContext;
|
||||
|
||||
@GET
|
||||
@Path("auth")
|
||||
@Security(ROLE_USER)
|
||||
public async getAuthUrl(): Promise<{ authUrl: string }> {
|
||||
const userId = this.context.request.user.userId;
|
||||
|
||||
try {
|
||||
const hookshot = new HookshotGithubBridge(userId);
|
||||
const authUrl = await hookshot.getAuthUrl();
|
||||
return {authUrl};
|
||||
} catch (e) {
|
||||
LogService.error("DimensionHookshotGithubService", e);
|
||||
throw new ApiError(400, "Error getting auth info");
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("orgs")
|
||||
@Security(ROLE_USER)
|
||||
public async getOrgs(): Promise<{ orgs: HookshotGithubOrg[] }> {
|
||||
const userId = this.context.request.user.userId;
|
||||
|
||||
const hookshot = new HookshotGithubBridge(userId);
|
||||
const userInfo = await hookshot.getLoggedInUserInfo();
|
||||
if (!userInfo.loggedIn) {
|
||||
throw new ApiError(403, "Not logged in", "T2B_NOT_LOGGED_IN");
|
||||
}
|
||||
return {orgs: userInfo.organisations};
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("org/:orgId/repos")
|
||||
@Security(ROLE_USER)
|
||||
public async getRepos(@PathParam("orgId") orgId: string): Promise<{ repos: HookshotGithubRepo[] }> {
|
||||
const userId = this.context.request.user.userId;
|
||||
|
||||
const hookshot = new HookshotGithubBridge(userId);
|
||||
const repos = await hookshot.getRepos(orgId);
|
||||
return {repos};
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("room/:roomId/connect")
|
||||
@Security(ROLE_USER)
|
||||
public async bridgeRoom(@PathParam("roomId") roomId: string, request: BridgeRoomRequest): Promise<HookshotJiraRoomConfig> {
|
||||
const userId = this.context.request.user.userId;
|
||||
|
||||
try {
|
||||
const hookshot = new HookshotGithubBridge(userId);
|
||||
return hookshot.bridgeRoom(roomId, request.orgId, request.repoId);
|
||||
} catch (e) {
|
||||
LogService.error("DimensionHookshotGithubService", e);
|
||||
throw new ApiError(400, "Error bridging room");
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("room/:roomId/connections/all")
|
||||
@Security(ROLE_USER)
|
||||
public async unbridgeRoom(@PathParam("roomId") roomId: string): Promise<any> {
|
||||
const userId = this.context.request.user.userId;
|
||||
|
||||
try {
|
||||
const hookshot = new HookshotGithubBridge(userId);
|
||||
const connections = await hookshot.getRoomConfigurations(roomId);
|
||||
for (const conn of connections) {
|
||||
await hookshot.unbridgeRoom(roomId, conn.id);
|
||||
}
|
||||
return {}; // 200 OK
|
||||
} catch (e) {
|
||||
LogService.error("DimensionHookshotGithubService", e);
|
||||
throw new ApiError(400, "Error unbridging room");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
import HookshotGithubBridgeRecord from "../db/models/HookshotGithubBridgeRecord";
|
||||
import { HookshotConnection, HookshotGithubRoomConfig, HookshotTypes } from "./models/hookshot";
|
||||
import {
|
||||
HookshotConnection, HookshotGithubRepo,
|
||||
HookshotGithubRoomConfig,
|
||||
HookshotGithubUserInfo,
|
||||
HookshotJiraUserInfo,
|
||||
HookshotTypes
|
||||
} from "./models/hookshot";
|
||||
import { HookshotBridge } from "./HookshotBridge";
|
||||
|
||||
export class HookshotGithubBridge extends HookshotBridge {
|
||||
@ -16,6 +22,11 @@ export class HookshotGithubBridge extends HookshotBridge {
|
||||
return bridges[0];
|
||||
}
|
||||
|
||||
public async getAuthUrl(): Promise<string> {
|
||||
const bridge = await this.getDefaultBridge();
|
||||
return this.doProvisionRequest(bridge, "GET", `/v1/github/oauth`).then(r => r['url']);
|
||||
}
|
||||
|
||||
public async getBotUserId(): Promise<string> {
|
||||
const confs = await this.getAllServiceInformation();
|
||||
const conf = confs.find(c => c.eventType === HookshotTypes.Github);
|
||||
@ -27,14 +38,40 @@ export class HookshotGithubBridge extends HookshotBridge {
|
||||
return !!bridges && bridges.length > 0 && !!(await this.getBotUserId());
|
||||
}
|
||||
|
||||
public async getLoggedInUserInfo(): Promise<HookshotGithubUserInfo> {
|
||||
const bridge = await this.getDefaultBridge();
|
||||
return this.doProvisionRequest<HookshotGithubUserInfo>(bridge, "GET", `/v1/github/account`);
|
||||
}
|
||||
|
||||
public async getRepos(orgId: string): Promise<HookshotGithubRepo[]> {
|
||||
const bridge = await this.getDefaultBridge();
|
||||
const results: HookshotGithubRepo[] = [];
|
||||
let more = true;
|
||||
let page = 1;
|
||||
let perPage = 10;
|
||||
do {
|
||||
const res = await this.doProvisionRequest<HookshotGithubRepo[]>(bridge, "GET", `/v1/github/orgs/${orgId}/repositories`, {
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
results.push(...res);
|
||||
if (res.length < perPage) more = false;
|
||||
} while(more);
|
||||
return results;
|
||||
}
|
||||
|
||||
public async getRoomConfigurations(inRoomId: string): Promise<HookshotGithubRoomConfig[]> {
|
||||
return (await this.getAllRoomConfigurations(inRoomId)).filter(c => c.eventType === HookshotTypes.Github);
|
||||
}
|
||||
|
||||
public async bridgeRoom(roomId: string): Promise<HookshotGithubRoomConfig> {
|
||||
public async bridgeRoom(roomId: string, orgId: string, repoId: string): Promise<HookshotGithubRoomConfig> {
|
||||
const bridge = await this.getDefaultBridge();
|
||||
|
||||
const body = {};
|
||||
const body = {
|
||||
commandPrefix: "!github",
|
||||
org: orgId,
|
||||
repo: repoId,
|
||||
};
|
||||
return await this.doProvisionRequest<HookshotGithubRoomConfig>(bridge, "PUT", `/v1/${roomId}/connections/${HookshotTypes.Github}`, null, body);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
export enum HookshotTypes {
|
||||
Github = "uk.half-shot.matrix-hookshot.github.repository",
|
||||
Jira = "uk.half-shot.matrix-hookshot.jira.project",
|
||||
}
|
||||
|
||||
export interface HookshotConnection {
|
||||
type: string;
|
||||
eventType: string; // state key in the connection
|
||||
@ -16,12 +21,46 @@ export interface HookshotConnectionTypeDefinition {
|
||||
botUserId: string;
|
||||
}
|
||||
|
||||
export interface HookshotGithubRoomConfig {
|
||||
|
||||
export interface HookshotGithubRoomConfig extends HookshotConnection {
|
||||
config: {
|
||||
org: string;
|
||||
repo: string;
|
||||
ignoreHooks: SupportedGithubRepoEventType[];
|
||||
commandPrefix: string;
|
||||
};
|
||||
}
|
||||
|
||||
export enum SupportedJiraEventType {
|
||||
export interface HookshotGithubOrg {
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface HookshotGithubRepo {
|
||||
name: string;
|
||||
owner: string;
|
||||
fullName: string;
|
||||
avatarUrl: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface HookshotGithubUserInfo {
|
||||
loggedIn: boolean;
|
||||
organisations?: HookshotGithubOrg[];
|
||||
}
|
||||
|
||||
export enum SupportedGithubRepoEventType {
|
||||
IssueCreated = "issue.created",
|
||||
IssueChanged = "issue.changed",
|
||||
IssueEdited = "issue.edited",
|
||||
Issue = "issue",
|
||||
PROpened = "pull_request.opened",
|
||||
PRClosed = "pull_request.closed",
|
||||
PRMerged = "pull_request.merged",
|
||||
PRReadyForReview = "pull_request.ready_for_review",
|
||||
PRReviewed = "pull_request.reviewed",
|
||||
PR = "pull_request",
|
||||
ReleaseCreated = "release.created",
|
||||
Release = "release",
|
||||
}
|
||||
|
||||
export interface HookshotJiraRoomConfig extends HookshotConnection {
|
||||
@ -32,9 +71,8 @@ export interface HookshotJiraRoomConfig extends HookshotConnection {
|
||||
};
|
||||
}
|
||||
|
||||
export enum HookshotTypes {
|
||||
Github = "uk.half-shot.matrix-hookshot.github.repository",
|
||||
Jira = "uk.half-shot.matrix-hookshot.jira.project",
|
||||
export enum SupportedJiraEventType {
|
||||
IssueCreated = "issue.created",
|
||||
}
|
||||
|
||||
export interface HookshotJiraUserInfo {
|
||||
|
@ -8,15 +8,21 @@
|
||||
<my-spinner></my-spinner>
|
||||
</div>
|
||||
<div class="my-ibox-content" *ngIf="!loadingConnections">
|
||||
<div *ngIf="!isBridged && needsAuth">
|
||||
<div *ngIf="isBridged">
|
||||
<p>{{'This room is bridged to' | translate}} {{bridgedRepoSlug}}</p>
|
||||
<button type="button" class="btn btn-sm btn-danger" [disabled]="isBusy" (click)="unbridgeRoom()">
|
||||
{{'Unbridge' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!isBridged && authUrl">
|
||||
<p>
|
||||
{{'In order to bridge to Github, you\'ll need to authorize the bridge to access your organization(s). Please click the button below to do so.' | translate}}
|
||||
</p>
|
||||
<a [href]="authUrl" rel="noopener" target="_blank">
|
||||
<img src="/assets/img/slack_auth_button.png" alt="sign in with slack"/>
|
||||
<a [href]="authUrl" rel="noopener" target="_blank" class="btn btn-lg btn-link">
|
||||
<img src="/assets/img/avatars/github.png" width="35" /> {{'Sign in with GitHub' | translate}}
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="!isBridged && !needsAuth">
|
||||
<div *ngIf="!isBridged && !authUrl">
|
||||
<label class="label-block">
|
||||
{{'Organization' | translate}}
|
||||
<select class="form-control form-control-sm" [(ngModel)]="orgId"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { BridgeComponent } from "../bridge.component";
|
||||
import { ScalarClientApiService } from "../../../shared/services/scalar/scalar-client-api.service";
|
||||
import { SafeUrl } from "@angular/platform-browser";
|
||||
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { FE_HookshotGithubConnection } from "../../../shared/models/hookshot_github";
|
||||
import { HookshotGithubApiService } from "../../../shared/services/integrations/hookshot-github-api.service";
|
||||
@ -18,15 +18,19 @@ interface HookshotConfig {
|
||||
export class HookshotGithubBridgeConfigComponent extends BridgeComponent<HookshotConfig> implements OnInit {
|
||||
|
||||
public isBusy: boolean;
|
||||
public needsAuth = false;
|
||||
public authUrl: SafeUrl;
|
||||
public loadingConnections = false;
|
||||
public loadingConnections = true;
|
||||
public bridgedRepoSlug: string;
|
||||
|
||||
public orgs: string[] = [];
|
||||
public repos: string[] = []; // for org
|
||||
public orgId: string;
|
||||
|
||||
public repos: string[] = []; // for org
|
||||
public repoId: string;
|
||||
|
||||
constructor(private hookshot: HookshotGithubApiService, private scalar: ScalarClientApiService, public translate: TranslateService) {
|
||||
private timerId: any;
|
||||
|
||||
constructor(private hookshot: HookshotGithubApiService, private scalar: ScalarClientApiService, private sanitizer: DomSanitizer, public translate: TranslateService) {
|
||||
super("hookshot_github", translate);
|
||||
this.translate = translate;
|
||||
}
|
||||
@ -34,15 +38,58 @@ export class HookshotGithubBridgeConfigComponent extends BridgeComponent<Hooksho
|
||||
public ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.prepare();
|
||||
this.loadingConnections = true;
|
||||
this.tryLoadOrgs();
|
||||
}
|
||||
|
||||
private prepare() {
|
||||
private tryLoadOrgs() {
|
||||
this.hookshot.getOrgs().then(r => {
|
||||
console.log(r);
|
||||
this.orgs = r.map(o => o.name);
|
||||
this.orgId = this.orgs[0];
|
||||
this.loadRepos();
|
||||
|
||||
if (this.timerId) {
|
||||
clearTimeout(this.timerId);
|
||||
}
|
||||
}).catch(e => {
|
||||
if (e.status === 403 && e.error.dim_errcode === "T2B_NOT_LOGGED_IN") {
|
||||
this.hookshot.getAuthUrl().then(url => {
|
||||
this.authUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
this.loadingConnections = false;
|
||||
this.timerId = setTimeout(() => {
|
||||
this.tryLoadOrgs();
|
||||
}, 1000);
|
||||
});
|
||||
} else {
|
||||
console.error(e);
|
||||
this.translate.get('Error getting Github information').subscribe((res: string) => {
|
||||
this.toaster.pop("error", res);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public loadRepos() {
|
||||
// TODO
|
||||
this.isBusy = true;
|
||||
this.hookshot.getRepos(this.orgId).then(repos => {
|
||||
this.repos = repos.map(r => r.name);
|
||||
this.repoId = this.repos[0];
|
||||
|
||||
if (this.isBridged) {
|
||||
const conn = this.bridge.config.connections[0].config;
|
||||
this.bridgedRepoSlug = `${conn.org}/${conn.repo}`;
|
||||
}
|
||||
|
||||
this.isBusy = false;
|
||||
this.loadingConnections = false;
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
this.isBusy = false;
|
||||
this.translate.get('Error getting Github information').subscribe((res: string) => {
|
||||
this.toaster.pop("error", res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public get isBridged(): boolean {
|
||||
|
@ -43,25 +43,26 @@ export class HookshotJiraBridgeConfigComponent extends BridgeComponent<HookshotC
|
||||
|
||||
public ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.loadingConnections = true;
|
||||
this.tryLoadInstances();
|
||||
}
|
||||
|
||||
private tryLoadInstances() {
|
||||
this.loadingConnections = true;
|
||||
this.hookshot.getInstances().then(r => {
|
||||
this.instances = r;
|
||||
this.instance = this.instances[0];
|
||||
this.loadProjects();
|
||||
|
||||
if (this.timerId) {
|
||||
clearInterval(this.timerId);
|
||||
clearTimeout(this.timerId);
|
||||
}
|
||||
}).catch(e => {
|
||||
if (e.status === 403 && e.error.dim_errcode === "T2B_NOT_LOGGED_IN") {
|
||||
this.hookshot.getAuthUrl().then(url => {
|
||||
this.authUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
this.loadingConnections = false;
|
||||
this.timerId = setInterval(() => {
|
||||
this.timerId = setTimeout(() => {
|
||||
this.tryLoadInstances();
|
||||
}, 1000);
|
||||
});
|
||||
|
@ -7,5 +7,22 @@ export interface FE_HookshotGithubBridge {
|
||||
}
|
||||
|
||||
export interface FE_HookshotGithubConnection {
|
||||
|
||||
config: {
|
||||
org: string;
|
||||
repo: string;
|
||||
commandPrefix?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FE_HookshotGithubOrg {
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface FE_HookshotGithubRepo {
|
||||
name: string;
|
||||
owner: string;
|
||||
fullName: string;
|
||||
avatarUrl: string;
|
||||
description: string;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { AuthedApi } from "../authed-api";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { FE_HookshotGithubConnection } from "../../models/hookshot_github";
|
||||
import { FE_HookshotGithubConnection, FE_HookshotGithubOrg, FE_HookshotGithubRepo } from "../../models/hookshot_github";
|
||||
|
||||
@Injectable()
|
||||
export class HookshotGithubApiService extends AuthedApi {
|
||||
@ -18,4 +18,16 @@ export class HookshotGithubApiService extends AuthedApi {
|
||||
public unbridgeRoom(roomId: string): Promise<any> {
|
||||
return this.authedDelete("/api/v1/dimension/hookshot/github/room/" + roomId + "/connections/all").toPromise();
|
||||
}
|
||||
|
||||
public getAuthUrl(): Promise<string> {
|
||||
return this.authedGet("/api/v1/dimension/hookshot/github/auth").toPromise().then(r => r['authUrl']);
|
||||
}
|
||||
|
||||
public getOrgs(): Promise<FE_HookshotGithubOrg[]> {
|
||||
return this.authedGet("/api/v1/dimension/hookshot/github/orgs").toPromise().then(r => r['orgs']);
|
||||
}
|
||||
|
||||
public getRepos(orgId: string): Promise<FE_HookshotGithubRepo[]> {
|
||||
return this.authedGet("/api/v1/dimension/hookshot/github/org/" + orgId + "/repos").toPromise().then(r => r['repos']);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user