converted ManageTriggerGithostComponent to TypeScript

This commit is contained in:
alecmerdler 2017-02-19 18:17:12 -08:00 committed by Joseph Schorr
parent 14222be9fe
commit b1516193a5
13 changed files with 238 additions and 665 deletions

View file

@ -1,306 +0,0 @@
/**
* An element which displays the setup and management workflow for a normal SCM git trigger.
*/
angular.module('quay').directive('manageTriggerGithost', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/manage-trigger-githost.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'repository': '=repository',
'trigger': '=trigger',
'activateTrigger': '&activateTrigger'
},
controller: function($scope, $element, ApiService, TableService, TriggerService, RolesService) {
$scope.TableService = TableService;
$scope.config = {};
$scope.local = {};
$scope.currentState = null;
$scope.namespacesPerPage = 10;
$scope.repositoriesPerPage = 10;
$scope.robotsPerPage = 10;
$scope.local.namespaceOptions = {
'filter': '',
'predicate': 'score',
'reverse': false,
'page': 0
};
$scope.local.repositoryOptions = {
'filter': '',
'predicate': 'last_updated',
'reverse': false,
'page': 0,
'hideStale': true
};
$scope.local.robotOptions = {
'filter': '',
'predicate': 'can_read',
'reverse': false,
'page': 0
};
$scope.getTriggerIcon = function() {
return TriggerService.getIcon($scope.trigger.service);
};
$scope.createTrigger = function() {
console.log($scope.local);
var config = {
'build_source': $scope.local.selectedRepository.full_name,
'subdir': $scope.local.dockerfilePath.substr(1) // Remove starting /
};
if ($scope.local.triggerOptions.hasBranchTagFilter &&
$scope.local.triggerOptions.branchTagFilter) {
config['branchtag_regex'] = $scope.local.triggerOptions.branchTagFilter;
}
var activate = function() {
$scope.activateTrigger({'config': config, 'pull_robot': $scope.local.robotAccount});
};
if ($scope.local.robotAccount) {
if ($scope.local.robotAccount.can_read) {
activate();
} else {
// Add read permission onto the base repository for the robot and then activate the
// trigger.
var robot_name = $scope.local.robotAccount.name;
RolesService.setRepositoryRole($scope.repository, 'read', 'robot', robot_name, activate);
}
} else {
activate();
}
};
var buildOrderedNamespaces = function() {
if (!$scope.local.namespaces) {
return;
}
var namespaces = $scope.local.namespaces || [];
$scope.local.orderedNamespaces = TableService.buildOrderedItems(namespaces,
$scope.local.namespaceOptions,
['id'],
['score'])
$scope.local.maxScore = 0;
namespaces.forEach(function(namespace) {
$scope.local.maxScore = Math.max(namespace.score, $scope.local.maxScore);
});
};
var loadNamespaces = function() {
$scope.local.namespaces = null;
$scope.local.selectedNamespace = null;
$scope.local.orderedNamespaces = null;
$scope.local.selectedRepository = null;
$scope.local.orderedRepositories = null;
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': $scope.trigger.id
};
ApiService.listTriggerBuildSourceNamespaces(null, params).then(function(resp) {
$scope.local.namespaces = resp['namespaces'];
$scope.local.repositories = null;
buildOrderedNamespaces();
}, ApiService.errorDisplay('Could not retrieve the list of ' + $scope.namespaceTitle))
};
var buildOrderedRepositories = function() {
if (!$scope.local.repositories) {
return;
}
var repositories = $scope.local.repositories || [];
repositories.forEach(function(repository) {
repository['last_updated_datetime'] = new Date(repository['last_updated'] * 1000);
});
if ($scope.local.repositoryOptions.hideStale) {
var existingRepositories = repositories;
repositories = repositories.filter(function(repository) {
var older_date = moment(repository['last_updated_datetime']).add(1, 'months');
return !moment().isAfter(older_date);
});
if (existingRepositories.length > 0 && repositories.length == 0) {
repositories = existingRepositories;
}
}
$scope.local.orderedRepositories = TableService.buildOrderedItems(repositories,
$scope.local.repositoryOptions,
['name', 'description'],
[]);
};
var loadRepositories = function(namespace) {
$scope.local.repositories = null;
$scope.local.selectedRepository = null;
$scope.local.repositoryRefs = null;
$scope.local.triggerOptions = {
'hasBranchTagFilter': false
};
$scope.local.orderedRepositories = null;
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': $scope.trigger.id
};
var data = {
'namespace': namespace.id
};
ApiService.listTriggerBuildSources(data, params).then(function(resp) {
if (namespace == $scope.local.selectedNamespace) {
$scope.local.repositories = resp['sources'];
buildOrderedRepositories();
}
}, ApiService.errorDisplay('Could not retrieve repositories'));
};
var loadRepositoryRefs = function(repository) {
$scope.local.repositoryRefs = null;
$scope.local.triggerOptions = {
'hasBranchTagFilter': false
};
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': $scope.trigger.id,
'field_name': 'refs'
};
var config = {
'build_source': repository.full_name
};
ApiService.listTriggerFieldValues(config, params).then(function(resp) {
if (repository == $scope.local.selectedRepository) {
$scope.local.repositoryRefs = resp['values'];
$scope.local.repositoryFullRefs = resp['values'].map(function(ref) {
var kind = ref.kind == 'branch' ? 'heads' : 'tags';
var icon = ref.kind == 'branch' ? 'fa-code-fork' : 'fa-tag';
return {
'value': kind + '/' + ref.name,
'icon': icon,
'title': ref.name
};
});
}
}, ApiService.errorDisplay('Could not retrieve repository refs'));
};
var loadDockerfileLocations = function(repository) {
$scope.local.dockerfilePath = null;
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': $scope.trigger.id
};
var config = {
'build_source': repository.full_name
};
ApiService.listBuildTriggerSubdirs(config, params).then(function(resp) {
if (repository == $scope.local.selectedRepository) {
$scope.local.dockerfileLocations = resp;
}
}, ApiService.errorDisplay('Could not retrieve Dockerfile locations'));
};
var buildOrderedRobotAccounts = function() {
if (!$scope.local.triggerAnalysis || !$scope.local.triggerAnalysis.robots) {
return;
}
var robots = $scope.local.triggerAnalysis.robots;
$scope.local.orderedRobotAccounts = TableService.buildOrderedItems(robots,
$scope.local.robotOptions,
['name'],
[]);
};
var checkDockerfilePath = function(repository, path) {
$scope.local.triggerAnalysis = null;
$scope.local.robotAccount = null;
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': $scope.trigger.id
};
var config = {
'build_source': repository.full_name,
'subdir': path.substr(1)
};
var data = {
'config': config
};
ApiService.analyzeBuildTrigger(data, params).then(function(resp) {
$scope.local.triggerAnalysis = resp;
buildOrderedRobotAccounts();
}, ApiService.errorDisplay('Could not analyze trigger'));
};
$scope.$watch('trigger', function(trigger) {
if (trigger && $scope.repository) {
$scope.config = trigger['config'] || {};
$scope.namespaceTitle = 'organization';
$scope.local.selectedNamespace = null;
loadNamespaces();
}
});
$scope.$watch('local.selectedNamespace', function(namespace) {
if (namespace) {
loadRepositories(namespace);
}
});
$scope.$watch('local.selectedRepository', function(repository) {
if (repository) {
loadRepositoryRefs(repository);
loadDockerfileLocations(repository);
}
});
$scope.$watch('local.dockerfilePath', function(path) {
if (path && $scope.local.selectedRepository) {
checkDockerfilePath($scope.local.selectedRepository, path);
}
});
$scope.$watch('local.namespaceOptions.predicate', buildOrderedNamespaces);
$scope.$watch('local.namespaceOptions.reverse', buildOrderedNamespaces);
$scope.$watch('local.namespaceOptions.filter', buildOrderedNamespaces);
$scope.$watch('local.repositoryOptions.predicate', buildOrderedRepositories);
$scope.$watch('local.repositoryOptions.reverse', buildOrderedRepositories);
$scope.$watch('local.repositoryOptions.filter', buildOrderedRepositories);
$scope.$watch('local.repositoryOptions.hideStale', buildOrderedRepositories);
$scope.$watch('local.robotOptions.predicate', buildOrderedRobotAccounts);
$scope.$watch('local.robotOptions.reverse', buildOrderedRobotAccounts);
$scope.$watch('local.robotOptions.filter', buildOrderedRobotAccounts);
}
};
return directiveDefinitionObject;
});

