From 0dce935c4093674ab6129bc5d52473bb7e52dd66 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 14 Sep 2016 16:06:52 -0400 Subject: [PATCH 1/5] Move repo notification create to its own page Also fixes a bug around Slack setup Fixes #1834 --- .../create-external-notification-dialog.css | 20 -- .../ui/create-external-notification.css | 69 ++++++ .../create-external-notification-dialog.html | 174 ------------- .../create-external-notification.html | 231 ++++++++++++++++++ .../directives/repo-view/repo-panel-info.html | 8 - .../directives/repository-events-table.html | 13 +- static/js/app.js | 3 + ...log.js => create-external-notification.js} | 46 ++-- .../directives/ui/repository-events-table.js | 8 - .../pages/create-repository-notification.js | 33 +++ .../js/services/external-notification-data.js | 19 +- .../create-repository-notification.html | 21 ++ 12 files changed, 390 insertions(+), 255 deletions(-) delete mode 100644 static/css/directives/ui/create-external-notification-dialog.css create mode 100644 static/css/directives/ui/create-external-notification.css delete mode 100644 static/directives/create-external-notification-dialog.html create mode 100644 static/directives/create-external-notification.html rename static/js/directives/ui/{create-external-notification-dialog.js => create-external-notification.js} (79%) create mode 100644 static/js/pages/create-repository-notification.js create mode 100644 static/partials/create-repository-notification.html diff --git a/static/css/directives/ui/create-external-notification-dialog.css b/static/css/directives/ui/create-external-notification-dialog.css deleted file mode 100644 index 12394955c..000000000 --- a/static/css/directives/ui/create-external-notification-dialog.css +++ /dev/null @@ -1,20 +0,0 @@ -#createNotificationModal .dropdown-select { - margin: 0px; -} - -#createNotificationModal .options-table { - width: 100%; - margin-bottom: 10px; -} - -#createNotificationModal .options-table td { - padding-bottom: 6px; -} - -#createNotificationModal .options-table td.name { - width: 160px; -} - -#createNotificationModal .options-table-wrapper { - padding: 10px; -} \ No newline at end of file diff --git a/static/css/directives/ui/create-external-notification.css b/static/css/directives/ui/create-external-notification.css new file mode 100644 index 000000000..133cced84 --- /dev/null +++ b/static/css/directives/ui/create-external-notification.css @@ -0,0 +1,69 @@ +.create-external-notification-element { + padding: 10px; +} + +.create-external-notification-element .dropdown-select { + margin: 0px; +} + +.create-external-notification-element .button-bar { + margin-top: 20px; + padding: 20px; + border-top: 1px solid #eee; + padding-bottom: 8px; + padding-left: 6px; +} + +.create-external-notification-element .options-table .section-header { + font-size: 18px; + padding: 6px; + padding-bottom: 2px; +} + +.create-external-notification-element .options-table { + width: 100%; +} + +.create-external-notification-element .options-table td { + padding: 10px; +} + +.create-external-notification-element .options-table td.name { + padding-left: 20px; + width: 1px; + white-space: nowrap; + vertical-align: top; + padding-top: 16px; +} + +.create-external-notification-element .options-table td.value { + padding-left: 20px; +} + +.create-external-notification-element .help-table { + width: 100%; +} + +.create-external-notification-element .help-table > tbody > tr > td { + vertical-align: top; + max-width: 600px; +} + +.create-external-notification-element .help-table td.help-col { + vertical-align: top; + padding: 20px; + padding-top: 40px; + width: 500px; + font-size: 15px; + color: #aaa; +} + +.create-external-notification-element .config-section { + margin-top: 20px; +} + + +.create-external-notification-element .help-text { + margin-top: 10px; + color: #aaa; +} \ No newline at end of file diff --git a/static/directives/create-external-notification-dialog.html b/static/directives/create-external-notification-dialog.html deleted file mode 100644 index f45e7353e..000000000 --- a/static/directives/create-external-notification-dialog.html +++ /dev/null @@ -1,174 +0,0 @@ - - diff --git a/static/directives/create-external-notification.html b/static/directives/create-external-notification.html new file mode 100644 index 000000000..8032ed04a --- /dev/null +++ b/static/directives/create-external-notification.html @@ -0,0 +1,231 @@ +
+
+ +
+ + + + + + + +
+ + + + + + + + + + +
When this event occurs
+ +
With {{ field.title }} (optional): +
+ + + + + + + +
+ {{ field.values[currentEventConfig[field.name]].description }} +
+ + +
+ {{ field.help_text }} +
+
+
+
+ supports a number of events around repositories (such as push completed), building (build queued, build completed, etc) and security (vulnerability detected). Some events also allow for filtering, for further granular control of when notifications fire. +
+ +
+ + + + + + +
+ + + + + + + + + + +
Then issue a notification
+ +
{{ field.title }}: +
+ + + + + + + + + + + + +
+ + +
+ +
+
+ + + + + + +
+ JSON metadata representing the event will be POSTed to the URL. All requests made to TLS-enabled URLs will be signed with the key. +

