mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 11:40:02 +00:00
Replace github state with store
This commit is contained in:
@@ -87,6 +87,9 @@ export class ApiV3Service {
|
||||
// /api/v3/notifications
|
||||
public readonly notifications = this.apiV3CustomEndpoint(ApiV3NotificationsPaths);
|
||||
|
||||
// /api/v3/github_pull_requests
|
||||
public readonly github_pull_requests = this.apiV3CollectionEndpoint('github_pull_requests');
|
||||
|
||||
// /api/v3/grids
|
||||
public readonly grids = this.apiV3CustomEndpoint(ApiV3GridsPaths);
|
||||
|
||||
|
||||
@@ -68,6 +68,12 @@ class GithubPullRequest < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def visible?(user = User.current)
|
||||
WorkPackage
|
||||
.visible(user)
|
||||
.exists?(id: work_packages.select(:id))
|
||||
end
|
||||
|
||||
##
|
||||
# When a PR lives long enough and receives many pushes, the same check (say, a CI test run) can be run multiple times.
|
||||
# This method only returns the latest of each type of check_run.
|
||||
|
||||
+22
-14
@@ -27,9 +27,12 @@
|
||||
//++
|
||||
|
||||
import copy from 'copy-text-to-clipboard';
|
||||
import { Component, Inject, Input } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Inject,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import { GitActionsService } from '../git-actions/git-actions.service';
|
||||
import { ISnippet } from "core-app/features/plugins/linked/openproject-github_integration/typings";
|
||||
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
|
||||
import { OPContextMenuComponent } from "core-app/shared/components/op-context-menu/op-context-menu.component";
|
||||
import {
|
||||
@@ -37,14 +40,15 @@ import {
|
||||
OpContextMenuLocalsToken,
|
||||
} from "core-app/shared/components/op-context-menu/op-context-menu.types";
|
||||
import { I18nService } from "core-app/core/i18n/i18n.service";
|
||||
import { ISnippet } from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.model';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'op-git-actions-menu',
|
||||
templateUrl: './git-actions-menu.template.html',
|
||||
styleUrls: [
|
||||
'./styles/git-actions-menu.sass'
|
||||
]
|
||||
'./styles/git-actions-menu.sass',
|
||||
],
|
||||
})
|
||||
export class GitActionsMenuComponent extends OPContextMenuComponent {
|
||||
@Input() public workPackage:WorkPackageResource;
|
||||
@@ -54,38 +58,42 @@ export class GitActionsMenuComponent extends OPContextMenuComponent {
|
||||
copyButtonHelpText: this.I18n.t('js.github_integration.tab_header.git_actions.copy_button_help'),
|
||||
copyResult: {
|
||||
success: this.I18n.t('js.github_integration.tab_header.git_actions.copy_success'),
|
||||
error: this.I18n.t('js.github_integration.tab_header.git_actions.copy_error')
|
||||
}
|
||||
error: this.I18n.t('js.github_integration.tab_header.git_actions.copy_error'),
|
||||
},
|
||||
};
|
||||
|
||||
public lastCopyResult:string = this.text.copyResult.success;
|
||||
|
||||
public showCopyResult:boolean = false;
|
||||
|
||||
public copiedSnippetId:string = '';
|
||||
|
||||
public snippets:ISnippet[] = [
|
||||
{
|
||||
id: 'branch',
|
||||
name: this.I18n.t('js.github_integration.tab_header.git_actions.branch_name'),
|
||||
textToCopy: () => this.gitActions.branchName(this.workPackage)
|
||||
textToCopy: () => this.gitActions.branchName(this.workPackage),
|
||||
},
|
||||
{
|
||||
id: 'message',
|
||||
name: this.I18n.t('js.github_integration.tab_header.git_actions.commit_message'),
|
||||
textToCopy: () => this.gitActions.commitMessage(this.workPackage)
|
||||
textToCopy: () => this.gitActions.commitMessage(this.workPackage),
|
||||
},
|
||||
{
|
||||
id: 'command',
|
||||
name: this.I18n.t('js.github_integration.tab_header.git_actions.cmd'),
|
||||
textToCopy: () => this.gitActions.gitCommand(this.workPackage)
|
||||
textToCopy: () => this.gitActions.gitCommand(this.workPackage),
|
||||
},
|
||||
];
|
||||
|
||||
constructor(@Inject(OpContextMenuLocalsToken)
|
||||
public locals:OpContextMenuLocalsMap,
|
||||
readonly I18n:I18nService,
|
||||
readonly gitActions:GitActionsService) {
|
||||
constructor(
|
||||
@Inject(OpContextMenuLocalsToken)
|
||||
public locals:OpContextMenuLocalsMap,
|
||||
readonly I18n:I18nService,
|
||||
readonly gitActions:GitActionsService,
|
||||
) {
|
||||
super(locals);
|
||||
this.workPackage = this.locals.workPackage;
|
||||
this.workPackage = this.locals.workPackage as WorkPackageResource;
|
||||
}
|
||||
|
||||
public onCopyButtonClick(snippet:ISnippet):void {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<tab-header [workPackage]="workPackage"></tab-header>
|
||||
<tab-prs [workPackage]="workPackage"></tab-prs>
|
||||
<op-tab-prs [workPackage]="workPackage"></op-tab-prs>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { HalResource } from "core-app/features/hal/resources/hal-resource";
|
||||
|
||||
export class GithubCheckRunResource extends HalResource {
|
||||
public get state() {
|
||||
return this.states.projects.get(this.id!) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude the schema _link from the linkable Resources.
|
||||
*/
|
||||
public $linkableKeys():string[] {
|
||||
return _.without(super.$linkableKeys(), 'schema');
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { HalResource } from "core-app/features/hal/resources/hal-resource";
|
||||
|
||||
export class GithubPullRequestResource extends HalResource {
|
||||
public get state() {
|
||||
return this.states.projects.get(this.id!) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude the schema _link from the linkable Resources.
|
||||
*/
|
||||
public $linkableKeys():string[] {
|
||||
return _.without(super.$linkableKeys(), 'schema');
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { HalResource } from "core-app/features/hal/resources/hal-resource";
|
||||
|
||||
export class GithubUserResource extends HalResource {
|
||||
public get state() {
|
||||
return this.states.projects.get(this.id!) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude the schema _link from the linkable Resources.
|
||||
*/
|
||||
public $linkableKeys():string[] {
|
||||
return _.without(super.$linkableKeys(), 'schema');
|
||||
}
|
||||
}
|
||||
@@ -33,19 +33,19 @@ import { TabHeaderComponent } from './tab-header/tab-header.component';
|
||||
import { TabPrsComponent } from './tab-prs/tab-prs.component';
|
||||
import { GitActionsMenuDirective } from './git-actions-menu/git-actions-menu.directive';
|
||||
import { GitActionsMenuComponent } from './git-actions-menu/git-actions-menu.component';
|
||||
import { WorkPackagesGithubPrsService } from './tab-prs/wp-github-prs.service';
|
||||
import { PullRequestComponent } from './pull-request/pull-request.component';
|
||||
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { GithubPullRequestResourceService } from './state/github-pull-request.service';
|
||||
|
||||
export function workPackageGithubPrsCount(
|
||||
workPackage:WorkPackageResource,
|
||||
injector:Injector,
|
||||
):Observable<number> {
|
||||
const githubPrsService = injector.get(WorkPackagesGithubPrsService);
|
||||
const githubPrsService = injector.get(GithubPullRequestResourceService);
|
||||
return githubPrsService
|
||||
.requireAndStream(workPackage)
|
||||
.ofWorkPackage(workPackage)
|
||||
.pipe(
|
||||
map((prs) => prs.length),
|
||||
);
|
||||
@@ -68,7 +68,7 @@ export function initializeGithubIntegrationPlugin(injector:Injector) {
|
||||
OpenprojectTabsModule,
|
||||
],
|
||||
providers: [
|
||||
WorkPackagesGithubPrsService,
|
||||
GithubPullRequestResourceService,
|
||||
],
|
||||
declarations: [
|
||||
GitHubTabComponent,
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
// ++ Ng1FieldControlsWrapper,
|
||||
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
Injector,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
|
||||
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
|
||||
import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service';
|
||||
import { DisplayFieldService } from 'core-app/shared/components/fields/display/display-field.service';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import {
|
||||
AttributeModelLoaderService,
|
||||
SupportedAttributeModels,
|
||||
} from 'core-app/shared/components/fields/macros/attribute-model-loader.service';
|
||||
import { capitalize } from 'core-app/shared/helpers/string-helpers';
|
||||
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
|
||||
|
||||
export const githubPullRequestMacroSelector = 'macro.github-pull-request';
|
||||
|
||||
@Component({
|
||||
selector: githubPullRequestMacroSelector,
|
||||
templateUrl: './pull-request-macro.component.html',
|
||||
styleUrls: ['./pull-request-macro.component.sass'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
HalResourceEditingService,
|
||||
],
|
||||
})
|
||||
export class PullRequestMacroComponent {
|
||||
@Input() pullRequestId:string;
|
||||
|
||||
constructor(
|
||||
readonly elementRef:ElementRef,
|
||||
readonly injector:Injector,
|
||||
readonly resourceLoader:AttributeModelLoaderService,
|
||||
readonly schemaCache:SchemaCacheService,
|
||||
readonly displayField:DisplayFieldService,
|
||||
readonly I18n:I18nService,
|
||||
readonly cdRef:ChangeDetectorRef) {
|
||||
populateInputsFromDataset(this);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const element = this.elementRef.nativeElement as HTMLElement;
|
||||
const model:SupportedAttributeModels = element.dataset.model as any;
|
||||
const id:string = element.dataset.id!;
|
||||
const attributeName:string = element.dataset.attribute!;
|
||||
this.attributeScope = capitalize(model);
|
||||
|
||||
this.loadResourceAttribute(model, id, attributeName);
|
||||
}
|
||||
|
||||
private async loadResourceAttribute(model:SupportedAttributeModels, id:string, attributeName:string) {
|
||||
let resource:HalResource|null;
|
||||
|
||||
try {
|
||||
this.resource = resource = await this.resourceLoader.require(model, id);
|
||||
} catch (e) {
|
||||
console.error(`Failed to render macro ${e}`);
|
||||
return this.markError(this.text.not_found);
|
||||
}
|
||||
|
||||
if (!resource) {
|
||||
this.markError(this.text.not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
const schema = await this.schemaCache.ensureLoaded(resource);
|
||||
this.attribute = schema.attributeFromLocalizedName(attributeName) || attributeName;
|
||||
this.label = schema[this.attribute]?.name;
|
||||
|
||||
if (!this.label) {
|
||||
this.markError(this.text.invalid_attribute(attributeName));
|
||||
}
|
||||
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
markError(message:string) {
|
||||
this.error = this.I18n.t('js.editor.macro.error', { message });
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
}
|
||||
+16
-9
@@ -13,16 +13,16 @@
|
||||
<div class="op-pull-request--info">
|
||||
{{ text.label_created_by }}
|
||||
<img
|
||||
*ngIf="pullRequest._embedded.githubUser"
|
||||
alt='PR author avatar'
|
||||
class='op-pull-request--avatar op-avatar op-avatar_mini'
|
||||
[src]="pullRequest.githubUser.avatarUrl"
|
||||
*ngIf="pullRequest.githubUser"
|
||||
[src]="pullRequest._embedded.githubUser.avatarUrl"
|
||||
/>
|
||||
<span class='op-pull-request--user'>
|
||||
<a
|
||||
[href]="pullRequest.githubUser.htmlUrl"
|
||||
[textContent]="pullRequest.githubUser.login"
|
||||
*ngIf="pullRequest.githubUser"
|
||||
*ngIf="pullRequest._embedded.githubUser"
|
||||
[href]="pullRequest._embedded.githubUser.htmlUrl"
|
||||
[textContent]="pullRequest._embedded.githubUser.login"
|
||||
></a>.
|
||||
</span>
|
||||
|
||||
@@ -37,15 +37,22 @@
|
||||
{{state}}
|
||||
</span>
|
||||
|
||||
<span class="op-pull-request--checks-label" *ngIf="pullRequest.checkRuns?.length">{{ text.label_actions }}</span>
|
||||
<span class="op-pull-request--checks-label"
|
||||
*ngIf="pullRequest._embedded.checkRuns?.length"
|
||||
[textContent]="text.label_actions"
|
||||
></span>
|
||||
|
||||
<ul [attr.aria-label]="text.label_actions" class='op-pull-request--checks' *ngIf="pullRequest.checkRuns?.length">
|
||||
<li class='op-pr-check' *ngFor="let checkRun of pullRequest.checkRuns">
|
||||
<ul
|
||||
*ngIf="pullRequest._embedded.checkRuns?.length"
|
||||
[attr.aria-label]="text.label_actions"
|
||||
class='op-pull-request--checks'
|
||||
>
|
||||
<li class='op-pr-check' *ngFor="let checkRun of pullRequest._embedded.checkRuns">
|
||||
<span class='op-pr-check--state-icon' [ngClass]="'op-pr-check--state-icon_' + checkRunState(checkRun)">
|
||||
<op-icon icon-classes="icon-{{ checkRunStateIcon(checkRun) }}"
|
||||
[icon-title]="checkRunStateText(checkRun)"></op-icon>
|
||||
</span>
|
||||
<span class='op-pr-check--avatar'><img alt='app owner avatar' [src]="checkRun.appOwnerAvatarUrl" /></span>
|
||||
<span class='op-pr-check--avatar'><img alt='app owner avatar' [src]="checkRun.appOwnerAvatarUrl"/></span>
|
||||
|
||||
<span class='op-pr-check--info'>
|
||||
<span class='op-pr-check--name' [textContent]="checkRun.name"></span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//-- copyright
|
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
@@ -26,24 +26,33 @@
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { GithubCheckRunResource } from 'core-app/features/plugins/linked/openproject-github_integration/hal/resources/github-check-run-resource';
|
||||
import { IGithubPullRequestResource } from "core-app/features/plugins/linked/openproject-github_integration/typings";
|
||||
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
|
||||
import { I18nService } from "core-app/core/i18n/i18n.service";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
HostBinding,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import {
|
||||
IGithubCheckRunResource,
|
||||
IGithubPullRequest,
|
||||
} from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.model';
|
||||
|
||||
@Component({
|
||||
selector: 'github-pull-request',
|
||||
selector: 'op-github-pull-request',
|
||||
templateUrl: './pull-request.component.html',
|
||||
styleUrls: [
|
||||
'./pull-request.component.sass',
|
||||
'./pr-check.component.sass',
|
||||
],
|
||||
host: { class: 'op-pull-request' }
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
|
||||
export class PullRequestComponent {
|
||||
@Input() public pullRequest:IGithubPullRequestResource;
|
||||
@HostBinding('class.op-pull-request') className = true;
|
||||
|
||||
@Input() public pullRequest:IGithubPullRequest;
|
||||
|
||||
public text = {
|
||||
label_created_by: this.I18n.t('js.label_created_by'),
|
||||
@@ -52,63 +61,64 @@ export class PullRequestComponent {
|
||||
label_actions: this.I18n.t('js.github_integration.github_actions'),
|
||||
};
|
||||
|
||||
constructor(readonly PathHelper:PathHelperService,
|
||||
readonly I18n:I18nService) {
|
||||
constructor(
|
||||
readonly PathHelper:PathHelperService,
|
||||
readonly I18n:I18nService,
|
||||
) {
|
||||
}
|
||||
|
||||
get state() {
|
||||
if (this.pullRequest.state === 'open') {
|
||||
return (this.pullRequest.draft ? 'draft' : 'open');
|
||||
} else {
|
||||
return(this.pullRequest.merged ? 'merged' : 'closed');
|
||||
}
|
||||
return (this.pullRequest.merged ? 'merged' : 'closed');
|
||||
}
|
||||
|
||||
public checkRunStateText(checkRun:GithubCheckRunResource) {
|
||||
public checkRunStateText(checkRun:IGithubCheckRunResource) {
|
||||
/* Github apps can *optionally* add an output object (and a title) which is the most relevant information to display.
|
||||
If that is not present, we can display the conclusion (which is present only on finished runs).
|
||||
If that is not present, we can always fall back to the status. */
|
||||
return(checkRun.outputTitle || checkRun.conclusion || checkRun.status);
|
||||
return (checkRun.outputTitle || checkRun.conclusion || checkRun.status);
|
||||
}
|
||||
|
||||
public checkRunState(checkRun:GithubCheckRunResource) {
|
||||
return(checkRun.conclusion || checkRun.status);
|
||||
public checkRunState(checkRun:IGithubCheckRunResource) {
|
||||
return (checkRun.conclusion || checkRun.status);
|
||||
}
|
||||
|
||||
public checkRunStateIcon(checkRun:GithubCheckRunResource) {
|
||||
public checkRunStateIcon(checkRun:IGithubCheckRunResource) {
|
||||
switch (this.checkRunState(checkRun)) {
|
||||
case 'success': {
|
||||
return 'checkmark'
|
||||
return 'checkmark';
|
||||
}
|
||||
case 'queued': {
|
||||
return 'getting-started'
|
||||
return 'getting-started';
|
||||
}
|
||||
case 'in_progress': {
|
||||
return 'loading1'
|
||||
return 'loading1';
|
||||
}
|
||||
case 'failure': {
|
||||
return 'cancel'
|
||||
return 'cancel';
|
||||
}
|
||||
case 'timed_out': {
|
||||
return 'reminder'
|
||||
return 'reminder';
|
||||
}
|
||||
case 'action_required': {
|
||||
return 'warning'
|
||||
return 'warning';
|
||||
}
|
||||
case 'stale': {
|
||||
return 'not-supported'
|
||||
return 'not-supported';
|
||||
}
|
||||
case 'skipped': {
|
||||
return 'redo'
|
||||
return 'redo';
|
||||
}
|
||||
case 'neutral': {
|
||||
return 'minus1'
|
||||
return 'minus1';
|
||||
}
|
||||
case 'cancelled': {
|
||||
return 'minus1'
|
||||
return 'minus1';
|
||||
}
|
||||
default: {
|
||||
return 'not-supported'
|
||||
return 'not-supported';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-30
@@ -1,4 +1,8 @@
|
||||
import { HalResourceClass } from 'core-app/modules/hal/resources/hal-resource';
|
||||
import {
|
||||
IHalResourceLink,
|
||||
IHalResourceLinks,
|
||||
} from 'core-app/core/state/hal-resource';
|
||||
import { ID } from '@datorama/akita';
|
||||
|
||||
export interface ISnippet {
|
||||
id:string;
|
||||
@@ -6,35 +10,6 @@ export interface ISnippet {
|
||||
textToCopy:() => string
|
||||
}
|
||||
|
||||
export interface IGithubPullRequestResource extends HalResourceClass {
|
||||
additionsCount?:number;
|
||||
body?:{
|
||||
format?:string;
|
||||
raw?:string;
|
||||
html?:string;
|
||||
},
|
||||
changedFilesCount?:number;
|
||||
commentsCount?:number;
|
||||
createdAt?:string;
|
||||
deletionsCount?:number;
|
||||
draft?:boolean;
|
||||
githubUpdatedAt?:string;
|
||||
htmlUrl?:string;
|
||||
id?:number;
|
||||
labels?:string[];
|
||||
merged?:boolean;
|
||||
mergedAt?:string;
|
||||
mergedBy?:IGithubUserResource;
|
||||
number?:number;
|
||||
repository?:string;
|
||||
reviewCommentsCount?:number;
|
||||
state?:string;
|
||||
title?:string;
|
||||
updatedAt?:string;
|
||||
githubUser?:IGithubUserResource;
|
||||
checkRuns?:IGithubCheckRunResource[];
|
||||
}
|
||||
|
||||
export interface IGithubUserResource {
|
||||
avatarUrl:string;
|
||||
htmlUrl:string;
|
||||
@@ -53,3 +28,44 @@ export interface IGithubCheckRunResource {
|
||||
startedAt:string;
|
||||
status:string;
|
||||
}
|
||||
|
||||
export interface IGithubPullRequestResourceLinks extends IHalResourceLinks {
|
||||
githubUser:IHalResourceLink;
|
||||
mergedBy?:IHalResourceLink;
|
||||
checkRuns?:IHalResourceLink[];
|
||||
}
|
||||
|
||||
export interface IGithubPullRequestResourceEmbedded {
|
||||
githubUser?:IGithubUserResource;
|
||||
mergedBy?:IGithubUserResource;
|
||||
checkRuns?:IGithubCheckRunResource[];
|
||||
}
|
||||
|
||||
export interface IGithubPullRequest {
|
||||
id:ID;
|
||||
additionsCount?:number;
|
||||
body?:{
|
||||
format?:string;
|
||||
raw?:string;
|
||||
html?:string;
|
||||
},
|
||||
changedFilesCount?:number;
|
||||
commentsCount?:number;
|
||||
createdAt?:string;
|
||||
deletionsCount?:number;
|
||||
draft?:boolean;
|
||||
githubUpdatedAt?:string;
|
||||
htmlUrl?:string;
|
||||
labels?:string[];
|
||||
merged?:boolean;
|
||||
mergedAt?:string;
|
||||
number?:number;
|
||||
repository?:string;
|
||||
reviewCommentsCount?:number;
|
||||
state?:string;
|
||||
title?:string;
|
||||
updatedAt?:string;
|
||||
|
||||
_links:IGithubPullRequestResourceLinks;
|
||||
_embedded:IGithubPullRequestResourceEmbedded;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
|
||||
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
|
||||
import {
|
||||
CollectionStore,
|
||||
ResourceCollectionLoadOptions,
|
||||
ResourceCollectionService,
|
||||
} from 'core-app/core/state/resource-collection.service';
|
||||
import { IGithubPullRequest } from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.model';
|
||||
import { GithubPullRequestsStore } from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.store';
|
||||
import { ID } from '@datorama/akita';
|
||||
|
||||
@Injectable()
|
||||
export class GithubPullRequestResourceService extends ResourceCollectionService<IGithubPullRequest> {
|
||||
ofWorkPackage(workPackage:WorkPackageResource) {
|
||||
return this.requireEntity(`${workPackage.href as string}/github_pull_requests`);
|
||||
}
|
||||
|
||||
requireSingle(id:ID) {
|
||||
return this.requireEntity(this.entityPath(id));
|
||||
}
|
||||
|
||||
fetchCollection(
|
||||
params:ApiV3ListParameters|string,
|
||||
options:ResourceCollectionLoadOptions = { handleErrors: true },
|
||||
):Observable<IHALCollection<IGithubPullRequest>> {
|
||||
if (typeof params !== 'string') {
|
||||
throw new Error('Github PR service can only deal with string collection keys being their full paths')
|
||||
}
|
||||
|
||||
return this.request(params, params, options);
|
||||
}
|
||||
|
||||
protected basePath():string {
|
||||
return this.apiV3Service.github_pull_requests.path;
|
||||
}
|
||||
|
||||
protected entityPath(id:ID) {
|
||||
return this.apiV3Service.github_pull_requests.id(id).path;
|
||||
}
|
||||
|
||||
protected createStore():CollectionStore<IGithubPullRequest> {
|
||||
return new GithubPullRequestsStore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { EntityStore, StoreConfig } from '@datorama/akita';
|
||||
import { CollectionState, createInitialCollectionState } from 'core-app/core/state/collection-store';
|
||||
import { IGithubPullRequest } from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.model';
|
||||
|
||||
export interface GithubPullRequestsState extends CollectionState<IGithubPullRequest> {
|
||||
}
|
||||
|
||||
@StoreConfig({ name: 'github-pull-requests' })
|
||||
export class GithubPullRequestsStore extends EntityStore<GithubPullRequestsState> {
|
||||
constructor() {
|
||||
super(createInitialCollectionState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<ng-container *ngIf="(pullRequests$ | async)?.length === 0">
|
||||
<p [innerHTML]="getEmptyText()"></p>
|
||||
</ng-container>
|
||||
|
||||
<op-github-pull-request
|
||||
*ngFor="let pullRequest of (pullRequests$ | async)"
|
||||
[pullRequest]="pullRequest"
|
||||
></op-github-pull-request>
|
||||
@@ -1,4 +1,4 @@
|
||||
//-- copyright
|
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
@@ -26,43 +26,52 @@
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
|
||||
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
|
||||
import { IGithubPullRequestResource } from "../../../../../../../../modules/github_integration/frontend/module/typings";
|
||||
import { I18nService } from "core-app/core/i18n/i18n.service";
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { GithubPullRequestResourceService } from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.service';
|
||||
import { IGithubPullRequest } from 'core-app/features/plugins/linked/openproject-github_integration/state/github-pull-request.model';
|
||||
import {
|
||||
map,
|
||||
shareReplay,
|
||||
} from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'tab-prs',
|
||||
templateUrl: './tab-prs.template.html',
|
||||
host: { class: 'op-prs' }
|
||||
selector: 'op-tab-prs',
|
||||
templateUrl: './tab-prs.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TabPrsComponent implements OnInit {
|
||||
@Input() public workPackage:WorkPackageResource;
|
||||
@HostBinding('class.op-github-prs') className = true;
|
||||
|
||||
public pullRequests:IGithubPullRequestResource[] = [];
|
||||
@Input() workPackage:WorkPackageResource;
|
||||
|
||||
pullRequests$:Observable<IGithubPullRequest[]>;
|
||||
|
||||
emptyText:string;
|
||||
|
||||
constructor(
|
||||
readonly I18n:I18nService,
|
||||
readonly apiV3Service:ApiV3Service,
|
||||
readonly halResourceService:HalResourceService,
|
||||
readonly changeDetector:ChangeDetectorRef,
|
||||
readonly githubPullRequests:GithubPullRequestResourceService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const pullRequestsPath = this.apiV3Service.work_packages.id({id: this.workPackage.id })?.github_pull_requests.path;
|
||||
|
||||
this.halResourceService
|
||||
.get<CollectionResource<IGithubPullRequestResource>>(pullRequestsPath)
|
||||
.subscribe((value) => {
|
||||
this.pullRequests = value.elements;
|
||||
this.changeDetector.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
public getEmptyText() {
|
||||
return this.I18n.t('js.github_integration.tab_prs.empty',{ wp_id: this.workPackage.id });
|
||||
ngOnInit():void {
|
||||
this.emptyText = this.I18n.t('js.github_integration.tab_prs.empty', { wp_id: this.workPackage.id });
|
||||
this.pullRequests$ = this
|
||||
.githubPullRequests
|
||||
.ofWorkPackage(this.workPackage)
|
||||
.pipe(
|
||||
map((elements) => _.sortBy(elements, 'updatedAt')),
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<ng-container *ngIf="pullRequests.length === 0">
|
||||
<p [innerHTML]="getEmptyText()"></p>
|
||||
</ng-container>
|
||||
|
||||
<github-pull-request [pullRequest]="pullRequest" *ngFor="let pullRequest of pullRequests"></github-pull-request>
|
||||
@@ -1,51 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
|
||||
import { HalResource } from "core-app/features/hal/resources/hal-resource";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ConfigurationService } from "core-app/core/config/configuration.service";
|
||||
import { WorkPackageLinkedResourceCache } from 'core-app/features/work-packages/components/wp-single-view-tabs/wp-linked-resource-cache.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkPackagesGithubPrsService extends WorkPackageLinkedResourceCache<HalResource[]> {
|
||||
|
||||
constructor(public ConfigurationService:ConfigurationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected load(workPackage:WorkPackageResource):Promise<HalResource[]> {
|
||||
return workPackage.github_pull_requests.$update().then((data:any) => {
|
||||
return this.sortList(data.elements);
|
||||
});
|
||||
}
|
||||
|
||||
protected sortList(pullRequests:HalResource[], attr = 'createdAt'):HalResource[] {
|
||||
return _.sortBy(_.flatten(pullRequests), attr);
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2023 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module API
|
||||
module V3
|
||||
module GithubPullRequests
|
||||
class GithubPullRequestsAPI < ::API::OpenProjectAPI
|
||||
resources :github_pull_requests do
|
||||
route_param :id, type: Integer, desc: 'Pull Request ID' do
|
||||
after_validation do
|
||||
@pull_request = GithubPullRequest.find(declared_params[:id])
|
||||
|
||||
authorize_by_with_raise @pull_request.visible?(current_user) do
|
||||
raise API::Errors::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
get &::API::V3::Utilities::Endpoints::Show.new(model: ::GithubPullRequest,
|
||||
instance_generator: ->(*) { @pull_request })
|
||||
.mount
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -83,6 +83,10 @@ module OpenProject::GithubIntegration
|
||||
mount ::API::V3::GithubPullRequests::GithubPullRequestsByWorkPackageAPI
|
||||
end
|
||||
|
||||
add_api_endpoint 'API::V3::Root' do
|
||||
mount ::API::V3::GithubPullRequests::GithubPullRequestsAPI
|
||||
end
|
||||
|
||||
config.to_prepare do
|
||||
# Register the cron job to clean up old github pull requests
|
||||
::Cron::CronJob.register! ::Cron::ClearOldPullRequestsJob
|
||||
|
||||
+7
-43
@@ -45,19 +45,20 @@ module OpenProject::GithubIntegration
|
||||
github_system_user = User.find_by(id: payload.open_project_user_id)
|
||||
work_packages = find_mentioned_work_packages(payload.pull_request.body, github_system_user)
|
||||
|
||||
pull_request = upsert_pull_request(work_packages)
|
||||
|
||||
comment_on_referenced_work_packages(
|
||||
work_packages_to_comment_on(payload.action, work_packages),
|
||||
work_packages_to_comment_on(payload.action, pull_request, work_packages),
|
||||
github_system_user,
|
||||
journal_entry
|
||||
journal_entry(pull_request)
|
||||
)
|
||||
upsert_pull_request(work_packages)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :payload
|
||||
|
||||
def work_packages_to_comment_on(action, work_packages)
|
||||
def work_packages_to_comment_on(action, pull_request, work_packages)
|
||||
if action == 'edited'
|
||||
without_already_referenced(work_packages, pull_request)
|
||||
else
|
||||
@@ -65,13 +66,6 @@ module OpenProject::GithubIntegration
|
||||
end
|
||||
end
|
||||
|
||||
def pull_request
|
||||
@pull_request ||= GithubPullRequest
|
||||
.where(github_id: payload.pull_request.id)
|
||||
.or(GithubPullRequest.where(github_html_url: payload.pull_request.html_url))
|
||||
.take
|
||||
end
|
||||
|
||||
def upsert_pull_request(work_packages)
|
||||
return if work_packages.empty? && pull_request.nil?
|
||||
|
||||
@@ -79,38 +73,8 @@ module OpenProject::GithubIntegration
|
||||
work_packages:)
|
||||
end
|
||||
|
||||
def journal_entry
|
||||
key = journal_entry_i18n_key
|
||||
return nil unless key
|
||||
|
||||
pull_request = payload.pull_request
|
||||
repository = pull_request.base.repo
|
||||
sender = payload.sender
|
||||
|
||||
I18n.t("github_integration.pull_request_#{key}_comment",
|
||||
pr_number: pull_request.number,
|
||||
pr_title: pull_request.title,
|
||||
pr_url: pull_request.html_url,
|
||||
repository: repository.full_name,
|
||||
repository_url: repository.html_url,
|
||||
github_user: sender.login,
|
||||
github_user_url: sender.html_url)
|
||||
end
|
||||
|
||||
def journal_entry_i18n_key
|
||||
key = {
|
||||
'opened' => 'opened',
|
||||
'reopened' => 'opened',
|
||||
'closed' => 'closed',
|
||||
'edited' => 'referenced',
|
||||
'referenced' => 'referenced',
|
||||
'ready_for_review' => 'ready_for_review'
|
||||
}[payload.action]
|
||||
|
||||
return 'merged' if key == 'closed' && payload.pull_request.merged
|
||||
return 'draft' if key == 'open' && payload.pull_request.draft
|
||||
|
||||
key
|
||||
def journal_entry(pull_request)
|
||||
%(<macro class="github_pull_request" data-pull-request-id="#{pull_request.id}"></macro>)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user