* Let GitHub integration show changes in a separate tab
* added new github integration icons to icon font
* add tab content: working tab-header and copy-menu
* modernise github_integrations ruby code
* refactored some code to be more modern ruby (if wrote most of it 7
years ago and couldn't look at some parts without squinting too much)
* make some intended-to-be-private module methods actually private
* fixed all rubocop errors in the /modules/github_integration
* re-organized tests a little
* gave our rubocop.yml some RSpec-related defaults -- happy to discuss
these, but I think we can live with these as a good starting point
👆 all without actually (intentionally) changing the behaviour
* removed dead angular template code
* codeclimate found more things than rubocop :)
* removed create-pr-button since we decided against implementing that feature
* added missing translations
* properly cache the github related part of the wp api
* lower case pull requests in translations
* fix specs
pull/9151/head
parent
ebbe5ec584
commit
a46de71009
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 148 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,51 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
en: |
||||
js: |
||||
github_integration: |
||||
work_packages: |
||||
tab_name: "GitHub" |
||||
tab_header: |
||||
title: "Pull Requests" |
||||
copy_menu: |
||||
label: Git |
||||
description: Copy important git content to clipboard |
||||
git_actions: |
||||
branch: Branch |
||||
branch_help: Copy the default branch name. |
||||
message: Message |
||||
message_help: Copy the default commit message. |
||||
cmd: Command |
||||
cmd_help: Copy a shell command which creates a new branch and an empty commit using the default branch name and commit message. |
||||
title: Copy to Clipboard |
||||
copy_button_help: Copy to clipboard |
||||
copy_success: ✅ Copied! |
||||
copy_error: ❌ Copy failed! |
||||
tab_prs: |
||||
empty: There are no pull requests linked yet. Link an existing PR by using the code <code>OP#%{wp_id}</code> in the PR description or create a new PR. |
@ -0,0 +1,120 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import copy from 'copy-text-to-clipboard'; |
||||
import {Component, Inject, Input} from '@angular/core'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; |
||||
import { GitActionsService} from '../git-actions/git-actions.service'; |
||||
import { OPContextMenuComponent } from 'core-app/components/op-context-menu/op-context-menu.component'; |
||||
import { OpContextMenuLocalsMap, OpContextMenuLocalsToken } from 'core-app/components/op-context-menu/op-context-menu.types'; |
||||
|
||||
interface Tab { |
||||
label:string, |
||||
help:string, |
||||
selected:boolean, |
||||
lines:number, |
||||
textToCopy: ()=>string |
||||
} |
||||
|
||||
@Component({ |
||||
selector: 'git-actions-menu', |
||||
templateUrl: './git-actions-menu.template.html', |
||||
styleUrls: [ |
||||
'./styles/git-actions-menu.sass' |
||||
] |
||||
}) |
||||
export class GitActionsMenuComponent extends OPContextMenuComponent { |
||||
@Input() public workPackage:WorkPackageResource; |
||||
|
||||
public text = { |
||||
title: this.I18n.t('js.github_integration.tab_header.git_actions.title'), |
||||
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') |
||||
} |
||||
}; |
||||
|
||||
public lastCopyResult:string = this.text.copyResult.success; |
||||
public showCopyResult:boolean = false; |
||||
|
||||
public tabs:Tab[] = [ |
||||
{ |
||||
label: this.I18n.t('js.github_integration.tab_header.git_actions.branch'), |
||||
help: this.I18n.t('js.github_integration.tab_header.git_actions.branch_help'), |
||||
selected: true, |
||||
lines: 1, |
||||
textToCopy: () => this.gitActions.branchName(this.workPackage) |
||||
}, |
||||
{ |
||||
label: this.I18n.t('js.github_integration.tab_header.git_actions.message'), |
||||
help: this.I18n.t('js.github_integration.tab_header.git_actions.message_help'), |
||||
selected: false, |
||||
lines: 6, |
||||
textToCopy: () => this.gitActions.commitMessage(this.workPackage) |
||||
}, |
||||
{ |
||||
label: this.I18n.t('js.github_integration.tab_header.git_actions.cmd'), |
||||
help: this.I18n.t('js.github_integration.tab_header.git_actions.cmd_help'), |
||||
selected: false, |
||||
lines: 6, |
||||
textToCopy: () => this.gitActions.gitCommand(this.workPackage) |
||||
}, |
||||
]; |
||||
|
||||
constructor(@Inject(OpContextMenuLocalsToken) |
||||
public locals:OpContextMenuLocalsMap, |
||||
readonly I18n:I18nService, |
||||
readonly gitActions:GitActionsService) { |
||||
super(locals); |
||||
this.workPackage = this.locals.workPackage; |
||||
} |
||||
|
||||
public selectedTab():Tab { |
||||
const selectedTabs = this.tabs.filter((tab)=>tab.selected); |
||||
return(selectedTabs[0] || this.tabs[0]); |
||||
} |
||||
|
||||
public selectTab(tab:Tab) { |
||||
this.tabs.forEach(tab => tab.selected = false); |
||||
tab.selected = true; |
||||
} |
||||
|
||||
public onCopyButtonClick() { |
||||
const success = copy(this.selectedTab().textToCopy()) |
||||
|
||||
if (success) { |
||||
this.lastCopyResult = this.text.copyResult.success; |
||||
} else { |
||||
this.lastCopyResult = this.text.copyResult.error; |
||||
} |
||||
this.showCopyResult = true; |
||||
window.setTimeout(() => { this.showCopyResult = false;}, 2000); |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {OpContextMenuItem} from 'core-components/op-context-menu/op-context-menu.types'; |
||||
import {OPContextMenuService} from 'core-components/op-context-menu/op-context-menu.service'; |
||||
import {Directive, ElementRef, Input} from '@angular/core'; |
||||
import {OpContextMenuTrigger} from 'core-components/op-context-menu/handlers/op-context-menu-trigger.directive'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {GitActionsMenuComponent} from './git-actions-menu.component'; |
||||
|
||||
@Directive({ |
||||
selector: '[gitActionsCopyDropdown]' |
||||
}) |
||||
export class GitActionsMenuDirective extends OpContextMenuTrigger { |
||||
@Input('gitActionsCopyDropdown-workPackage') public workPackage:WorkPackageResource; |
||||
|
||||
constructor(readonly elementRef:ElementRef, |
||||
readonly opContextMenu:OPContextMenuService) { |
||||
super(elementRef, opContextMenu); |
||||
} |
||||
|
||||
protected open(evt:JQuery.TriggeredEvent) { |
||||
this.opContextMenu.show(this, evt, GitActionsMenuComponent); |
||||
} |
||||
|
||||
public get locals():{ showAnchorRight?:boolean, contextMenuId?:string, items:OpContextMenuItem[], workPackage:WorkPackageResource } { |
||||
return { |
||||
workPackage: this.workPackage, |
||||
contextMenuId: 'github-integration-git-actions-menu', |
||||
items: [] |
||||
}; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,24 @@ |
||||
<div class="git-actions-menu dropdown-relative dropdown -overflow-in-view dropdown-anchor-right"> |
||||
<h3 class="title"> |
||||
<op-icon icon-classes="button--icon icon-console-light"></op-icon> |
||||
{{text.title}} |
||||
</h3> |
||||
<ul class="tabrow"> |
||||
<!-- The hrefs with empty URLs are necessary for IE10 to focus these links |
||||
properly. Thus, don't remove the hrefs or the empty URLs! --> |
||||
<li *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.selected]="tab.selected"> |
||||
<a href="" [textContent]="tab.label"></a> |
||||
</li> |
||||
</ul> |
||||
<div class="copy-wrapper"> |
||||
<textarea class="copy-content" [textContent]="selectedTab().textToCopy()" [style.height.em]="selectedTab().lines" readonly="true"></textarea> |
||||
<button class="button copy-button" |
||||
type="button" |
||||
[attr.aria-label]="text.copyButtonHelpText" |
||||
(click)="onCopyButtonClick()"> |
||||
<op-icon icon-classes="button--icon icon-copy"></op-icon> |
||||
</button> |
||||
<div class="copy-result-message" *ngIf="showCopyResult" [textContent]="lastCopyResult"></div> |
||||
</div> |
||||
<div class="help-text" [textContent]="selectedTab().help"></div> |
||||
</div> |
@ -0,0 +1,68 @@ |
||||
.git-actions-menu |
||||
background-color: var(--body-background) |
||||
border: var(--content-default-border-width) solid var(--content-default-border-color) |
||||
padding: 1rem |
||||
min-width: 25rem |
||||
box-shadow: .1em .1em .4em rgba(0,0,0,0.1) |
||||
|
||||
.tabrow |
||||
margin-bottom: 0.5rem |
||||
|
||||
.copy-wrapper |
||||
width: 100% |
||||
position: relative |
||||
margin-bottom: 1rem |
||||
|
||||
.copy-content |
||||
width: calc(100% - 2.2em) |
||||
// the min-height should be the size of the copy-icon, which is the sum of: |
||||
// 2 * button padding (0.65em) |
||||
// font-size of the icon (0.9em) |
||||
// 1px where I don't know where it comes from |
||||
min-height: calc(2 * 0.65em + 0.9em + 1px) |
||||
border-radius: 2px 0 0 2px |
||||
padding: 0.65em |
||||
color: var(--gray-dark) |
||||
white-space: pre |
||||
resize: none |
||||
font-size: 1rem |
||||
display: inline-block |
||||
|
||||
.copy-button |
||||
margin: 0 |
||||
border: 1px solid #ccc |
||||
border-radius: 0 2px 2px 0 |
||||
vertical-align: top |
||||
left: -1px |
||||
position: relative |
||||
|
||||
&:hover |
||||
border-color: #999 |
||||
|
||||
.copy-result-message |
||||
background-color: var(--main-menu-bg-color) |
||||
display: inline-block |
||||
padding: 0.5em |
||||
border-radius: 5px |
||||
color: var(--main-menu-font-color) |
||||
position: absolute |
||||
right: 0 |
||||
top: calc(2 * 0.65em + 0.9em + 1px + 9px) |
||||
box-shadow: 1px 1px 4px var(--gray-dark) |
||||
|
||||
&:before |
||||
content: "" |
||||
border-bottom: 0.6em solid var(--main-menu-bg-color) |
||||
height: 0 |
||||
width: 0 |
||||
position: absolute |
||||
top: -9px |
||||
right: 10px |
||||
border-left: 0.3em solid transparent |
||||
border-right: 0.3em solid transparent |
||||
|
||||
.help-text |
||||
color: var(--gray-dark) |
||||
margin-bottom: 1rem |
||||
display: inline-block |
||||
max-width: 20em |
@ -0,0 +1,97 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
/*jshint expr: true*/ |
||||
|
||||
import { GitActionsService } from './git-actions.service'; |
||||
import { WorkPackageResource } from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import { PathHelperService } from 'core-app/modules/common/path-helper/path-helper.service'; |
||||
import { TestBed, waitForAsync } from '@angular/core/testing'; |
||||
|
||||
describe('GitActionsService', function() { |
||||
let service:GitActionsService; |
||||
|
||||
const createWorkPackage = (overrides = {}) => { |
||||
const defaults = { |
||||
id: '42', |
||||
subject: 'Find the question', |
||||
description: { |
||||
raw: 'I recently found the answer is 42. We need to compute the correct question.' |
||||
}, |
||||
type: { name: 'User Story' }, |
||||
pathHelper: new PathHelperService() |
||||
}; |
||||
const workPackage = { ...defaults, ...overrides }; |
||||
return(workPackage as WorkPackageResource); |
||||
}; |
||||
|
||||
beforeEach(waitForAsync(() => { |
||||
TestBed.configureTestingModule({ |
||||
providers: [ |
||||
GitActionsService |
||||
] |
||||
}).compileComponents() |
||||
.then(() => { |
||||
service = TestBed.inject(GitActionsService); |
||||
}); |
||||
})); |
||||
|
||||
beforeEach(() => { |
||||
service = new GitActionsService(); |
||||
}); |
||||
|
||||
|
||||
it('it produces a branch name, commit message, and a git command', () => { |
||||
const wp = createWorkPackage(); |
||||
expect(service.branchName(wp)).toEqual('user-story/42-find-the-question'); |
||||
expect(service.commitMessage(wp)).toEqual(`[#42] Find the question
|
||||
|
||||
I recently found the answer is 42. We need to compute the correct |
||||
question. |
||||
|
||||
http://localhost:9876/work_packages/42
|
||||
`);
|
||||
expect(service.gitCommand(wp)).toEqual(`git checkout -b 'user-story/42-find-the-question' && git commit --allow-empty -m '[#42] Find the question
|
||||
|
||||
I recently found the answer is 42. We need to compute the correct |
||||
question. |
||||
|
||||
http://localhost:9876/work_packages/42
|
||||
'`);
|
||||
}); |
||||
|
||||
it('shell-escapes output for the git-command', () => { |
||||
const wp = createWorkPackage({description: { raw: "' && rm -rf / #"}}); |
||||
expect(service.gitCommand(wp)).toEqual(`git checkout -b 'user-story/42-find-the-question' && git commit --allow-empty -m '[#42] Find the question
|
||||
|
||||
'\\'' && rm -rf / # |
||||
|
||||
http://localhost:9876/work_packages/42
|
||||
'`);
|
||||
}); |
||||
}); |
@ -0,0 +1,63 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {Injectable} from '@angular/core'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import formatter from 'tickety-tick-formatter'; |
||||
|
||||
// probably not providable in root when we want to cache the formatter and set custom templates
|
||||
@Injectable({ |
||||
providedIn: 'root', |
||||
}) |
||||
export class GitActionsService { |
||||
private formatter = formatter(); |
||||
|
||||
public branchName(workPackage:WorkPackageResource):string { |
||||
return(this.formatter.branch(this.formattingInput(workPackage))); |
||||
} |
||||
|
||||
public commitMessage(workPackage:WorkPackageResource):string { |
||||
return(this.formatter.commit(this.formattingInput(workPackage))); |
||||
} |
||||
|
||||
public gitCommand(workPackage:WorkPackageResource):string { |
||||
return(this.formatter.command(this.formattingInput(workPackage))); |
||||
} |
||||
|
||||
private formattingInput(workPackage: WorkPackageResource) { |
||||
const type = workPackage.type.name || ''; |
||||
const id = workPackage.id || ''; |
||||
const title = workPackage.subject; |
||||
const url = window.location.origin + workPackage.pathHelper.workPackagePath(id); |
||||
const description = workPackage.description.raw || ''; |
||||
|
||||
return({ |
||||
id, type, title, url, description |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {Component, Input, OnInit} from '@angular/core'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; |
||||
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; |
||||
import { TabComponent } from 'core-app/components/wp-tabs/components/wp-tab-wrapper/tab'; |
||||
|
||||
@Component({ |
||||
selector: 'github-tab', |
||||
templateUrl: './github-tab.template.html' |
||||
}) |
||||
export class GitHubTabComponent implements OnInit, TabComponent { |
||||
@Input() public workPackage:WorkPackageResource; |
||||
|
||||
public pullRequests = []; |
||||
|
||||
constructor(readonly PathHelper:PathHelperService, |
||||
readonly I18n:I18nService) { |
||||
} |
||||
|
||||
ngOnInit() { |
||||
this.pullRequests = []; |
||||
} |
||||
} |
@ -0,0 +1,4 @@ |
||||
<tab-header [workPackage]="workPackage"></tab-header> |
||||
<tab-prs [workPackage]="workPackage" |
||||
[pullRequests]="pullRequests" |
||||
></tab-prs> |
@ -0,0 +1,82 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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,
|
||||
// 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 docs/COPYRIGHT.rdoc for more details.
|
||||
|
||||
import {Injector, NgModule} from '@angular/core'; |
||||
|
||||
import {HookService} from 'core-app/modules/plugins/hook-service'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import { Tab } from 'core-app/components/wp-tabs/components/wp-tab-wrapper/tab'; |
||||
import {OpenprojectCommonModule} from 'core-app/modules/common/openproject-common.module'; |
||||
|
||||
import {GitHubTabComponent} from './github-tab/github-tab.component'; |
||||
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'; |
||||
|
||||
function displayable(work_package: WorkPackageResource): boolean { |
||||
return(!!work_package.github); |
||||
} |
||||
|
||||
export function initializeGithubIntegrationPlugin(injector:Injector) { |
||||
const hooks = injector.get<HookService>(HookService); |
||||
hooks.registerWorkPackageTab( |
||||
new Tab( |
||||
GitHubTabComponent, |
||||
I18n.t('js.github_integration.work_packages.tab_name'), |
||||
'github', |
||||
displayable |
||||
) |
||||
); |
||||
} |
||||
|
||||
|
||||
@NgModule({ |
||||
imports: [ |
||||
OpenprojectCommonModule |
||||
], |
||||
providers: [ |
||||
], |
||||
declarations: [ |
||||
GitHubTabComponent, |
||||
TabHeaderComponent, |
||||
TabPrsComponent, |
||||
GitActionsMenuDirective, |
||||
GitActionsMenuComponent, |
||||
], |
||||
exports: [ |
||||
GitHubTabComponent, |
||||
TabHeaderComponent, |
||||
TabPrsComponent, |
||||
GitActionsMenuDirective, |
||||
GitActionsMenuComponent, |
||||
] |
||||
}) |
||||
export class PluginModule { |
||||
constructor(injector:Injector) { |
||||
initializeGithubIntegrationPlugin(injector); |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
/*-- copyright |
||||
* OpenProject is an open source project management software. |
||||
* Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
*/ |
||||
|
||||
.github-pr-header |
||||
display: flex |
||||
flex-wrap: wrap-reverse |
||||
justify-content: flex-end |
||||
|
||||
border-bottom: 1px solid #ddd |
||||
|
||||
margin: 1.5rem 0 0.8rem 0 |
||||
padding: 0 0 0.5rem 0 |
||||
|
||||
.title |
||||
flex: 1 1 auto |
||||
border-bottom: 0 |
||||
margin: 0 |
||||
padding: 0 |
@ -0,0 +1,55 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {Component, Input} from '@angular/core'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; |
||||
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; |
||||
|
||||
@Component({ |
||||
selector: 'tab-header', |
||||
templateUrl: './tab-header.template.html', |
||||
styleUrls: [ |
||||
'./styles/tab-header.sass' |
||||
] |
||||
}) |
||||
export class TabHeaderComponent { |
||||
@Input() public workPackage:WorkPackageResource; |
||||
|
||||
public text = { |
||||
title: this.I18n.t('js.github_integration.tab_header.title'), |
||||
createPrButtonLabel: this.I18n.t('js.github_integration.tab_header.create_pr.label'), |
||||
createPrButtonDescription: this.I18n.t('js.github_integration.tab_header.create_pr.description'), |
||||
gitMenuLabel: this.I18n.t('js.github_integration.tab_header.copy_menu.label'), |
||||
gitMenuDescription: this.I18n.t('js.github_integration.tab_header.copy_menu.description'), |
||||
}; |
||||
|
||||
constructor(readonly PathHelper:PathHelperService, |
||||
readonly I18n:I18nService) { |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
<div class="github-pr-header"> |
||||
<h3 class="title"> |
||||
<op-icon icon-classes="button--icon icon-merge-branch"></op-icon> |
||||
{{text.title}} |
||||
</h3> |
||||
<ul class="toolbar-items hide-when-print"> |
||||
<li class="toolbar-item"> |
||||
<button class="button github-git-copy" |
||||
type="button" |
||||
[attr.aria-label]="text.gitMenuDescription" |
||||
gitActionsCopyDropdown |
||||
[gitActionsCopyDropdown-workPackage]="workPackage"> |
||||
<op-icon icon-classes="button--icon icon-console-light"></op-icon> |
||||
<span class="button--text" |
||||
[textContent]="text.gitMenuLabel" |
||||
aria-hidden="true"></span> |
||||
<op-icon icon-classes="button--icon icon-small icon-pulldown hidden-for-mobile"></op-icon> |
||||
</button> |
||||
</li> |
||||
</ul> |
||||
</div> |
@ -0,0 +1,50 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {Component, Input} from '@angular/core'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; |
||||
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; |
||||
|
||||
|
||||
@Component({ |
||||
selector: 'tab-prs', |
||||
templateUrl: './tab-prs.template.html' |
||||
}) |
||||
export class TabPrsComponent { |
||||
@Input() public workPackage:WorkPackageResource; |
||||
@Input() public pullRequests:WorkPackageResource[]; |
||||
|
||||
constructor(readonly PathHelper:PathHelperService, |
||||
readonly I18n:I18nService) { |
||||
} |
||||
|
||||
public getEmptyText() { |
||||
return this.I18n.t('js.github_integration.tab_prs.empty',{ wp_id: this.workPackage.id }); |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
<ng-container *ngIf="pullRequests.length === 0"> |
||||
<p [innerHTML]="getEmptyText()"></p> |
||||
</ng-container> |
@ -0,0 +1,21 @@ |
||||
module OpenProject::GithubIntegration |
||||
module Patches |
||||
module API |
||||
module WorkPackageRepresenter |
||||
module_function |
||||
|
||||
def extension |
||||
->(*) do |
||||
link :github, |
||||
cache_if: -> { current_user.allowed_to?(:show_github_content, represented.project) } do |
||||
{ |
||||
href: "#{work_package_path(id: represented.id)}/tabs/github", |
||||
title: "github" |
||||
} |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,115 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
require_relative '../support/pages/work_package_github_tab' |
||||
|
||||
describe 'Open the GitHub tab', type: :feature, js: true do |
||||
let(:user) do |
||||
FactoryBot.create(:user, |
||||
member_in_project: project, |
||||
member_through_role: role) |
||||
end |
||||
let(:role) do |
||||
FactoryBot.create(:role, |
||||
permissions: %i(view_work_packages |
||||
add_work_package_notes |
||||
show_github_content)) |
||||
end |
||||
let(:project) { FactoryBot.create :project } |
||||
let(:work_package) { FactoryBot.create(:work_package, project: project) } |
||||
let(:github_tab) { Pages::GitHubTab.new(work_package.id) } |
||||
|
||||
shared_examples_for "a github tab" do |
||||
before do |
||||
login_as(user) |
||||
work_package |
||||
end |
||||
|
||||
# compares the clipboard content by drafting a new comment, pressing ctrl+v and |
||||
# comparing the pasted content against the provided text |
||||
def expect_clipboard_content(text) |
||||
work_package_page.switch_to_tab(tab: 'activity') |
||||
|
||||
work_package_page.trigger_edit_comment |
||||
work_package_page.update_comment(' ') # ensure the comment editor is fully loaded |
||||
github_tab.paste_clipboard_content |
||||
expect(work_package_page.add_comment_container).to have_content(text) |
||||
|
||||
work_package_page.switch_to_tab(tab: 'github') |
||||
end |
||||
|
||||
it 'show the github tab when the user is allowed to see it' do |
||||
work_package_page.visit! |
||||
work_package_page.switch_to_tab(tab: 'github') |
||||
expect(page).to have_content('There are no pull requests') |
||||
expect(page).to have_content("Link an existing PR by using the code OP##{work_package.id}") |
||||
|
||||
github_tab.git_actions_menu_button.click |
||||
github_tab.git_actions_copy_button.click |
||||
expect(page).to have_text('Copied!') |
||||
expect_clipboard_content("#{work_package.type.name.downcase}/#{work_package.id}-workpackage-no-#{work_package.id}") |
||||
end |
||||
|
||||
describe 'when the user does not have the permissions to see the github tab' do |
||||
let(:role) do |
||||
FactoryBot.create(:role, |
||||
permissions: %i(view_work_packages |
||||
add_work_package_notes)) |
||||
end |
||||
|
||||
it 'does not show the github tab' do |
||||
work_package_page.visit! |
||||
|
||||
github_tab.expect_tab_not_present |
||||
end |
||||
end |
||||
|
||||
describe 'when the github integration is not enabled for the project' do |
||||
let(:project) { FactoryBot.create(:project, disable_modules: 'github') } |
||||
|
||||
it 'does not show the github tab' do |
||||
work_package_page.visit! |
||||
|
||||
github_tab.expect_tab_not_present |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'work package full view' do |
||||
let(:work_package_page) { Pages::FullWorkPackage.new(work_package) } |
||||
|
||||
it_behaves_like 'a github tab' |
||||
end |
||||
|
||||
describe 'work package split view' do |
||||
let(:work_package_page) { Pages::SplitWorkPackage.new(work_package) } |
||||
|
||||
it_behaves_like 'a github tab' |
||||
end |
||||
end |
@ -0,0 +1,68 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'rbconfig' |
||||
require 'support/pages/page' |
||||
|
||||
module Pages |
||||
class GitHubTab < Page |
||||
attr_reader :work_package_id |
||||
|
||||
def initialize(work_package_id) |
||||
super() |
||||
@work_package_id = work_package_id |
||||
end |
||||
|
||||
def path |
||||
"/work_packages/#{work_package_id}/tabs/github" |
||||
end |
||||
|
||||
def git_actions_menu_button |
||||
find('.github-git-copy:not([disabled])', text: 'Git') |
||||
end |
||||
|
||||
def git_actions_copy_button |
||||
find('.git-actions-menu .copy-button:not([disabled])') |
||||
end |
||||
|
||||
def paste_clipboard_content |
||||
meta_key = osx? ? :command : :control |
||||
page.send_keys(meta_key, 'v') |
||||
end |
||||
|
||||
def expect_tab_not_present |
||||
expect(page).not_to have_selector('.tabrow li', text: 'GITHUB') |
||||
end |
||||
|
||||
private |
||||
|
||||
def osx? |
||||
RbConfig::CONFIG['host_os'] =~ /darwin/ |
||||
end |
||||
end |
||||
end |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.7 KiB |
Loading…
Reference in new issue