+ The contents for each event can be found in the user guide: + + http://docs.quay.io/guides/notifications.html + +
+
+ + +
+ {{ field.help_text }} +
+
+
+ Once an event has fired, supports a number of notification methods, including various chat systems (Slack, HipChat, etc), notification via e-mail or programatic handling via the firing of a webhook. +
+
+ +
+ + + + + +
+ + + + + + +
With extra configuration
Notification title: + +
+
+ The title for a notification is an optional field for a human-readable title for the notification. +
+
+ + +
+ +
+
+ + + +
\ No newline at end of file diff --git a/static/directives/repo-view/repo-panel-info.html b/static/directives/repo-view/repo-panel-info.html index 6605fecf7..2a4edf047 100644 --- a/static/directives/repo-view/repo-panel-info.html +++ b/static/directives/repo-view/repo-panel-info.html @@ -75,14 +75,6 @@ - - Automated Security Scanning -
Continually scanning this repository for 17K+ known vulnerabilities. Read more about this feature.
-
- Configure Vulnerability Alerts -
-
diff --git a/static/directives/repository-events-table.html b/static/directives/repository-events-table.html index 7fae2b4ff..67257080b 100644 --- a/static/directives/repository-events-table.html +++ b/static/directives/repository-events-table.html @@ -4,9 +4,9 @@ Events and Notifications
@@ -19,7 +19,7 @@ Click the "Create Notification" button above to add a new notification for a repository event.
- Click here to add a new notification for a repository event. + Click here to add a new notification for a repository event.
@@ -95,11 +95,4 @@ - - -
diff --git a/static/js/app.js b/static/js/app.js index 19e0390d9..a08e89d70 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -120,6 +120,9 @@ quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeP // Repo Build View .route('/repository/:namespace/:name/build/:buildid', 'build-view') + // Create repository notification + .route('/repository/:namespace/:name/create-notification', 'create-repository-notification') + // Repo List .route('/repository/', 'repo-list') diff --git a/static/js/directives/ui/create-external-notification-dialog.js b/static/js/directives/ui/create-external-notification.js similarity index 79% rename from static/js/directives/ui/create-external-notification-dialog.js rename to static/js/directives/ui/create-external-notification.js index 874769216..f6f00774f 100644 --- a/static/js/directives/ui/create-external-notification-dialog.js +++ b/static/js/directives/ui/create-external-notification.js @@ -1,16 +1,15 @@ /** - * An element which displays a dialog to register a new external notification on a repository. + * An element which displays a form to register a new external notification on a repository. */ -angular.module('quay').directive('createExternalNotificationDialog', function () { +angular.module('quay').directive('createExternalNotification', function () { var directiveDefinitionObject = { priority: 0, - templateUrl: '/static/directives/create-external-notification-dialog.html', + templateUrl: '/static/directives/create-external-notification.html', replace: false, transclude: false, restrict: 'C', scope: { 'repository': '=repository', - 'counter': '=counter', 'notificationCreated': '¬ificationCreated', 'defaultData': '=defaultData' }, @@ -27,7 +26,12 @@ angular.module('quay').directive('createExternalNotificationDialog', function () $scope.methods = ExternalNotificationData.getSupportedMethods(); $scope.getPattern = function(field) { - return new RegExp(field.regex); + if (field._cached_regex) { + return field._cached_regex; + } + + field._cached_regex = new RegExp(field.pattern); + return field._cached_regex; }; $scope.setEvent = function(event) { @@ -99,14 +103,6 @@ angular.module('quay').directive('createExternalNotificationDialog', function () ApiService.createRepoNotification(data, params).then(function(resp) { $scope.status = ''; $scope.notificationCreated({'notification': resp}); - - // Used by repository-events-summary. - if (!$scope.repository._notificationCounter) { - $scope.repository._notificationCounter = 0; - } - - $scope.repository._notificationCounter++; - $('#createNotificationModal').modal('hide'); }); }; @@ -123,6 +119,7 @@ angular.module('quay').directive('createExternalNotificationDialog', function () } $scope.unauthorizedEmail = true; + $('#authorizeEmailModal').modal({}); }; $scope.sendAuthEmail = function() { @@ -146,6 +143,11 @@ angular.module('quay').directive('createExternalNotificationDialog', function () }, 1000); }; + $scope.cancelEmailAuth = function() { + $scope.status = ''; + $('#authorizeEmailModal').modal('hide'); + }; + $scope.getHelpUrl = function(field, config) { var helpUrl = field['help_url']; if (!helpUrl) { @@ -155,21 +157,9 @@ angular.module('quay').directive('createExternalNotificationDialog', function () return StringBuilderService.buildUrl(helpUrl, config); }; - $scope.$watch('counter', function(counter) { - if (counter) { - $scope.clearCounter++; - $scope.status = ''; - $scope.currentEvent = null; - $scope.currentMethod = null; - $scope.unauthorizedEmail = false; - - $timeout(function() { - if ($scope.defaultData && $scope.defaultData['currentEvent']) { - $scope.setEvent($scope.defaultData['currentEvent']); - } - }, 100); - - $('#createNotificationModal').modal({}); + $scope.$watch('defaultData', function(counter) { + if ($scope.defaultData && $scope.defaultData['currentEvent']) { + $scope.setEvent($scope.defaultData['currentEvent']); } }); } diff --git a/static/js/directives/ui/repository-events-table.js b/static/js/directives/ui/repository-events-table.js index 8aca804b6..02c94ba73 100644 --- a/static/js/directives/ui/repository-events-table.js +++ b/static/js/directives/ui/repository-events-table.js @@ -53,14 +53,6 @@ angular.module('quay').directive('repositoryEventsTable', function () { loadNotifications(); - $scope.handleNotificationCreated = function(notification) { - $scope.notifications.push(notification); - }; - - $scope.askCreateNotification = function() { - $scope.showNewNotificationCounter++; - }; - $scope.findEnumValue = function(values, index) { var found = null; Object.keys(values).forEach(function(key) { diff --git a/static/js/pages/create-repository-notification.js b/static/js/pages/create-repository-notification.js new file mode 100644 index 000000000..7a02ca4d6 --- /dev/null +++ b/static/js/pages/create-repository-notification.js @@ -0,0 +1,33 @@ +(function() { + /** + * Create repository notification page. + */ + angular.module('quayPages').config(['pages', function(pages) { + pages.create('create-repository-notification', 'create-repository-notification.html', CreateRepoNotificationCtrl, { + 'newLayout': true, + 'title': 'Create Repo Notification: {{ namespace }}/{{ name }}', + 'description': 'Create repository notification for repository {{ namespace }}/{{ name }}' + }) + }]); + + function CreateRepoNotificationCtrl($scope, $routeParams, $location, ApiService) { + $scope.namespace = $routeParams.namespace; + $scope.name = $routeParams.name; + + var loadRepository = function() { + var params = { + 'repository': $scope.namespace + '/' + $scope.name + }; + + $scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) { + $scope.repository = repo; + }); + }; + + loadRepository(); + + $scope.notificationCreated = function() { + $location.url('repository/' + $scope.namespace + '/' + $scope.name + '?tab=settings'); + }; + } +})(); \ No newline at end of file diff --git a/static/js/services/external-notification-data.js b/static/js/services/external-notification-data.js index 229667e8c..229a84ae0 100644 --- a/static/js/services/external-notification-data.js +++ b/static/js/services/external-notification-data.js @@ -52,8 +52,12 @@ function(Config, Features, VulnerabilityService) { { 'name': 'level', 'type': 'enum', - 'title': 'Minimum Priority Level', + 'title': 'Minimum Severity Level', 'values': VulnerabilityService.LEVELS, + 'help_text': 'A vulnerability must have a severity of the chosen level (or higher) ' + + 'for this notification to fire. Defcon 1 is a special severity level ' + + 'manually tagged by the ' + Config.REGISTRY_TITLE_SHORT + ' team for ' + + 'above-critical issues', } ] }); @@ -68,7 +72,8 @@ function(Config, Features, VulnerabilityService) { { 'name': 'target', 'type': 'entity', - 'title': 'Recipient' + 'title': 'Recipient', + 'help_text': 'The ' + Config.REGISTRY_TITLE_SHORT + ' user to notify' } ] }, @@ -117,11 +122,11 @@ function(Config, Features, VulnerabilityService) { 'fields': [ { 'name': 'room_id', - 'type': 'regex', + 'type': 'pattern', 'title': 'Room ID #', - 'regex': '^[0-9]+$', + 'pattern': '^[0-9]+$', 'help_url': 'https://hipchat.com/admin/rooms', - 'regex_fail_message': 'We require the HipChat room number, not name.' + 'pattern_fail_message': 'We require the HipChat room number, not name.' }, { 'name': 'notification_token', @@ -138,9 +143,9 @@ function(Config, Features, VulnerabilityService) { 'fields': [ { 'name': 'url', - 'type': 'regex', + 'type': 'pattern', 'title': 'Webhook URL', - 'regex': '^https://hooks\\.slack\\.com/services/[A-Z0-9]+/[A-Z0-9]+/[a-zA-Z0-9]+$', + 'pattern': '^https://hooks\\.slack\\.com/services/[A-Z0-9]+/[A-Z0-9]+/[a-zA-Z0-9]+$', 'help_url': 'https://slack.com/services/new/incoming-webhook', 'placeholder': 'https://hooks.slack.com/service/{some}/{token}/{here}' } diff --git a/static/partials/create-repository-notification.html b/static/partials/create-repository-notification.html new file mode 100644 index 000000000..70ab559cc --- /dev/null +++ b/static/partials/create-repository-notification.html @@ -0,0 +1,21 @@ +
+
+
+ + + {{ namespace }} / {{ name }} + + + + + Create repository notification + +
+ +
+
+
+
+
From 03d4445a02a7c4563158894b488f3756e70a3549 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 14 Sep 2016 16:48:17 -0400 Subject: [PATCH 2/5] Add notification filtering for builds based on ref regex Fixes #1835 --- endpoints/notificationevent.py | 30 +++- .../js/services/external-notification-data.js | 50 ++++++- test/test_notifications.py | 135 ++++++++++++++++++ 3 files changed, 206 insertions(+), 9 deletions(-) create mode 100644 test/test_notifications.py diff --git a/endpoints/notificationevent.py b/endpoints/notificationevent.py index f1bf0a09d..dc0491e27 100644 --- a/endpoints/notificationevent.py +++ b/endpoints/notificationevent.py @@ -1,6 +1,7 @@ import logging import time import json +import re from datetime import datetime from notificationhelper import build_event_data @@ -138,7 +139,28 @@ class VulnerabilityFoundEvent(NotificationEvent): ', '.join(event_data['tags'])) -class BuildQueueEvent(NotificationEvent): +class BaseBuildEvent(NotificationEvent): + def should_perform(self, event_data, notification_data): + event_config = json.loads(notification_data.event_config_json) + ref_regex = event_config.get('ref-regex') or None + if ref_regex is None: + return True + + # Lookup the ref. If none, this is a non-git build and we should not fire the event. + ref = event_data.get('trigger_metadata', {}).get('ref', None) + if ref is None: + return False + + # Try parsing the regex string as a regular expression. If we fail, we fail to fire + # the event. + try: + return bool(re.compile(str(ref_regex)).match(ref)) + except Exception: + logger.warning('Regular expression error for build event filter: %s', ref_regex) + return False + + +class BuildQueueEvent(BaseBuildEvent): @classmethod def event_name(cls): return 'build_queued' @@ -177,7 +199,7 @@ class BuildQueueEvent(NotificationEvent): return 'Build queued ' + _build_summary(event_data) -class BuildStartEvent(NotificationEvent): +class BuildStartEvent(BaseBuildEvent): @classmethod def event_name(cls): return 'build_start' @@ -205,7 +227,7 @@ class BuildStartEvent(NotificationEvent): return 'Build started ' + _build_summary(event_data) -class BuildSuccessEvent(NotificationEvent): +class BuildSuccessEvent(BaseBuildEvent): @classmethod def event_name(cls): return 'build_success' @@ -234,7 +256,7 @@ class BuildSuccessEvent(NotificationEvent): return 'Build succeeded ' + _build_summary(event_data) -class BuildFailureEvent(NotificationEvent): +class BuildFailureEvent(BaseBuildEvent): @classmethod def event_name(cls): return 'build_failure' diff --git a/static/js/services/external-notification-data.js b/static/js/services/external-notification-data.js index 229a84ae0..1618b7909 100644 --- a/static/js/services/external-notification-data.js +++ b/static/js/services/external-notification-data.js @@ -20,22 +20,62 @@ function(Config, Features, VulnerabilityService) { { 'id': 'build_queued', 'title': 'Dockerfile Build Queued', - 'icon': 'fa-tasks' + 'icon': 'fa-tasks', + 'fields': [ + { + 'name': 'ref-regex', + 'type': 'regex', + 'title': 'matching ref(s)', + 'help_text': 'An optional regular expression for matching the git branch or tag ' + + 'git ref. If left blank, the notification will fire for all builds.', + 'optional': true, + } + ] }, { 'id': 'build_start', 'title': 'Dockerfile Build Started', - 'icon': 'fa-circle-o-notch' + 'icon': 'fa-circle-o-notch', + 'fields': [ + { + 'name': 'ref-regex', + 'type': 'regex', + 'title': 'matching ref(s)', + 'help_text': 'An optional regular expression for matching the git branch or tag ' + + 'git ref. If left blank, the notification will fire for all builds.', + 'optional': true, + } + ] }, { 'id': 'build_success', 'title': 'Dockerfile Build Successfully Completed', - 'icon': 'fa-check-circle-o' + 'icon': 'fa-check-circle-o', + 'fields': [ + { + 'name': 'ref-regex', + 'type': 'regex', + 'title': 'matching ref(s)', + 'help_text': 'An optional regular expression for matching the git branch or tag ' + + 'git ref. If left blank, the notification will fire for all builds.', + 'optional': true, + } + ] }, { 'id': 'build_failure', 'title': 'Dockerfile Build Failed', - 'icon': 'fa-times-circle-o' + 'icon': 'fa-times-circle-o', + 'fields': [ + { + 'name': 'ref-regex', + 'type': 'regex', + 'title': 'matching ref(s)', + 'help_text': 'An optional regular expression for matching the git branch or tag ' + + 'git ref. If left blank, the notification will fire for all builds.', + 'optional': true, + } + ] }]; for (var i = 0; i < buildEvents.length; ++i) { @@ -52,7 +92,7 @@ function(Config, Features, VulnerabilityService) { { 'name': 'level', 'type': 'enum', - 'title': 'Minimum Severity Level', + 'title': 'minimum severity level', 'values': VulnerabilityService.LEVELS, 'help_text': 'A vulnerability must have a severity of the chosen level (or higher) ' + 'for this notification to fire. Defcon 1 is a special severity level ' + diff --git a/test/test_notifications.py b/test/test_notifications.py new file mode 100644 index 000000000..3da728912 --- /dev/null +++ b/test/test_notifications.py @@ -0,0 +1,135 @@ +import unittest + +from endpoints.notificationevent import BuildSuccessEvent +from util.morecollections import AttrDict + +class TestShouldPerform(unittest.TestCase): + def test_build_nofilter(self): + notification_data = AttrDict({ + 'event_config_json': '{}', + }) + + # No build data at all. + self.assertTrue(BuildSuccessEvent().should_perform({}, notification_data)) + + # With trigger metadata but no ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': {}, + }, notification_data)) + + # With trigger metadata and a ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/somebranch', + }, + }, notification_data)) + + + def test_build_emptyfilter(self): + notification_data = AttrDict({ + 'event_config_json': '{"ref-regex": ""}', + }) + + # No build data at all. + self.assertTrue(BuildSuccessEvent().should_perform({}, notification_data)) + + # With trigger metadata but no ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': {}, + }, notification_data)) + + # With trigger metadata and a ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/somebranch', + }, + }, notification_data)) + + + def test_build_invalidfilter(self): + notification_data = AttrDict({ + 'event_config_json': '{"ref-regex": "]["}', + }) + + # No build data at all. + self.assertFalse(BuildSuccessEvent().should_perform({}, notification_data)) + + # With trigger metadata but no ref. + self.assertFalse(BuildSuccessEvent().should_perform({ + 'trigger_metadata': {}, + }, notification_data)) + + # With trigger metadata and a ref. + self.assertFalse(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/somebranch', + }, + }, notification_data)) + + + def test_build_withfilter(self): + notification_data = AttrDict({ + 'event_config_json': '{"ref-regex": "refs/heads/master"}', + }) + + # No build data at all. + self.assertFalse(BuildSuccessEvent().should_perform({}, notification_data)) + + # With trigger metadata but no ref. + self.assertFalse(BuildSuccessEvent().should_perform({ + 'trigger_metadata': {}, + }, notification_data)) + + # With trigger metadata and a not-matching ref. + self.assertFalse(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/somebranch', + }, + }, notification_data)) + + # With trigger metadata and a matching ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/master', + }, + }, notification_data)) + + + def test_build_withwildcardfilter(self): + notification_data = AttrDict({ + 'event_config_json': '{"ref-regex": "refs/heads/.+"}', + }) + + # No build data at all. + self.assertFalse(BuildSuccessEvent().should_perform({}, notification_data)) + + # With trigger metadata but no ref. + self.assertFalse(BuildSuccessEvent().should_perform({ + 'trigger_metadata': {}, + }, notification_data)) + + # With trigger metadata and a not-matching ref. + self.assertFalse(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/tags/sometag', + }, + }, notification_data)) + + # With trigger metadata and a matching ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/master', + }, + }, notification_data)) + + # With trigger metadata and another matching ref. + self.assertTrue(BuildSuccessEvent().should_perform({ + 'trigger_metadata': { + 'ref': 'refs/heads/somebranch', + }, + }, notification_data)) + + +if __name__ == '__main__': + unittest.main() + From 9140b0e41d61cc3a0f9c86521fc02fc23cd30952 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 14 Sep 2016 17:07:14 -0400 Subject: [PATCH 3/5] Add placeholder for regex field in create event control --- static/directives/create-external-notification.html | 1 + static/js/services/external-notification-data.js | 1 + 2 files changed, 2 insertions(+) diff --git a/static/directives/create-external-notification.html b/static/directives/create-external-notification.html index 8032ed04a..3ff3e2056 100644 --- a/static/directives/create-external-notification.html +++ b/static/directives/create-external-notification.html @@ -40,6 +40,7 @@ diff --git a/static/js/services/external-notification-data.js b/static/js/services/external-notification-data.js index 1618b7909..997fe96d8 100644 --- a/static/js/services/external-notification-data.js +++ b/static/js/services/external-notification-data.js @@ -29,6 +29,7 @@ function(Config, Features, VulnerabilityService) { 'help_text': 'An optional regular expression for matching the git branch or tag ' + 'git ref. If left blank, the notification will fire for all builds.', 'optional': true, + 'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)' } ] }, From c4eaed186e1bce2fa21eced7110151214e7d7960 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Thu, 15 Sep 2016 12:04:39 -0400 Subject: [PATCH 4/5] Hide help columns on small device sizes --- static/directives/create-external-notification.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/directives/create-external-notification.html b/static/directives/create-external-notification.html index 3ff3e2056..65ba8bc9b 100644 --- a/static/directives/create-external-notification.html +++ b/static/directives/create-external-notification.html @@ -59,7 +59,7 @@ - + supports a number of events around repositories (such as push completed), building (build queued, build completed, etc) and security (vulnerability detected). Some events also allow for filtering, for further granular control of when notifications fire. @@ -154,7 +154,7 @@ - + Once an event has fired, supports a number of notification methods, including various chat systems (Slack, HipChat, etc), notification via e-mail or programatic handling via the firing of a webhook. @@ -175,7 +175,7 @@ - + The title for a notification is an optional field for a human-readable title for the notification. From 949dcb9d3512ede93d057052651487b7ba1ec1a3 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Thu, 15 Sep 2016 13:25:57 -0400 Subject: [PATCH 5/5] Add regex validating field --- .../create-external-notification.html | 7 ++-- static/directives/regex-editor.html | 4 ++ static/js/directives/ui/regex-editor.js | 39 +++++++++++++++++++ .../js/services/external-notification-data.js | 3 ++ 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 static/directives/regex-editor.html create mode 100644 static/js/directives/ui/regex-editor.js diff --git a/static/directives/create-external-notification.html b/static/directives/create-external-notification.html index 65ba8bc9b..7bc684365 100644 --- a/static/directives/create-external-notification.html +++ b/static/directives/create-external-notification.html @@ -39,9 +39,10 @@ - +
+
+
+ +
\ No newline at end of file diff --git a/static/js/directives/ui/regex-editor.js b/static/js/directives/ui/regex-editor.js new file mode 100644 index 000000000..4b3358c49 --- /dev/null +++ b/static/js/directives/ui/regex-editor.js @@ -0,0 +1,39 @@ +/** + * An element which displays an edit box for regular expressions. + */ +angular.module('quay').directive('regexEditor', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/regex-editor.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'placeholder': '@placeholder', + 'optional': '=optional', + 'binding': '=binding' + }, + controller: function($scope, $element) { + } + }; + return directiveDefinitionObject; +}); + +angular.module('quay').directive('requireValidRegex', function() { + return { + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + function validator(value) { + try { + new RegExp(value) + ctrl.$setValidity('regex', true); + } catch (e) { + ctrl.$setValidity('regex', false); + } + return value; + } + + ctrl.$parsers.push(validator); + } + }; +}); \ No newline at end of file diff --git a/static/js/services/external-notification-data.js b/static/js/services/external-notification-data.js index 997fe96d8..623f1445d 100644 --- a/static/js/services/external-notification-data.js +++ b/static/js/services/external-notification-data.js @@ -45,6 +45,7 @@ function(Config, Features, VulnerabilityService) { 'help_text': 'An optional regular expression for matching the git branch or tag ' + 'git ref. If left blank, the notification will fire for all builds.', 'optional': true, + 'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)' } ] }, @@ -60,6 +61,7 @@ function(Config, Features, VulnerabilityService) { 'help_text': 'An optional regular expression for matching the git branch or tag ' + 'git ref. If left blank, the notification will fire for all builds.', 'optional': true, + 'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)' } ] }, @@ -75,6 +77,7 @@ function(Config, Features, VulnerabilityService) { 'help_text': 'An optional regular expression for matching the git branch or tag ' + 'git ref. If left blank, the notification will fire for all builds.', 'optional': true, + 'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)' } ] }];