View file

@ -8,7 +8,7 @@
<div class="linear-workflow-section row"
section-id="namespace"
section-title="{{ 'Select ' + $ctrl.namespaceTitle }}"
section-valid="local.selectedNamespace">
section-valid="$ctrl.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>
@ -22,21 +22,21 @@
current-page="$ctrl.local.namespaceOptions.page"
page-size="$ctrl.namespacesPerPage"></span>
<input class="form-control" type="text"
ng-model="local.namespaceOptions.filter"
ng-model="$ctrl.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>
<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"
@ -46,14 +46,14 @@
<td>
<input type="radio"
ng-model="$ctrl.local.selectedNamespace"
ng-value="$ctrl.namespace">
ng-value="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>
<img class="namespace-avatar" ng-src="{{ namespace.avatar_url }}">
<span class="anchor" href="{{ namespace.url }}" is-text-only="!namespace.url">{{ namespace.id }}</span>
</td>
<td class="importance-col hidden-xs">
<span class="strength-indicator" value="::$ctrl.namespace.score" maximum="::$ctrl.local.maxScore"
<span class="strength-indicator" value="::namespace.score" maximum="::$ctrl.local.maxScore"
log-base="10"></span>
</td>
</tr>
@ -133,7 +133,7 @@
<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"
<input type="radio" ng-model="$ctrl.local.selectedRepository" ng-value="repository"
ng-if="repository.has_admin_permissions">
</td>
<td class="nowrap-col">
@ -200,7 +200,7 @@
<tr>
<td style="white-space: nowrap;">Regular Expression:</td>
<td>
<input type="text" class="form-control" ng-model="local.triggerOptions.branchTagFilter" required>
<input type="text" class="form-control" ng-model="$ctrl.local.triggerOptions.branchTagFilter" required>
<div class="description">Examples: heads/master, tags/tagname, heads/.+</div>
<regex-match-view
items="$ctrl.local.repositoryFullRefs"
@ -238,7 +238,7 @@
<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 }}
Please select the location of the Dockerfile to build when this trigger is invoked
</strong>
<dockerfile-path-select

