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' }) export class ManageTriggerGithostComponent implements ng.IComponentController { // FIXME: Use one-way data binding @Input('=') public repository: any; @Input('=') public trigger: Trigger; @Output() public activateTrigger: (trigger: {config: any, pull_robot: any}) => void; private config: any; private local: any = { 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; private namespaceTitle: string; private namespace: any; constructor(private ApiService: any, private TableService: any, private TriggerService: 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, this.initialSetup.bind(this)); this.$scope.$watch(() => this.repository, this.initialSetup.bind(this)); 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.setPossibleContexts(path); this.checkDockerfilePath(this.local.selectedRepository, path, this.local.dockerContext); } }); this.$scope.$watch(() => this.local.dockerContext, (context) => { if (context && this.local.selectedRepository) { this.checkDockerfilePath(this.local.selectedRepository, this.local.dockerfilePath, context); } }); 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); } private initialSetup(): void { if (!this.repository || !this.trigger) { return; } if (this.namespaceTitle) { // Already setup. return; } this.config = this.trigger['config'] || {}; this.namespaceTitle = 'organization'; this.local.selectedNamespace = null; this.loadNamespaces(); } public getTriggerIcon(): any { return this.TriggerService.getIcon(this.trigger.service); } public createTrigger(): void { var config: any = { build_source: this.local.selectedRepository.full_name, dockerfile_path: this.local.dockerfilePath, context: this.local.dockerContext }; 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 && this.local.triggerAnalysis.status == 'requiresrobot') { 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 { 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(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; this.local.dockerContext = 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, context: 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, 'dockerfile_path': path.substr(1), 'context': context }; var data = { 'config': config }; this.ApiService.analyzeBuildTrigger(data, params) .then((resp) => { this.local.triggerAnalysis = resp; this.buildOrderedRobotAccounts(); }, this.ApiService.errorDisplay('Could not analyze trigger')); } private setPossibleContexts(path){ if(this.local.dockerfileLocations.contextMap){ this.local.contexts = this.local.dockerfileLocations.contextMap[path] || []; } } } /** * A type representing local application data. */ 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; }; }; /** * A type representing a trigger. */ export type Trigger = { id: number; service: any; };