diff --git a/endpoints/trigger.py b/endpoints/trigger.py index dd8d30eec..a0f945704 100644 --- a/endpoints/trigger.py +++ b/endpoints/trigger.py @@ -65,6 +65,27 @@ class TriggerProviderException(Exception): pass +def _determine_build_ref(run_parameters, get_branch_sha, get_tag_sha, default_branch): + run_parameters = run_parameters or {} + + kind = '' + value = '' + + if 'refs' in run_parameters: + kind = run_parameters['refs']['kind'] + value = run_parameters['refs']['name'] + elif 'branch_name' in run_parameters: + kind = 'branch' + value = run_parameters['branch_name'] + + kind = kind or 'branch' + value = value or default_branch + + ref = 'refs/tags/' + value if kind == 'tag' else 'refs/heads/' + value + commit_sha = get_tag_sha(value) if kind == 'tag' else get_branch_sha(value) + return (commit_sha, ref) + + def find_matching_branches(config, branches): if 'branchtag_regex' in config: try: @@ -548,16 +569,25 @@ class BitbucketBuildTrigger(BuildTriggerHandler): run_parameters = run_parameters or {} repository = self._get_repository_client() - # Find the branch to build. - branch_name = run_parameters.get('branch_name') or self._get_default_branch(repository) + def get_branch_sha(branch_name): + # Lookup the commit SHA for the branch. + (result, data, _) = repository.get_branches() + if not result or not branch_name in data: + raise TriggerStartException('Could not find branch commit SHA') - # Lookup the commit SHA for the branch. - (result, data, _) = repository.get_branches() - if not result or not branch_name in data: - raise TriggerStartException('Could not find branch commit SHA') + return data[branch_name]['node'] - commit_sha = data[branch_name]['node'] - ref = 'refs/heads/%s' % (branch_name) + def get_tag_sha(tag_name): + # Lookup the commit SHA for the tag. + (result, data, _) = repository.get_tags() + if not result or not tag_name in data: + raise TriggerStartException('Could not find tag commit SHA') + + return data[tag_name]['node'] + + # Find the branch or tag to build. + (commit_sha, ref) = _determine_build_ref(run_parameters, get_branch_sha, get_tag_sha, + self._get_default_branch(repository)) return self._prepare_build(commit_sha, ref, True) @@ -945,22 +975,32 @@ class GithubBuildTrigger(BuildTriggerHandler): def manual_start(self, run_parameters=None): config = self.config source = config['build_source'] - run_parameters = run_parameters or {} try: gh_client = self._get_client() - - # Lookup the branch and its associated current SHA. repo = gh_client.get_repo(source) - branch_name = run_parameters.get('branch_name') or repo.default_branch - branch = repo.get_branch(branch_name) - commit_sha = branch.commit.sha - ref = 'refs/heads/%s' % (branch_name) - - return self._prepare_build(ref, commit_sha, True, repo=repo) + default_branch = repo.default_branch except GithubException as ghe: raise TriggerStartException(ghe.data['message']) + def get_branch_sha(branch_name): + branch = repo.get_branch(branch_name) + return branch.commit.sha + + def get_tag_sha(tag_name): + tags = {tag.name: tag for tag in repo.get_tags()} + if not tag_name in tags: + raise TriggerStartException('Could not find tag in repository') + + return tags[tag_name].commit.sha + + # Find the branch or tag to build. + (commit_sha, ref) = _determine_build_ref(run_parameters, get_branch_sha, get_tag_sha, + default_branch) + + return self._prepare_build(ref, commit_sha, True, repo=repo) + + def list_field_values(self, field_name): if field_name == 'refs': branches = self.list_field_values('branch_name') @@ -1446,29 +1486,36 @@ class GitLabBuildTrigger(BuildTriggerHandler): return self._prepare_build(commit['id'], ref, False) def manual_start(self, run_parameters=None): - run_parameters = run_parameters or {} gl_client = self._get_authorized_client() repo = gl_client.getproject(self.config['build_source']) if repo is False: raise TriggerStartException('Could not find repository') - branch_name = run_parameters.get('branch_name') or repo['default_branch'] + def get_tag_sha(tag_name): + tags = gl_client.getrepositorytags(repo['id']) + if tags is False: + raise TriggerStartException('Could not find tags') - branches = gl_client.getbranches(repo['id']) - if branches is False: - raise TriggerStartException('Could not find branches') + for tag in tags: + if tag['name'] == tag_name: + return tag['commit']['id'] - commit = None - for branch in branches: - if branch['name'] == branch_name: - commit = branch['commit'] - if commit is None: raise TriggerStartException('Could not find commit') - ref = 'refs/heads/%s' % branch_name + def get_branch_sha(branch_name): + branch = gl_client.getbranch(repo['id'], branch_name) + if branch is False: + raise TriggerStartException('Could not find branch') - return self._prepare_build(commit['id'], ref, True) + return branch['commit']['id'] + + # Find the branch or tag to build. + (commit_sha, ref) = _determine_build_ref(run_parameters, get_branch_sha, get_tag_sha, + repo['default_branch']) + + + return self._prepare_build(commit_sha, ref, True) def get_repository_url(self): gl_client = self._get_authorized_client() diff --git a/static/css/directives/ui/dropdown-select-direct.css b/static/css/directives/ui/dropdown-select-direct.css new file mode 100644 index 000000000..fa0bd0054 --- /dev/null +++ b/static/css/directives/ui/dropdown-select-direct.css @@ -0,0 +1,58 @@ +.dropdown-select-direct { + margin: 10px; + position: relative; +} + +.dropdown-select-direct .dropdown-select-direct-icon { + position: absolute; + top: 6px; + left: 6px; + z-index: 2; +} + +.dropdown-select-direct .dropdown-select-direct-icon.fa { + top: 10px; + left: 8px; + font-size: 20px; +} + +.dropdown-select-direct .dropdown-select-direct-icon.none-icon { + color: #ccc; + display: inline; +} + +.dropdown-select-direct.has-item .dropdown-direct-icon-icon { + display: inline; +} + +.dropdown-select-direct.has-item .dropdown-direct-icon-icon.none-icon { + display: none; +} + +.dropdown-select-direct input.form-control[readonly] { + cursor: pointer; + background-color: #fff; +} + +.dropdown-select-direct .lookahead-input { + padding-left: 32px; +} + +.dropdown-select-direct .twitter-typeahead { + display: block !important; +} + +.dropdown-select-direct .twitter-typeahead .tt-hint { + padding-left: 32px; +} + +.dropdown-select-direct .dropdown { + position: absolute; + right: 0px; + top: 0px; +} + +.dropdown-select-direct .dropdown button.dropdown-toggle { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; +} diff --git a/static/directives/dropdown-select-direct.html b/static/directives/dropdown-select-direct.html new file mode 100644 index 000000000..67364e21d --- /dev/null +++ b/static/directives/dropdown-select-direct.html @@ -0,0 +1,22 @@ + diff --git a/static/directives/manual-trigger-build-dialog.html b/static/directives/manual-trigger-build-dialog.html index 701d49b35..5f0477ae4 100644 --- a/static/directives/manual-trigger-build-dialog.html +++ b/static/directives/manual-trigger-build-dialog.html @@ -12,18 +12,36 @@
- +
{{ field.title }}:{{ field.title }}:
- - + +
+ + +
+ + +
+ - +
+ + - + +
and a menu on the right. + */ +angular.module('quay').directive('dropdownSelectDirect', function ($compile) { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/dropdown-select-direct.html', + replace: true, + transclude: true, + restrict: 'C', + scope: { + 'selectedItem': '=selectedItem', + + 'placeholder': '=placeholder', + 'items': '=items', + 'iconMap': '=iconMap', + + 'valueKey': '@valueKey', + 'titleKey': '@titleKey', + 'iconKey': '@iconKey', + + 'noneIcon': '@noneIcon', + + 'clearValue': '=clearValue' + }, + controller: function($scope, $element, $rootScope) { + if (!$rootScope.__dropdownSelectCounter) { + $rootScope.__dropdownSelectCounter = 1; + } + + $scope.placeholder = $scope.placeholder || ''; + $scope.internalItem = null; + + // Setup lookahead. + var input = $($element).find('.lookahead-input'); + + $scope.setItem = function(item) { + $scope.selectedItem = item; + }; + + $scope.$watch('clearValue', function(cv) { + if (cv) { + $scope.selectedItem = null; + $(input).val(''); + } + }); + + $scope.$watch('selectedItem', function(item) { + if ($scope.selectedItem == $scope.internalItem) { + // The item has already been set due to an internal action. + return; + } + + if ($scope.selectedItem != null) { + $(input).val(item[$scope.valueKey]); + } else { + $(input).val(''); + } + }); + + $scope.$watch('items', function(items) { + $(input).off(); + if (!items || !$scope.valueKey) { + return; + } + + var formattedItems = []; + for (var i = 0; i < items.length; ++i) { + var currentItem = items[i]; + var formattedItem = { + 'value': currentItem[$scope.valueKey], + 'item': currentItem + }; + + formattedItems.push(formattedItem); + } + + var dropdownHound = new Bloodhound({ + name: 'dropdown-items-' + $rootScope.__dropdownSelectCounter, + local: formattedItems, + datumTokenizer: function(d) { + return Bloodhound.tokenizers.whitespace(d.val || d.value || ''); + }, + queryTokenizer: Bloodhound.tokenizers.whitespace + }); + dropdownHound.initialize(); + + $(input).typeahead({}, { + source: dropdownHound.ttAdapter(), + templates: { + 'suggestion': function (datum) { + template = datum['template'] ? datum['template'](datum) : datum['value']; + return template; + } + } + }); + + $(input).on('input', function(e) { + $scope.$apply(function() { + $scope.internalItem = null; + $scope.selectedItem = null; + }); + }); + + $(input).on('typeahead:selected', function(e, datum) { + $scope.$apply(function() { + $scope.internalItem = datum['item']; + $scope.selectedItem = datum['item']; + }); + }); + + $rootScope.__dropdownSelectCounter++; + }); + } + }; + return directiveDefinitionObject; +}); diff --git a/static/js/directives/ui/manual-trigger-build-dialog.js b/static/js/directives/ui/manual-trigger-build-dialog.js index 28d3bd93e..b05d579f6 100644 --- a/static/js/directives/ui/manual-trigger-build-dialog.js +++ b/static/js/directives/ui/manual-trigger-build-dialog.js @@ -16,6 +16,7 @@ angular.module('quay').directive('manualTriggerBuildDialog', function () { controller: function($scope, $element, ApiService, TriggerService) { $scope.parameters = {}; $scope.fieldOptions = {}; + $scope.lookaheadItems = {}; $scope.startTrigger = function() { $('#startTriggerDialog').modal('hide'); @@ -36,7 +37,7 @@ angular.module('quay').directive('manualTriggerBuildDialog', function () { var parameters = TriggerService.getRunParameters($scope.trigger.service); for (var i = 0; i < parameters.length; ++i) { var parameter = parameters[i]; - if (parameter['type'] == 'option') { + if (parameter['type'] == 'option' || parameter['type'] == 'autocomplete') { // Load the values for this parameter. var params = { 'repository': $scope.repository.namespace + '/' + $scope.repository.name, diff --git a/static/js/services/trigger-service.js b/static/js/services/trigger-service.js index b5566aa18..e597e5628 100644 --- a/static/js/services/trigger-service.js +++ b/static/js/services/trigger-service.js @@ -6,15 +6,19 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K function(UtilService, $sanitize, KeyService, Features, CookieService, Config) { var triggerService = {}; + var branch_tag = { + 'title': 'Branch/Tag', + 'type': 'autocomplete', + 'name': 'refs', + 'iconMap': { + 'branch': 'fa-code-fork', + 'tag': 'fa-tag' + } + }; + var triggerTypes = { 'github': { - 'run_parameters': [ - { - 'title': 'Branch', - 'type': 'option', - 'name': 'branch_name' - } - ], + 'run_parameters': [branch_tag], 'get_redirect_url': function(namespace, repository) { var redirect_uri = KeyService['githubRedirectUri'] + '/trigger/' + namespace + '/' + repository; @@ -55,13 +59,7 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K }, 'bitbucket': { - 'run_parameters': [ - { - 'title': 'Branch', - 'type': 'option', - 'name': 'branch_name' - } - ], + 'run_parameters': [branch_tag], 'get_redirect_url': function(namespace, repository) { return Config.getUrl('/bitbucket/setup/' + namespace + '/' + repository); }, @@ -84,13 +82,7 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K }, 'gitlab': { - 'run_parameters': [ - { - 'title': 'Branch', - 'type': 'option', - 'name': 'branch_name' - } - ], + 'run_parameters': [branch_tag], 'get_redirect_url': function(namespace, repository) { var redirect_uri = KeyService['gitlabRedirectUri'] + '/trigger'; var authorize_url = KeyService['gitlabTriggerAuthorizeUrl'];