diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-10 15:10:12 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-10 15:10:12 +0300 |
commit | 419966e5d34a98d89354ed658c33478ce02d8017 (patch) | |
tree | 6a83543e53f483b59953febc8d61a23b19dd5ccb /app | |
parent | bfbd788e0910597b93b31020300b15952828c386 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
18 files changed, 611 insertions, 87 deletions
diff --git a/app/assets/javascripts/admin/deploy_keys/components/table.vue b/app/assets/javascripts/admin/deploy_keys/components/table.vue new file mode 100644 index 00000000000..97a5a2f2f32 --- /dev/null +++ b/app/assets/javascripts/admin/deploy_keys/components/table.vue @@ -0,0 +1,54 @@ +<script> +import { GlTable, GlButton } from '@gitlab/ui'; + +import { __ } from '~/locale'; + +export default { + name: 'DeployKeysTable', + i18n: { + pageTitle: __('Public deploy keys'), + newDeployKeyButtonText: __('New deploy key'), + }, + fields: [ + { + key: 'title', + label: __('Title'), + }, + { + key: 'fingerprint', + label: __('Fingerprint'), + }, + { + key: 'projects', + label: __('Projects with write access'), + }, + { + key: 'created', + label: __('Created'), + }, + { + key: 'actions', + label: __('Actions'), + }, + ], + components: { + GlTable, + GlButton, + }, + inject: ['editPath', 'deletePath', 'createPath', 'emptyStateSvgPath'], +}; +</script> + +<template> + <div> + <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-py-5"> + <h4 class="gl-m-0"> + {{ $options.i18n.pageTitle }} + </h4> + <gl-button variant="confirm" :href="createPath">{{ + $options.i18n.newDeployKeyButtonText + }}</gl-button> + </div> + <gl-table :fields="$options.fields" data-testid="deploy-keys-list" /> + </div> +</template> diff --git a/app/assets/javascripts/admin/deploy_keys/index.js b/app/assets/javascripts/admin/deploy_keys/index.js new file mode 100644 index 00000000000..d86de4229de --- /dev/null +++ b/app/assets/javascripts/admin/deploy_keys/index.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import DeployKeysTable from './components/table.vue'; + +export const initAdminDeployKeysTable = () => { + const el = document.getElementById('js-admin-deploy-keys-table'); + + if (!el) return false; + + const { editPath, deletePath, createPath, emptyStateSvgPath } = el.dataset; + + return new Vue({ + el, + provide: { + editPath, + deletePath, + createPath, + emptyStateSvgPath, + }, + render(createElement) { + return createElement(DeployKeysTable); + }, + }); +}; diff --git a/app/assets/javascripts/chronic_duration.js b/app/assets/javascripts/chronic_duration.js new file mode 100644 index 00000000000..b595324f6e9 --- /dev/null +++ b/app/assets/javascripts/chronic_duration.js @@ -0,0 +1,386 @@ +/* + * NOTE: + * Changes to this file should be kept in sync with + * https://gitlab.com/gitlab-org/gitlab-chronic-duration/-/blob/master/lib/gitlab_chronic_duration.rb. + */ + +export class DurationParseError extends Error {} + +// On average, there's a little over 4 weeks in month. +const FULL_WEEKS_PER_MONTH = 4; + +const HOURS_PER_DAY = 24; +const DAYS_PER_MONTH = 30; + +const FLOAT_MATCHER = /[0-9]*\.?[0-9]+/g; +const DURATION_UNITS_LIST = ['seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years']; + +const MAPPINGS = { + seconds: 'seconds', + second: 'seconds', + secs: 'seconds', + sec: 'seconds', + s: 'seconds', + minutes: 'minutes', + minute: 'minutes', + mins: 'minutes', + min: 'minutes', + m: 'minutes', + hours: 'hours', + hour: 'hours', + hrs: 'hours', + hr: 'hours', + h: 'hours', + days: 'days', + day: 'days', + dy: 'days', + d: 'days', + weeks: 'weeks', + week: 'weeks', + wks: 'weeks', + wk: 'weeks', + w: 'weeks', + months: 'months', + mo: 'months', + mos: 'months', + month: 'months', + years: 'years', + year: 'years', + yrs: 'years', + yr: 'years', + y: 'years', +}; + +const JOIN_WORDS = ['and', 'with', 'plus']; + +function convertToNumber(string) { + const f = parseFloat(string); + return f % 1 > 0 ? f : parseInt(string, 10); +} + +function durationUnitsSecondsMultiplier(unit, opts) { + if (!DURATION_UNITS_LIST.includes(unit)) { + return 0; + } + + const hoursPerDay = opts.hoursPerDay || HOURS_PER_DAY; + const daysPerMonth = opts.daysPerMonth || DAYS_PER_MONTH; + const daysPerWeek = Math.trunc(daysPerMonth / FULL_WEEKS_PER_MONTH); + + switch (unit) { + case 'years': + return 31557600; + case 'months': + return 3600 * hoursPerDay * daysPerMonth; + case 'weeks': + return 3600 * hoursPerDay * daysPerWeek; + case 'days': + return 3600 * hoursPerDay; + case 'hours': + return 3600; + case 'minutes': + return 60; + case 'seconds': + return 1; + default: + return 0; + } +} + +function calculateFromWords(string, opts) { + let val = 0; + const words = string.split(' '); + words.forEach((v, k) => { + if (v === '') { + return; + } + if (v.search(FLOAT_MATCHER) >= 0) { + val += + convertToNumber(v) * + durationUnitsSecondsMultiplier( + words[parseInt(k, 10) + 1] || opts.defaultUnit || 'seconds', + opts, + ); + } + }); + return val; +} + +// Parse 3:41:59 and return 3 hours 41 minutes 59 seconds +function filterByType(string) { + const chronoUnitsList = DURATION_UNITS_LIST.filter((v) => v !== 'weeks'); + if ( + string + .replace(/ +/g, '') + .search(RegExp(`${FLOAT_MATCHER.source}(:${FLOAT_MATCHER.source})+`, 'g')) >= 0 + ) { + const res = []; + string + .replace(/ +/g, '') + .split(':') + .reverse() + .forEach((v, k) => { + if (!chronoUnitsList[k]) { + return; + } + res.push(`${v} ${chronoUnitsList[k]}`); + }); + return res.reverse().join(' '); + } + return string; +} + +// Get rid of unknown words and map found +// words to defined time units +function filterThroughWhiteList(string, opts) { + const res = []; + string.split(' ').forEach((word) => { + if (word === '') { + return; + } + if (word.search(FLOAT_MATCHER) >= 0) { + res.push(word.trim()); + return; + } + const strippedWord = word.trim().replace(/^,/g, '').replace(/,$/g, ''); + if (MAPPINGS[strippedWord] !== undefined) { + res.push(MAPPINGS[strippedWord]); + } else if (!JOIN_WORDS.includes(strippedWord) && opts.raiseExceptions) { + throw new DurationParseError( + `An invalid word ${JSON.stringify(word)} was used in the string to be parsed.`, + ); + } + }); + // add '1' at front if string starts with something recognizable but not with a number, like 'day' or 'minute 30sec' + if (res.length > 0 && MAPPINGS[res[0]]) { + res.splice(0, 0, 1); + } + return res.join(' '); +} + +function cleanup(string, opts) { + let res = string.toLowerCase(); + /* + * TODO The Ruby implementation of this algorithm uses the Numerizer module, + * which converts strings like "forty two" to "42", but there is no + * JavaScript equivalent of Numerizer. Skip it for now until Numerizer is + * ported to JavaScript. + */ + res = filterByType(res); + res = res + .replace(FLOAT_MATCHER, (n) => ` ${n} `) + .replace(/ +/g, ' ') + .trim(); + return filterThroughWhiteList(res, opts); +} + +function humanizeTimeUnit(number, unit, pluralize, keepZero) { + if (number === '0' && !keepZero) { + return null; + } + let res = number + unit; + // A poor man's pluralizer + if (number !== '1' && pluralize) { + res += 's'; + } + return res; +} + +// Given a string representation of elapsed time, +// return an integer (or float, if fractions of a +// second are input) +export function parseChronicDuration(string, opts = {}) { + const result = calculateFromWords(cleanup(string, opts), opts); + return !opts.keepZero && result === 0 ? null : result; +} + +// Given an integer and an optional format, +// returns a formatted string representing elapsed time +export function outputChronicDuration(seconds, opts = {}) { + const units = { + years: 0, + months: 0, + weeks: 0, + days: 0, + hours: 0, + minutes: 0, + seconds, + }; + + const hoursPerDay = opts.hoursPerDay || HOURS_PER_DAY; + const daysPerMonth = opts.daysPerMonth || DAYS_PER_MONTH; + const daysPerWeek = Math.trunc(daysPerMonth / FULL_WEEKS_PER_MONTH); + + const decimalPlaces = + seconds % 1 !== 0 ? seconds.toString().split('.').reverse()[0].length : null; + + const minute = 60; + const hour = 60 * minute; + const day = hoursPerDay * hour; + const month = daysPerMonth * day; + const year = 31557600; + + if (units.seconds >= 31557600 && units.seconds % year < units.seconds % month) { + units.years = Math.trunc(units.seconds / year); + units.months = Math.trunc((units.seconds % year) / month); + units.days = Math.trunc(((units.seconds % year) % month) / day); + units.hours = Math.trunc((((units.seconds % year) % month) % day) / hour); + units.minutes = Math.trunc(((((units.seconds % year) % month) % day) % hour) / minute); + units.seconds = Math.trunc(((((units.seconds % year) % month) % day) % hour) % minute); + } else if (seconds >= 60) { + units.minutes = Math.trunc(seconds / 60); + units.seconds %= 60; + if (units.minutes >= 60) { + units.hours = Math.trunc(units.minutes / 60); + units.minutes = Math.trunc(units.minutes % 60); + if (!opts.limitToHours) { + if (units.hours >= hoursPerDay) { + units.days = Math.trunc(units.hours / hoursPerDay); + units.hours = Math.trunc(units.hours % hoursPerDay); + if (opts.weeks) { + if (units.days >= daysPerWeek) { + units.weeks = Math.trunc(units.days / daysPerWeek); + units.days = Math.trunc(units.days % daysPerWeek); + if (units.weeks >= FULL_WEEKS_PER_MONTH) { + units.months = Math.trunc(units.weeks / FULL_WEEKS_PER_MONTH); + units.weeks = Math.trunc(units.weeks % FULL_WEEKS_PER_MONTH); + } + } + } else if (units.days >= daysPerMonth) { + units.months = Math.trunc(units.days / daysPerMonth); + units.days = Math.trunc(units.days % daysPerMonth); + } + } + } + } + } + + let joiner = opts.joiner || ' '; + let process = null; + + let dividers; + switch (opts.format) { + case 'micro': + dividers = { + years: 'y', + months: 'mo', + weeks: 'w', + days: 'd', + hours: 'h', + minutes: 'm', + seconds: 's', + }; + joiner = ''; + break; + case 'short': + dividers = { + years: 'y', + months: 'mo', + weeks: 'w', + days: 'd', + hours: 'h', + minutes: 'm', + seconds: 's', + }; + break; + case 'long': + dividers = { + /* eslint-disable @gitlab/require-i18n-strings */ + years: ' year', + months: ' month', + weeks: ' week', + days: ' day', + hours: ' hour', + minutes: ' minute', + seconds: ' second', + /* eslint-enable @gitlab/require-i18n-strings */ + pluralize: true, + }; + break; + case 'chrono': + dividers = { + years: ':', + months: ':', + weeks: ':', + days: ':', + hours: ':', + minutes: ':', + seconds: ':', + keepZero: true, + }; + process = (str) => { + // Pad zeros + // Get rid of lead off times if they are zero + // Get rid of lead off zero + // Get rid of trailing: + const divider = ':'; + const processed = []; + str.split(divider).forEach((n) => { + if (n === '') { + return; + } + // add zeros only if n is an integer + if (n.search('\\.') >= 0) { + processed.push( + parseFloat(n) + .toFixed(decimalPlaces) + .padStart(3 + decimalPlaces, '0'), + ); + } else { + processed.push(n.padStart(2, '0')); + } + }); + return processed + .join(divider) + .replace(/^(00:)+/g, '') + .replace(/^0/g, '') + .replace(/:$/g, ''); + }; + joiner = ''; + break; + default: + dividers = { + /* eslint-disable @gitlab/require-i18n-strings */ + years: ' yr', + months: ' mo', + weeks: ' wk', + days: ' day', + hours: ' hr', + minutes: ' min', + seconds: ' sec', + /* eslint-enable @gitlab/require-i18n-strings */ + pluralize: true, + }; + break; + } + + let result = []; + ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'].forEach((t) => { + if (t === 'weeks' && !opts.weeks) { + return; + } + let num = units[t]; + if (t === 'seconds' && num % 0 !== 0) { + num = num.toFixed(decimalPlaces); + } else { + num = num.toString(); + } + const keepZero = !dividers.keepZero && t === 'seconds' ? opts.keepZero : dividers.keepZero; + const humanized = humanizeTimeUnit(num, dividers[t], dividers.pluralize, keepZero); + if (humanized !== null) { + result.push(humanized); + } + }); + + if (opts.units) { + result = result.slice(0, opts.units); + } + + result = result.join(joiner); + + if (process) { + result = process(result); + } + + return result.length === 0 ? null : result; +} diff --git a/app/assets/javascripts/pages/admin/deploy_keys/index/index.js b/app/assets/javascripts/pages/admin/deploy_keys/index/index.js new file mode 100644 index 00000000000..1e52aa3efd8 --- /dev/null +++ b/app/assets/javascripts/pages/admin/deploy_keys/index/index.js @@ -0,0 +1,3 @@ +import { initAdminDeployKeysTable } from '~/admin/deploy_keys'; + +initAdminDeployKeysTable(); diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue index 0163b8c572b..6fe1459c80c 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { truncateSha } from '~/lib/utils/text_utility'; import { s__ } from '~/locale'; @@ -20,6 +20,7 @@ export const i18n = { `Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}`, ), viewBtn: s__('Pipeline|View pipeline'), + viewCommit: s__('Pipeline|View commit'), pipelineNotTriggeredMsg: s__( 'Pipeline|No pipeline was triggered for the latest changes due to the current CI/CD configuration.', ), @@ -36,6 +37,9 @@ export default { GlSprintf, PipelineEditorMiniGraph, }, + directives: { + GlTooltip: GlTooltipDirective, + }, inject: ['projectFullPath'], props: { commitSha: { @@ -60,13 +64,13 @@ export default { }; }, update(data) { - const { id, iid, commitPath = '', detailedStatus = {}, stages, status } = + const { id, iid, commit = {}, detailedStatus = {}, stages, status } = data.project?.pipeline || {}; return { id, iid, - commitPath, + commit, detailedStatus, stages, status, @@ -95,6 +99,16 @@ export default { }; }, computed: { + commitText() { + const shortSha = truncateSha(this.commitSha); + const commitTitle = this.pipeline.commit.title || ''; + + if (commitTitle.length > 0) { + return `${shortSha}: ${commitTitle}`; + } + + return shortSha; + }, hasPipelineData() { return Boolean(this.pipeline?.id); }, @@ -146,7 +160,7 @@ export default { </div> </template> <template v-else> - <div> + <div class="gl-text-truncate gl-md-max-w-50p gl-mr-1"> <a :href="status.detailsPath" class="gl-mr-auto"> <ci-icon :status="status" :size="16" data-testid="pipeline-status-icon" /> </a> @@ -158,12 +172,12 @@ export default { <template #status>{{ status.text }}</template> <template #commit> <gl-link - :href="pipeline.commitPath" - class="commit-sha gl-font-weight-normal" - target="_blank" + v-gl-tooltip.hover + :href="pipeline.commit.webPath" + :title="$options.i18n.viewCommit" data-testid="pipeline-commit" > - {{ shortSha }} + {{ commitText }} </gl-link> </template> </gl-sprintf> @@ -173,7 +187,6 @@ export default { <pipeline-editor-mini-graph :pipeline="pipeline" v-on="$listeners" /> <gl-button class="gl-mt-2 gl-md-mt-0" - target="_blank" category="secondary" variant="confirm" :href="status.detailsPath" diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql index 0c3653a2880..34e98ae3eb3 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql @@ -1,10 +1,13 @@ query getPipeline($fullPath: ID!, $sha: String!) { project(fullPath: $fullPath) { pipeline(sha: $sha) { - commitPath id iid status + commit { + title + webPath + } detailedStatus { detailsPath icon diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index fbcc97d6750..7e46f16e1d0 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -290,3 +290,10 @@ $gl-line-height-42: px-to-rem(42px); .gl-focus-ring-border-1-gray-900\! { @include gl-focus($gl-border-size-1, $gray-900, true); } + +// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2476 +.gl-md-max-w-50p { + @include gl-media-breakpoint-up(md) { + max-width: 50%; + } +} diff --git a/app/controllers/projects/alerting/notifications_controller.rb b/app/controllers/projects/alerting/notifications_controller.rb index 95b403faf55..ae8498ce65f 100644 --- a/app/controllers/projects/alerting/notifications_controller.rb +++ b/app/controllers/projects/alerting/notifications_controller.rb @@ -18,7 +18,11 @@ module Projects token = extract_alert_manager_token(request) result = notify_service.execute(token, integration) - head result.http_status + if result.success? + render json: AlertManagement::AlertSerializer.new.represent(result.payload[:alerts]), code: result.http_status + else + head result.http_status + end end private diff --git a/app/controllers/projects/prometheus/alerts_controller.rb b/app/controllers/projects/prometheus/alerts_controller.rb index 19c908026cf..312919831d4 100644 --- a/app/controllers/projects/prometheus/alerts_controller.rb +++ b/app/controllers/projects/prometheus/alerts_controller.rb @@ -30,7 +30,11 @@ module Projects token = extract_alert_manager_token(request) result = notify_service.execute(token) - head result.http_status + if result.success? + render json: AlertManagement::AlertSerializer.new.represent(result.payload[:alerts]), code: result.http_status + else + head result.http_status + end end def create diff --git a/app/helpers/admin/deploy_key_helper.rb b/app/helpers/admin/deploy_key_helper.rb new file mode 100644 index 00000000000..caf3757a68e --- /dev/null +++ b/app/helpers/admin/deploy_key_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Admin + module DeployKeyHelper + def admin_deploy_keys_data + { + edit_path: edit_admin_deploy_key_path(':id'), + delete_path: admin_deploy_key_path(':id'), + create_path: new_admin_deploy_key_path, + empty_state_svg_path: image_path('illustrations/empty-state/empty-deploy-keys-lg.svg') + } + end + end +end diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb index dee202d3785..58cbf8dbdea 100644 --- a/app/helpers/routing/pseudonymization_helper.rb +++ b/app/helpers/routing/pseudonymization_helper.rb @@ -31,24 +31,11 @@ module Routing end end - generate_url(masked_params.merge(params: masked_query_params)) + Gitlab::Routing.url_helpers.url_for(masked_params.merge(params: masked_query_params)) end private - def generate_url(masked_params) - # The below check is added since `project/insights` route does not - # work with Rails router `url_for` method. - # See https://gitlab.com/gitlab-org/gitlab/-/issues/343551 - if @request.path_parameters[:controller] == 'projects/insights' - default_root_url + "#{Gitlab::Routing.url_helpers.namespace_project_insights_path(masked_params)}" - elsif @request.path_parameters[:controller] == 'groups/insights' - default_root_url + "#{Gitlab::Routing.url_helpers.group_insights_path(masked_params)}" - else - Gitlab::Routing.url_helpers.url_for(masked_params) - end - end - def mask_id(value) if @request.path_parameters[:controller] == 'projects/blob' ':repository_path' diff --git a/app/serializers/alert_management/alert_entity.rb b/app/serializers/alert_management/alert_entity.rb new file mode 100644 index 00000000000..6871da44887 --- /dev/null +++ b/app/serializers/alert_management/alert_entity.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module AlertManagement + class AlertEntity < Grape::Entity + expose :iid + expose :title + end +end diff --git a/app/serializers/alert_management/alert_serializer.rb b/app/serializers/alert_management/alert_serializer.rb new file mode 100644 index 00000000000..89815bf6510 --- /dev/null +++ b/app/serializers/alert_management/alert_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module AlertManagement + class AlertSerializer < BaseSerializer + entity AlertManagement::AlertEntity + end +end diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb index 605ab7a1869..1b377a3d367 100644 --- a/app/services/alert_management/process_prometheus_alert_service.rb +++ b/app/services/alert_management/process_prometheus_alert_service.rb @@ -4,6 +4,7 @@ module AlertManagement class ProcessPrometheusAlertService extend ::Gitlab::Utils::Override include ::AlertManagement::AlertProcessing + include ::AlertManagement::Responses def initialize(project, payload) @project = project @@ -18,7 +19,7 @@ module AlertManagement complete_post_processing_tasks - ServiceResponse.success + success(alert) end private @@ -40,9 +41,5 @@ module AlertManagement def resolving_alert? incoming_payload.resolved? end - - def bad_request - ServiceResponse.error(message: 'Bad Request', http_status: :bad_request) - end end end diff --git a/app/services/concerns/alert_management/responses.rb b/app/services/concerns/alert_management/responses.rb new file mode 100644 index 00000000000..183a831a00a --- /dev/null +++ b/app/services/concerns/alert_management/responses.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module AlertManagement + # Module to hold common response logic for AlertManagement services. + module Responses + def success(alerts) + ServiceResponse.success(payload: { alerts: Array(alerts) }) + end + + def bad_request + ServiceResponse.error(message: 'Bad Request', http_status: :bad_request) + end + + def unauthorized + ServiceResponse.error(message: 'Unauthorized', http_status: :unauthorized) + end + + def unprocessable_entity + ServiceResponse.error(message: 'Unprocessable Entity', http_status: :unprocessable_entity) + end + + def forbidden + ServiceResponse.error(message: 'Forbidden', http_status: :forbidden) + end + end +end diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb index a5ee7173bdf..e5d40b60747 100644 --- a/app/services/projects/alerting/notify_service.rb +++ b/app/services/projects/alerting/notify_service.rb @@ -5,6 +5,7 @@ module Projects class NotifyService extend ::Gitlab::Utils::Override include ::AlertManagement::AlertProcessing + include ::AlertManagement::Responses def initialize(project, payload) @project = project @@ -23,7 +24,7 @@ module Projects complete_post_processing_tasks - ServiceResponse.success + success(alert) end private @@ -46,18 +47,6 @@ module Projects def valid_token?(token) token == integration.token end - - def bad_request - ServiceResponse.error(message: 'Bad Request', http_status: :bad_request) - end - - def unauthorized - ServiceResponse.error(message: 'Unauthorized', http_status: :unauthorized) - end - - def forbidden - ServiceResponse.error(message: 'Forbidden', http_status: :forbidden) - end end end end diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb index c1bf2e68436..56f65718d24 100644 --- a/app/services/projects/prometheus/alerts/notify_service.rb +++ b/app/services/projects/prometheus/alerts/notify_service.rb @@ -6,6 +6,7 @@ module Projects class NotifyService include Gitlab::Utils::StrongMemoize include ::IncidentManagement::Settings + include ::AlertManagement::Responses # This set of keys identifies a payload as a valid Prometheus # payload and thus processable by this service. See also @@ -27,9 +28,9 @@ module Projects return unprocessable_entity unless self.class.processable?(payload) return unauthorized unless valid_alert_manager_token?(token, integration) - process_prometheus_alerts + alert_responses = process_prometheus_alerts - ServiceResponse.success + alert_response(alert_responses) end def self.processable?(payload) @@ -128,23 +129,17 @@ module Projects end def process_prometheus_alerts - alerts.each do |alert| + alerts.map do |alert| AlertManagement::ProcessPrometheusAlertService .new(project, alert.to_h) .execute end end - def bad_request - ServiceResponse.error(message: 'Bad Request', http_status: :bad_request) - end - - def unauthorized - ServiceResponse.error(message: 'Unauthorized', http_status: :unauthorized) - end + def alert_response(alert_responses) + alerts = alert_responses.map { |resp| resp.payload[:alert] }.compact - def unprocessable_entity - ServiceResponse.error(message: 'Unprocessable Entity', http_status: :unprocessable_entity) + success(alerts) end end end diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index eec8f816f04..1cbb5296f8d 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -1,33 +1,37 @@ - page_title _('Deploy Keys') -- if @deploy_keys.any? - %h3.page-title.deploy-keys-title - = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.load.size } - = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'float-right btn gl-button btn-confirm btn-md gl-button' - .table-holder.deploy-keys-list - %table.table - %thead - %tr - %th.col-sm-2= _('Title') - %th.col-sm-4= _('Fingerprint') - %th.col-sm-2= _('Projects with write access') - %th.col-sm-2= _('Added at') - %th.col-sm-2 - %tbody - - @deploy_keys.each do |deploy_key| - %tr - %td - %strong= deploy_key.title - %td - %code.key-fingerprint= deploy_key.fingerprint - %td - - deploy_key.projects_with_write_access.each do |project| - = link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label' - %td - %span.cgray - = _('added %{created_at_timeago}').html_safe % { created_at_timeago: time_ago_with_tooltip(deploy_key.created_at) } - %td - .float-right - = link_to _('Edit'), edit_admin_deploy_key_path(deploy_key), class: 'btn gl-button btn-sm' - = link_to _('Remove'), admin_deploy_key_path(deploy_key), data: { confirm: _('Are you sure?') }, method: :delete, class: 'gl-button btn btn-sm btn-danger delete-key' + +- if Feature.enabled?(:admin_deploy_keys_vue, default_enabled: :yaml) + #js-admin-deploy-keys-table{ data: admin_deploy_keys_data } - else - = render 'shared/empty_states/deploy_keys' + - if @deploy_keys.any? + %h3.page-title.deploy-keys-title + = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.load.size } + = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'float-right btn gl-button btn-confirm btn-md gl-button' + .table-holder.deploy-keys-list + %table.table + %thead + %tr + %th.col-sm-2= _('Title') + %th.col-sm-4= _('Fingerprint') + %th.col-sm-2= _('Projects with write access') + %th.col-sm-2= _('Added at') + %th.col-sm-2 + %tbody + - @deploy_keys.each do |deploy_key| + %tr + %td + %strong= deploy_key.title + %td + %code.key-fingerprint= deploy_key.fingerprint + %td + - deploy_key.projects_with_write_access.each do |project| + = link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label' + %td + %span.cgray + = _('added %{created_at_timeago}').html_safe % { created_at_timeago: time_ago_with_tooltip(deploy_key.created_at) } + %td + .float-right + = link_to _('Edit'), edit_admin_deploy_key_path(deploy_key), class: 'btn gl-button btn-sm' + = link_to _('Remove'), admin_deploy_key_path(deploy_key), data: { confirm: _('Are you sure?') }, method: :delete, class: 'gl-button btn btn-sm btn-danger delete-key' + - else + = render 'shared/empty_states/deploy_keys' |