working on ManageTriggerGithostComponent

This commit is contained in:
alecmerdler 2017-02-18 01:45:00 -08:00 committed by Joseph Schorr
parent 39c18eb216
commit 14222be9fe
7 changed files with 554 additions and 2 deletions

View file

@ -12,7 +12,7 @@ export class ManageTriggerCustomGitComponent implements ng.IComponentController
// FIXME: Use one-way data binding
@Input('=') public trigger: {config: any};
@Output() public activateTrigger: any;
@Output() public activateTrigger: (trigger: {config: any}) => void;
private config: any = {};
private currentState: any | null;

View file

@ -11,7 +11,6 @@ angular.module('quay').directive('manageTriggerGithost', function () {
scope: {
'repository': '=repository',
'trigger': '=trigger',
'activateTrigger': '&activateTrigger'
},
controller: function($scope, $element, ApiService, TableService, TriggerService, RolesService) {
@ -52,6 +51,7 @@ angular.module('quay').directive('manageTriggerGithost', function () {
};
$scope.createTrigger = function() {
console.log($scope.local);
var config = {
'build_source': $scope.local.selectedRepository.full_name,
'subdir': $scope.local.dockerfilePath.substr(1) // Remove starting /

View file

@ -0,0 +1,355 @@
<div class="manage-trigger-githost-element manage-trigger-control">
<div class="linear-workflow"
workflow-state="$ctrl.currentState"
done-title="Create Trigger"
workflow-complete="$ctrl.createTrigger()">
<!-- Section: Namespace -->
<div class="linear-workflow-section row"
section-id="namespace"
section-title="{{ 'Select ' + $ctrl.namespaceTitle }}"
section-valid="local.selectedNamespace">
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.namespaces">
<h3>Select {{ $ctrl.namespaceTitle }}</h3>
<strong>
Please select the {{ $ctrl.namespaceTitle }} under which the repository lives
</strong>
<div class="co-top-bar">
<div class="co-filter-box">
<span class="page-controls"
total-count="$ctrl.local.orderedNamespaces.entries.length"
current-page="$ctrl.local.namespaceOptions.page"
page-size="$ctrl.namespacesPerPage"></span>
<input class="form-control" type="text"
ng-model="local.namespaceOptions.filter"
placeholder="Filter {{ $ctrl.namespaceTitle }}s...">
</div>
</div>
<table class="co-table" style="margin-top: 20px;">
<thead>
<td class="checkbox-col"></td>
<td ng-class="$ctrl.TableService.tablePredicateClass('id', $ctrl.local.namespaceOptions.predicate, $ctrl.local.namespaceOptions.reverse)">
<a ng-click="$ctrl.TableService.orderBy('id', $ctrl.local.namespaceOptions)">{{ $ctrl.namespaceTitle }}</a>
</td>
<td ng-class="$ctrl.TableService.tablePredicateClass('score', $ctrl.local.namespaceOptions.predicate, $ctrl.local.namespaceOptions.reverse)"
class="importance-col hidden-xs">
<a ng-click="$ctrl.TableService.orderBy('score', $ctrl.local.namespaceOptions)">Importance</a>
</td>
</thead>
<tr class="co-checkable-row"
ng-repeat="namespace in $ctrl.local.orderedNamespaces.visibleEntries | slice:($ctrl.namespacesPerPage * $ctrl.local.namespaceOptions.page):($ctrl.namespacesPerPage * ($ctrl.local.namespaceOptions.page + 1))"
ng-class="$ctrl.local.selectedNamespace == $ctrl.namespace ? 'checked' : ''"
bindonce>
<td>
<input type="radio"
ng-model="$ctrl.local.selectedNamespace"
ng-value="$ctrl.namespace">
</td>
<td>
<img class="namespace-avatar" ng-src="{{ $ctrl.namespace.avatar_url }}">
<span class="anchor" href="{{ $ctrl.namespace.url }}" is-text-only="!$ctrl.namespace.url">{{ $ctrl.namespace.id }}</span>
</td>
<td class="importance-col hidden-xs">
<span class="strength-indicator" value="::$ctrl.namespace.score" maximum="::$ctrl.local.maxScore"
log-base="10"></span>
</td>
</tr>
</table>
<div class="empty" ng-if="$ctrl.local.namespaces.length && !$ctrl.local.orderedNamespaces.entries.length"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching {{ $ctrl.namespaceTitle }} found.</div>
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
</div>
</div>
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!$ctrl.local.namespaces">
<span class="cor-loader-inline"></span> Retrieving {{ $ctrl.namespaceTitle }}s
</div>
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col" ng-show="$ctrl.local.namespaces">
<p>
<span class="registry-name"></span> has been granted access to read and view these {{ $ctrl.namespaceTitle }}s.
</p>
<p>
Don't see an expected {{ $ctrl.namespaceTitle }} here? Please make sure third-party access is enabled for <span class="registry-name"></span> under that {{ $ctrl.namespaceTitle }}.
</p>
</div>
</div><!-- /Section: Namespace -->
<!-- Section: Repository -->
<div class="linear-workflow-section row"
section-id="repo"
section-title="Select Repository"
section-valid="$ctrl.local.selectedRepository">
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.repositories">
<h3>Select Repository</h3>
<strong>
Select a repository in
<img class="namespace-avatar" ng-src="{{ $ctrl.local.selectedNamespace.avatar_url }}">
{{ $ctrl.local.selectedNamespace.id }}
</strong>
<div class="co-top-bar">
<div class="co-filter-box">
<span class="page-controls"
total-count="$ctrl.local.orderedRepositories.entries.length"
current-page="$ctrl.local.repositoryOptions.page"
page-size="$ctrl.repositoriesPerPage"></span>
<input class="form-control" type="text"
ng-model="$ctrl.local.repositoryOptions.filter"
placeholder="Filter repositories...">
<div class="filter-options">
<label>
<input type="checkbox"
ng-model="$ctrl.local.repositoryOptions.hideStale">
Hide stale repositories
</label>
</div>
</div>
</div>
<table class="co-table" style="margin-top: 20px;">
<thead>
<td class="checkbox-col"></td>
<td ng-class="$ctrl.TableService.tablePredicateClass('name', $ctrl.local.repositoryOptions.predicate, $ctrl.local.repositoryOptions.reverse)" class="nowrap-col">
<a ng-click="$ctrl.TableService.orderBy('name', $ctrl.local.repositoryOptions)">Repository Name</a>
</td>
<td ng-class="$ctrl.TableService.tablePredicateClass('last_updated_datetime', $ctrl.local.repositoryOptions.predicate, $ctrl.local.repositoryOptions.reverse)"
class="last-updated-col nowrap-col">
<a ng-click="$ctrl.TableService.orderBy('last_updated_datetime', $ctrl.local.namespaceOptions)">Last Updated</a>
</td>
<td class="hidden-xs">Description</td>
</thead>
<tr class="co-checkable-row"
ng-repeat="repository in $ctrl.local.orderedRepositories.visibleEntries | slice:($ctrl.repositoriesPerPage * $ctrl.local.repositoryOptions.page):($ctrl.repositoriesPerPage * ($ctrl.local.repositoryOptions.page + 1))"
ng-class="$ctrl.local.selectedRepository == repository ? 'checked' : ''"
bindonce>
<td>
<span ng-if="!repository.has_admin_permissions">
<i class="fa fa-exclamation-triangle"
data-title="Admin access is required to add the webhook trigger to this repository" bs-tooltip></i>
</span>
<input type="radio" ng-model="local.selectedRepository" ng-value="repository"
ng-if="repository.has_admin_permissions">
</td>
<td class="nowrap-col">
<i class="service-icon fa {{ $ctrl.getTriggerIcon() }}"></i>
<span class="anchor" href="{{ repository.url }}" is-text-only="!repository.url">{{ repository.name }}</span>
</td>
<td class="last-updated-col nowrap-col">
<span am-time-ago="repository.last_updated_datetime"></span>
</td>
<td class="hidden-xs">
<span ng-if="repository.description">{{ repository.description }}</span>
<span class="empty-description" ng-if="!repository.description">(None)</span>
</td>
</tr>
</table>
<div class="empty" ng-if="$ctrl.local.repositories.length && !$ctrl.local.orderedRepositories.entries.length"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching repositories found.</div>
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
</div>
</div>
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!$ctrl.local.repositories">
<span class="cor-loader-inline"></span> Retrieving repositories
</div>
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col" ng-show="$ctrl.local.repositories">
<p>
A webhook will be added to the selected repository in order to detect when new commits are made.
</p>
<p>
Don't see an expected repository here? Please make sure you have admin access on that repository.
</p>
</div>
</div><!-- /Section: Repository -->
<!-- Section: Trigger Options -->
<div class="linear-workflow-section row"
section-id="triggeroptions"
section-title="Configure Trigger"
section-valid="$ctrl.local.triggerOptions">
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.repositoryRefs">
<h3>Configure Trigger</h3>
<strong>
Configure trigger options for
<img class="namespace-avatar" ng-src="{{ $ctrl.local.selectedNamespace.avatar_url }}">
{{ $ctrl.local.selectedNamespace.id }}/{{ $ctrl.local.selectedRepository.name }}
</strong>
<div class="radio" style="margin-top: 20px;">
<label>
<input type="radio" name="optionRadio" ng-model="$ctrl.local.triggerOptions.hasBranchTagFilter" ng-value="false">
<div class="title">Trigger for all branches and tags <span class="weak">(default)</span></div>
<div class="description">Build a container image for each commit across all branches and tags</div>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="optionRadio" ng-model="$ctrl.local.triggerOptions.hasBranchTagFilter" ng-value="true">
<div class="title">Trigger only on branches and tags matching a regular expression</div>
<div class="description">Only build container images for a subset of branches and/or tags.</div>
<div class="extended" ng-if="$ctrl.local.triggerOptions.hasBranchTagFilter">
<table>
<tr>
<td style="white-space: nowrap;">Regular Expression:</td>
<td>
<input type="text" class="form-control" ng-model="local.triggerOptions.branchTagFilter" required>
<div class="description">Examples: heads/master, tags/tagname, heads/.+</div>
<regex-match-view
items="$ctrl.local.repositoryFullRefs"
regex="$ctrl.local.triggerOptions.branchTagFilter"
ng-if="$ctrl.local.triggerOptions.branchTagFilter"></regex-match-view>
</td>
</tr>
</table>
</div>
</label>
</div>
</div>
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!$ctrl.local.repositoryRefs">
<span class="cor-loader-inline"></span> Retrieving repository refs
</div>
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col">
<p>Do you want to build a new container image for commits across all branches and tags, or limit to a subset?</p>
<p>For example, if you use release branches instead of <code>master</code> for building versions of your software, you can configure the trigger to only build images for these branches.</p>
<p>All images built will be tagged with the name of the branch or tag whose change invoked the trigger</p>
</div>
</div><!-- /Section: Trigger Options -->
<!-- Section: Dockerfile Location -->
<div class="linear-workflow-section row"
section-id="dockerfilelocation"
section-title="Select Dockerfile"
section-valid="$ctrl.local.hasValidDockerfilePath">
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.dockerfileLocations.status == 'error'">
<div class="co-alert co-alert-warning">
{{ $ctrl.local.dockerfileLocations.message }}
</div>
</div>
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.dockerfileLocations.status == 'success'">
<h3>Select Dockerfile</h3>
<strong>
Please select the location of the Dockerfile to build when this trigger is invoked {{ $ctrl.local.paths }}
</strong>
<dockerfile-path-select
current-path="$ctrl.local.dockerfilePath"
paths="$ctrl.local.dockerfileLocations.subdir"
supports-full-listing="true"
is-valid-path="$ctrl.local.hasValidDockerfilePath"></dockerfile-path-select>
</div>
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!$ctrl.local.dockerfileLocations">
<span class="cor-loader-inline"></span> Retrieving Dockerfile locations
</div>
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col">
<p>Please select the location containing the Dockerfile to be built.</p>
<p>The build context will start at the location selected.</p>
</div>
</div><!-- /Section: Dockerfile Location -->
<!-- Section: Verification and Robot Account -->
<div class="linear-workflow-section row"
section-id="verification"
section-title="Confirm"
section-valid="$ctrl.local.triggerAnalysis.status != 'error' && ($ctrl.local.triggerAnalysis.status != 'requiresrobot' || $ctrl.local.robotAccount != null)">
<!-- Error -->
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.triggerAnalysis.status == 'error'">
<h3 class="error"><i class="fa fa-exclamation-circle"></i> Verification Error</h3>
<strong>
There was an error when verifying the state of <img class="namespace-avatar" ng-src="{{ $ctrl.local.selectedNamespace.avatar_url }}">
{{ $ctrl.local.selectedNamespace.id }}/{{ $ctrl.local.selectedRepository.name }}
</strong>
{{ $ctrl.local.triggerAnalysis.message }}
</div>
<!-- Warning -->
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.triggerAnalysis.status == 'warning'">
<h3 class="warning"><i class="fa fa-exclamation-triangle"></i> Verification Warning</h3>
{{ $ctrl.local.triggerAnalysis.message }}
</div>
<!-- Public base -->
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.triggerAnalysis.status == 'publicbase'">
<h3 class="success"><i class="fa fa-check-circle"></i> Ready to go!</h3>
<strong>Click "Create Trigger" to complete setup of this build trigger</strong>
</div>
<!-- Requires robot and is not admin -->
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.triggerAnalysis.status == 'requiresrobot' && !$ctrl.local.triggerAnalysis.is_admin">
<h3>Robot Account Required</h3>
<p>The selected Dockerfile in the selected repository depends upon a private base image</p>
<p>A robot account with access to the base image is required to setup this trigger, but you are not the administrator of this namespace.</p>
<p>Administrative access is required to continue to ensure security of the robot credentials.</p>
</div>
<!-- Requires robot and is admin -->
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="$ctrl.local.triggerAnalysis.status == 'requiresrobot' && $ctrl.local.triggerAnalysis.is_admin">
<h3>Select Robot Account</h3>
<strong>
The selected Dockerfile in the selected repository depends upon a private base image. Select a robot account with access:
</strong>
<div class="co-top-bar">
<div class="co-filter-box">
<span class="page-controls"
total-count="$ctrl.local.orderedRobotAccounts.entries.length"
current-page="$ctrl.local.robotOptions.page"
page-size="$ctrl.robotsPerPage"></span>
<input class="form-control" type="text" ng-model="$ctrl.local.robotOptions.filter" placeholder="Filter robot accounts...">
</div>
</div>
<table class="co-table" style="margin-top: 20px;">
<thead>
<td class="checkbox-col"></td>
<td ng-class="$ctrl.TableService.tablePredicateClass('name', $ctrl.local.robotOptions.predicate, $ctrl.local.robotOptions.reverse)">
<a ng-click="$ctrl.TableService.orderBy('name', $ctrl.local.robotOptions)">Robot Account</a>
</td>
<td ng-class="$ctrl.TableService.tablePredicateClass('can_read', $ctrl.local.robotOptions.predicate, $ctrl.local.robotOptions.reverse)">
<a ng-click="$ctrl.TableService.orderBy('can_read', $ctrl.local.robotOptions)">Has Read Access</a>
</td>
</thead>
<tr class="co-checkable-row"
ng-repeat="robot in $ctrl.local.orderedRobotAccounts.visibleEntries | slice:($ctrl.robotsPerPage * $ctrl.local.namespaceOptions.page):(robotsPerPage * ($ctrl.local.robotOptions.page + 1))"
ng-class="$ctrl.local.robotAccount == robot ? 'checked' : ''"
bindonce>
<td>
<input type="radio" ng-model="$ctrl.local.robotAccount" ng-value="robot">
</td>
<td>
<span class="entity-reference" entity="robot"></span>
</td>
<td>
<span ng-if="robot.can_read" class="success">Can Read</span>
<span ng-if="!robot.can_read">Read access will be added if selected</span>
</td>
</tr>
</table>
<div class="empty" ng-if="$ctrl.local.triggerAnalysis.robots.length && !$ctrl.local.orderedRobotAccounts.entries.length"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching robot accounts found.</div>
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
</div>
</div>
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col"
ng-if="$ctrl.local.triggerAnalysis.status == 'requiresrobot' && $ctrl.local.triggerAnalysis.is_admin">
<p>The Dockerfile you selected utilizes a private base image.</p>
<p>In order for the <span class="registry-name"></span> to pull the base image during the build process, a robot account with access must be selected.</p>
<p>Robot accounts that already have access to this base image are listed first. If you select a robot account that does not currently have access, read permission will be granted to that robot account on trigger creation.</p>
</div>
</div><!-- /Section: Robot Account -->
</div>

View file

@ -0,0 +1,47 @@
import { ManageTriggerGithostComponent, Local, Trigger } from './manage-trigger-githost.component';
describe("ManageTriggerGithostComponent", () => {
var component: ManageTriggerGithostComponent;
var apiServiceMock: any;
var tableServiceMock: any;
var triggerServiceMock: any;
var rolesServiceMock: any;
var repository: any;
var trigger: Trigger;
beforeEach(() => {
apiServiceMock = jasmine.createSpyObj('apiServiceMock', ['listTriggerBuildSourceNamespaces']);
tableServiceMock = jasmine.createSpyObj('tableServiceMock', ['buildOrderedItems']);
triggerServiceMock = jasmine.createSpyObj('triggerServiceMock', ['getIcon']);
rolesServiceMock = jasmine.createSpyObj('rolesServiceMock', ['setRepositoryRole']);
component = new ManageTriggerGithostComponent(apiServiceMock, tableServiceMock, triggerServiceMock, rolesServiceMock);
trigger = {service: "serviceMock", id: 1};
component.trigger = trigger;
});
describe("constructor", () => {
});
describe("$onInit", () => {
});
describe("$onChanges", () => {
});
describe("getTriggerIcon", () => {
it("calls trigger service to get icon", () => {
component.getTriggerIcon();
expect(triggerServiceMock.getIcon.calls.argsFor(0)[0]).toEqual(component.trigger.service);
});
});
describe("createTrigger", () => {
});
});

View file

@ -0,0 +1,148 @@
import { Input, Output, Component } from 'angular-ts-decorators';
@Component({
selector: 'manageTriggerGithost',
templateUrl: '/static/js/directives/ui/manage-trigger-githost/manage-trigger-githost.component.html'
})
export class ManageTriggerGithostComponent implements ng.IComponentController {
@Input('=') public repository: any;
@Input('=') public trigger: Trigger;
@Output() public activateTrigger: (trigger: {config: any, pull_robot: any}) => void;
private config: any;
private local: Local = {
namespaceOptions: {
filter: '',
predicate: 'score',
reverse: false,
page: 0
},
repositoryOptions: {
filter: '',
predicate: 'score',
reverse: false,
page: 0,
hideStale: true
},
robotOptions: {
filter: '',
predicate: 'score',
reverse: false,
page: 0
}
};
private currentState: any | null;
private namespacesPerPage: number = 10;
private repositoriesPerPage: number = 10;
private robotsPerPage: number = 10;
constructor(private ApiService: any,
private TableService: any,
private TriggerService: any,
private RolesService: any) {
}
public $onInit(): void {
}
public $onChanges(changes: ng.IOnChangesObject): void {
}
public getTriggerIcon(): any {
return this.TriggerService.getIcon(this.trigger.service);
}
public createTrigger(): void {
var config: any = {
build_source: this.local.selectedRepository.full_name,
subdir: this.local.dockerfilePath.substr(1) // Remove starting /
};
if (this.local.triggerOptions.hasBranchTagFilter &&
this.local.triggerOptions.branchTagFilter) {
config['branchtag_regex'] = this.local.triggerOptions.branchTagFilter;
}
var activate = () => {
this.activateTrigger({'config': config, 'pull_robot': this.local.robotAccount});
};
if (this.local.robotAccount) {
if (this.local.robotAccount.can_read) {
activate();
} else {
// Add read permission onto the base repository for the robot and then activate the
// trigger.
var robot_name = this.local.robotAccount.name;
this.RolesService.setRepositoryRole(this.repository, 'read', 'robot', robot_name, activate);
}
} else {
activate();
}
}
private buildOrderedNamespaces(): void {
}
private loadNamespaces(): void {
}
private buildOrderedRepositories(): void {
}
private loadRepositories(): void {
}
private loadRepositoryRefs(repository: any): void {
}
private loadDockerfileLocations(repository: any): void {
}
private buildOrderedRobotAccounts(): void {
}
private checkDockerfilePath(repository: any, path: string): void {
}
}
export type Local = {
namespaceOptions: {
filter: string;
predicate: string;
reverse: boolean;
page: number;
};
repositoryOptions: {
filter: string;
predicate: string;
reverse: boolean;
page: number;
hideStale: boolean;
};
robotOptions: {
filter: string;
predicate: string;
reverse: boolean;
page: number;
};
}
export type Trigger = {
id: number;
service: any;
}

View file

@ -8,6 +8,7 @@ import { NgModule } from "angular-ts-decorators";
import { QuayRoutes } from "./quay-routes.module";
import { DockerfilePathSelectComponent } from './directives/ui/dockerfile-path-select/dockerfile-path-select.component';
import { ManageTriggerCustomGitComponent } from './directives/ui/manage-trigger-custom-git/manage-trigger-custom-git.component';
import { ManageTriggerGithostComponent } from './directives/ui/manage-trigger-githost/manage-trigger-githost.component';
var quayDependencies: any[] = [
@ -57,6 +58,7 @@ if (INJECTED_CONFIG && INJECTED_CONFIG.RECAPTCHA_SITE_KEY) {
RegexMatchViewComponent,
DockerfilePathSelectComponent,
ManageTriggerCustomGitComponent,
ManageTriggerGithostComponent,
],
providers: [
ViewArrayImpl,

Binary file not shown.