View file

@ -9,16 +9,22 @@ describe("ManageTriggerGithostComponent", () => {
var rolesServiceMock: any;
var repository: any;
var trigger: Trigger;
var $scope: ng.IScope;
beforeEach(() => {
beforeEach(inject(($injector: ng.auto.IInjectorService) => {
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);
$scope = $injector.get('$rootScope');
component = new ManageTriggerGithostComponent(apiServiceMock,
tableServiceMock,
triggerServiceMock,
rolesServiceMock,
$scope);
trigger = {service: "serviceMock", id: 1};
component.trigger = trigger;
});
}));
describe("constructor", () => {

View file

@ -1,6 +1,10 @@
import { Input, Output, Component } from 'angular-ts-decorators';
import * as moment from 'moment';
/**
* A component that lets the user set up a build trigger for a public Git repository host service.
*/
@Component({
selector: 'manageTriggerGithost',
templateUrl: '/static/js/directives/ui/manage-trigger-githost/manage-trigger-githost.component.html'
@ -11,7 +15,7 @@ export class ManageTriggerGithostComponent implements ng.IComponentController {
@Input('=') public trigger: Trigger;
@Output() public activateTrigger: (trigger: {config: any, pull_robot: any}) => void;
private config: any;
private local: Local = {
private local: any = {
namespaceOptions: {
filter: '',
predicate: 'score',
@ -36,16 +40,65 @@ export class ManageTriggerGithostComponent implements ng.IComponentController {
private namespacesPerPage: number = 10;
private repositoriesPerPage: number = 10;
private robotsPerPage: number = 10;
private namespaceTitle: string;
private namespace: any;
constructor(private ApiService: any,
private TableService: any,
private TriggerService: any,
private RolesService: any) {
private RolesService: any,
private $scope: ng.IScope) {
// FIXME: Here binding methods to class context in order to pass them as arguments to $scope.$watch
this.buildOrderedNamespaces = this.buildOrderedNamespaces.bind(this);
this.loadNamespaces = this.loadNamespaces.bind(this);
this.buildOrderedRepositories = this.buildOrderedRepositories.bind(this);
this.loadRepositories = this.loadRepositories.bind(this);
this.loadRepositoryRefs = this.loadRepositoryRefs.bind(this);
this.buildOrderedRobotAccounts = this.buildOrderedRobotAccounts.bind(this);
this.loadDockerfileLocations = this.loadDockerfileLocations.bind(this);
this.checkDockerfilePath = this.checkDockerfilePath.bind(this);
}
public $onInit(): void {
// TODO: Replace $scope.$watch with @Output methods for child component mutations or $onChanges for parent mutations
this.$scope.$watch(() => this.trigger, (trigger) => {
if (trigger && this.repository) {
this.config = trigger['config'] || {};
this.namespaceTitle = 'organization';
this.local.selectedNamespace = null;
this.loadNamespaces();
}
});
this.$scope.$watch(() => this.local.selectedNamespace, (namespace) => {
if (namespace) {
this.loadRepositories(namespace);
}
});
this.$scope.$watch(() => this.local.selectedRepository, (repository) => {
if (repository) {
this.loadRepositoryRefs(repository);
this.loadDockerfileLocations(repository);
}
});
this.$scope.$watch(() => this.local.dockerfilePath, (path) => {
if (path && this.local.selectedRepository) {
this.checkDockerfilePath(this.local.selectedRepository, path);
}
});
this.$scope.$watch(() => this.local.namespaceOptions.predicate, this.buildOrderedNamespaces);
this.$scope.$watch(() => this.local.namespaceOptions.reverse, this.buildOrderedNamespaces);
this.$scope.$watch(() => this.local.namespaceOptions.filter, this.buildOrderedNamespaces);
this.$scope.$watch(() => this.local.repositoryOptions.predicate, this.buildOrderedRepositories);
this.$scope.$watch(() => this.local.repositoryOptions.reverse, this.buildOrderedRepositories);
this.$scope.$watch(() => this.local.repositoryOptions.filter, this.buildOrderedRepositories);
this.$scope.$watch(() => this.local.repositoryOptions.hideStale, this.buildOrderedRepositories);
this.$scope.$watch(() => this.local.robotOptions.predicate, this.buildOrderedRobotAccounts);
this.$scope.$watch(() => this.local.robotOptions.reverse, this.buildOrderedRobotAccounts);
this.$scope.$watch(() => this.local.robotOptions.filter, this.buildOrderedRobotAccounts);
}
public $onChanges(changes: ng.IOnChangesObject): void {
@ -86,35 +139,186 @@ export class ManageTriggerGithostComponent implements ng.IComponentController {
}
private buildOrderedNamespaces(): void {
if (!this.local.namespaces) {
return;
}
var namespaces = this.local.namespaces || [];
this.local.orderedNamespaces = this.TableService.buildOrderedItems(namespaces,
this.local.namespaceOptions,
['id'],
['score']);
this.local.maxScore = 0;
namespaces.forEach((namespace) => {
this.local.maxScore = Math.max(namespace.score, this.local.maxScore);
});
}
private loadNamespaces(): void {
this.local.namespaces = null;
this.local.selectedNamespace = null;
this.local.orderedNamespaces = null;
this.local.selectedRepository = null;
this.local.orderedRepositories = null;
var params = {
'repository': this.repository.namespace + '/' + this.repository.name,
'trigger_uuid': this.trigger.id
};
this.ApiService.listTriggerBuildSourceNamespaces(null, params)
.then((resp) => {
this.local.namespaces = resp['namespaces'];
this.local.repositories = null;
this.buildOrderedNamespaces();
}, this.ApiService.errorDisplay('Could not retrieve the list of ' + this.namespaceTitle));
}
private buildOrderedRepositories(): void {
if (!this.local.repositories) {
return;
}
var repositories = this.local.repositories || [];
repositories.forEach((repository) => {
repository['last_updated_datetime'] = new Date(repository['last_updated'] * 1000);
});
if (this.local.repositoryOptions.hideStale) {
var existingRepositories = repositories;
repositories = repositories.filter((repository) => {
var older_date = moment(repository['last_updated_datetime']).add(1, 'months');
return !moment().isAfter(older_date);
});
if (existingRepositories.length > 0 && repositories.length == 0) {
repositories = existingRepositories;
}
}
this.local.orderedRepositories = this.TableService.buildOrderedItems(repositories,
this.local.repositoryOptions,
['name', 'description'],
[]);
}
private loadRepositories(): void {
private loadRepositories(namespace: any): void {
this.local.repositories = null;
this.local.selectedRepository = null;
this.local.repositoryRefs = null;
this.local.triggerOptions = {
'hasBranchTagFilter': false
};
this.local.orderedRepositories = null;
var params = {
'repository': this.repository.namespace + '/' + this.repository.name,
'trigger_uuid': this.trigger.id
};
var data = {
'namespace': namespace.id
};
this.ApiService.listTriggerBuildSources(data, params).then((resp) => {
if (namespace == this.local.selectedNamespace) {
this.local.repositories = resp['sources'];
this.buildOrderedRepositories();
}
}, this.ApiService.errorDisplay('Could not retrieve repositories'));
}
private loadRepositoryRefs(repository: any): void {
this.local.repositoryRefs = null;
this.local.triggerOptions = {
'hasBranchTagFilter': false
};
var params = {
'repository': this.repository.namespace + '/' + this.repository.name,
'trigger_uuid': this.trigger.id,
'field_name': 'refs'
};
var config = {
'build_source': repository.full_name
};
this.ApiService.listTriggerFieldValues(config, params).then((resp) => {
if (repository == this.local.selectedRepository) {
this.local.repositoryRefs = resp['values'];
this.local.repositoryFullRefs = resp['values'].map((ref) => {
var kind = ref.kind == 'branch' ? 'heads' : 'tags';
var icon = ref.kind == 'branch' ? 'fa-code-fork' : 'fa-tag';
return {
'value': kind + '/' + ref.name,
'icon': icon,
'title': ref.name
};
});
}
}, this.ApiService.errorDisplay('Could not retrieve repository refs'));
}
private loadDockerfileLocations(repository: any): void {
this.local.dockerfilePath = null;
var params = {
'repository': this.repository.namespace + '/' + this.repository.name,
'trigger_uuid': this.trigger.id
};
var config = {
'build_source': repository.full_name
};
this.ApiService.listBuildTriggerSubdirs(config, params)
.then((resp) => {
if (repository == this.local.selectedRepository) {
this.local.dockerfileLocations = resp;
}
}, this.ApiService.errorDisplay('Could not retrieve Dockerfile locations'));
}
private buildOrderedRobotAccounts(): void {
if (!this.local.triggerAnalysis || !this.local.triggerAnalysis.robots) {
return;
}
var robots = this.local.triggerAnalysis.robots;
this.local.orderedRobotAccounts = this.TableService.buildOrderedItems(robots,
this.local.robotOptions,
['name'],
[]);
}
private checkDockerfilePath(repository: any, path: string): void {
this.local.triggerAnalysis = null;
this.local.robotAccount = null;
var params = {
'repository': this.repository.namespace + '/' + this.repository.name,
'trigger_uuid': this.trigger.id
};
var config = {
'build_source': repository.full_name,
'subdir': path.substr(1)
};
var data = {
'config': config
};
this.ApiService.analyzeBuildTrigger(data, params)
.then((resp) => {
this.local.triggerAnalysis = resp;
this.buildOrderedRobotAccounts();
}, this.ApiService.errorDisplay('Could not analyze trigger'));
}
}