diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-03 09:07:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-03 09:07:54 +0000 |
commit | 87ef501eacd66d7166183d20d84e33de022f7002 (patch) | |
tree | fa4e0f41e00a4b6aeb035530be4b5473f51b7a3d /app | |
parent | f321e51f46bcb628c3e96a44b5ebf3bb1c4033ab (diff) | |
download | gitlab-ce-87ef501eacd66d7166183d20d84e33de022f7002.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
36 files changed, 225 insertions, 69 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index 89d32efec6d..093d993c3ad 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -15,7 +15,6 @@ import { GlDropdownDivider, } from '@gitlab/ui'; import { __, sprintf, n__ } from '~/locale'; -import LoadingButton from '~/vue_shared/components/loading_button.vue'; import Icon from '~/vue_shared/components/icon.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import Stacktrace from './stacktrace.vue'; @@ -28,7 +27,6 @@ import query from '../queries/details.query.graphql'; export default { components: { - LoadingButton, GlButton, GlFormInput, GlLink, @@ -234,19 +232,21 @@ export default { </div> <div class="error-details-actions"> <div class="d-inline-flex bv-d-sm-down-none"> - <loading-button - :label="ignoreBtnLabel" + <gl-button :loading="updatingIgnoreStatus" data-qa-selector="update_ignore_status_button" @click="onIgnoreStatusUpdate" - /> - <loading-button + > + {{ ignoreBtnLabel }} + </gl-button> + <gl-button class="btn-outline-info ml-2" - :label="resolveBtnLabel" :loading="updatingResolveStatus" data-qa-selector="update_resolve_status_button" @click="onResolveStatusUpdate" - /> + > + {{ resolveBtnLabel }} + </gl-button> <gl-button v-if="error.gitlabIssuePath" class="ml-2" @@ -270,14 +270,15 @@ export default { name="issue[sentry_issue_attributes][sentry_issue_identifier]" /> <gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" /> - <loading-button + <gl-button v-if="!error.gitlabIssuePath" class="btn-success" - :label="__('Create issue')" :loading="issueCreationInProgress" data-qa-selector="create_issue_button" @click="createIssue" - /> + > + {{ __('Create issue') }} + </gl-button> </form> </div> <gl-dropdown diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 70f257180c6..552e8cac3a7 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -236,6 +236,7 @@ export default { </gl-dropdown> <div class="filtered-search-input-container flex-fill"> <gl-form-input + v-model="errorSearchQuery" class="pl-2 filtered-search" :disabled="loading" :placeholder="__('Search or filter results…')" diff --git a/app/assets/javascripts/monitoring/components/charts/options.js b/app/assets/javascripts/monitoring/components/charts/options.js new file mode 100644 index 00000000000..d9f49bd81f5 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/charts/options.js @@ -0,0 +1,78 @@ +import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; +import { s__ } from '~/locale'; + +const yAxisBoundaryGap = [0.1, 0.1]; +/** + * Max string length of formatted axis tick + */ +const maxDataAxisTickLength = 8; + +// Defaults +const defaultFormat = SUPPORTED_FORMATS.number; + +const defaultYAxisFormat = defaultFormat; +const defaultYAxisPrecision = 2; + +const defaultTooltipFormat = defaultFormat; +const defaultTooltipPrecision = 3; + +// Give enough space for y-axis with units and name. +const chartGridLeft = 75; + +// Axis options + +/** + * Converts .yml parameters to echarts axis options for data axis + * @param {Object} param - Dashboard .yml definition options + */ +const getDataAxisOptions = ({ format, precision, name }) => { + const formatter = getFormatter(format); + + return { + name, + nameLocation: 'center', // same as gitlab-ui's default + scale: true, + axisLabel: { + formatter: val => formatter(val, precision, maxDataAxisTickLength), + }, + }; +}; + +/** + * Converts .yml parameters to echarts y-axis options + * @param {Object} param - Dashboard .yml definition options + */ +export const getYAxisOptions = ({ + name = s__('Metrics|Values'), + format = defaultYAxisFormat, + precision = defaultYAxisPrecision, +} = {}) => { + return { + nameGap: 63, // larger gap than gitlab-ui's default to fit with formatted numbers + scale: true, + boundaryGap: yAxisBoundaryGap, + + ...getDataAxisOptions({ + name, + format, + precision, + }), + }; +}; + +// Chart grid + +/** + * Grid with enough room to display chart. + */ +export const getChartGrid = ({ left = chartGridLeft } = {}) => ({ left }); + +// Tooltip options + +export const getTooltipFormatter = ({ + format = defaultTooltipFormat, + precision = defaultTooltipPrecision, +} = {}) => { + const formatter = getFormatter(format); + return num => formatter(num, precision); +}; diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue index 1c39fb072d9..cba0a6da6a9 100644 --- a/app/assets/javascripts/monitoring/components/charts/time_series.vue +++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue @@ -4,7 +4,6 @@ import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import dateFormat from 'dateformat'; import { s__, __ } from '~/locale'; -import { getFormatter } from '~/lib/utils/unit_format'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import Icon from '~/vue_shared/components/icon.vue'; import { @@ -16,6 +15,7 @@ import { dateFormats, chartColorValues, } from '../../constants'; +import { getYAxisOptions, getChartGrid, getTooltipFormatter } from './options'; import { makeDataSeries } from '~/helpers/monitor_helper'; import { graphDataValidatorForValues } from '../../utils'; @@ -30,15 +30,13 @@ const deploymentYAxisCoords = { max: 100, }; -const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds +const THROTTLED_DATAZOOM_WAIT = 1000; // milliseconds const timestampToISODate = timestamp => new Date(timestamp).toISOString(); const events = { datazoom: 'datazoom', }; -const yValFormatter = getFormatter('number'); - export default { components: { GlAreaChart, @@ -167,14 +165,7 @@ export default { const option = omit(this.option, ['series', 'yAxis', 'xAxis']); const dataYAxis = { - name: this.yAxisLabel, - nameGap: 50, // same as gitlab-ui's default - nameLocation: 'center', // same as gitlab-ui's default - boundaryGap: [0.1, 0.1], - scale: true, - axisLabel: { - formatter: num => yValFormatter(num, 3), - }, + ...getYAxisOptions(this.graphData.yAxis), ...yAxis, }; @@ -204,6 +195,7 @@ export default { series: this.chartOptionSeries, xAxis: timeXAxis, yAxis: [dataYAxis, deploymentsYAxis], + grid: getChartGrid(), dataZoom: [this.dataZoomConfig], ...option, }; @@ -282,8 +274,9 @@ export default { }, }; }, - yAxisLabel() { - return `${this.graphData.y_label}`; + tooltipYFormatter() { + // Use same format as y-axis + return getTooltipFormatter({ format: this.graphData.yAxis?.format }); }, }, created() { @@ -315,12 +308,11 @@ export default { this.tooltip.commitUrl = deploy.commitUrl; } else { const { seriesName, color, dataIndex } = dataPoint; - const value = yValFormatter(yVal, 3); this.tooltip.content.push({ name: seriesName, dataIndex, - value, + value: this.tooltipYFormatter(yVal), color, }); } diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index a4073133028..a0bd45bef5e 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -19,7 +19,7 @@ import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import { s__ } from '~/locale'; import createFlash from '~/flash'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; +import { mergeUrlParams, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; import invalidUrl from '~/lib/utils/invalid_url'; import Icon from '~/vue_shared/components/icon.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; @@ -351,6 +351,10 @@ export default { }; redirectTo(mergeUrlParams(params, window.location.href)); }, + + refreshDashboard() { + refreshCurrentPage(); + }, }, addMetric: { title: s__('Metrics|Add metric'), @@ -438,7 +442,7 @@ export default { :label="s__('Metrics|Show last')" label-size="sm" label-for="monitor-time-window-dropdown" - class="col-sm-6 col-md-6 col-lg-4" + class="col-sm-auto col-md-auto col-lg-auto" > <date-time-picker ref="dateTimePicker" @@ -449,6 +453,18 @@ export default { /> </gl-form-group> + <gl-form-group class="col-sm-2 col-md-2 col-lg-1 refresh-dashboard-button"> + <gl-button + ref="refreshDashboardBtn" + v-gl-tooltip + variant="default" + :title="s__('Metrics|Reload this page')" + @click="refreshDashboard" + > + <icon name="repeat" /> + </gl-button> + </gl-form-group> + <gl-form-group v-if="hasHeaderButtons" label-for="prometheus-graphs-dropdown-buttons" diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index 82deaa7ccfd..0e97d50f317 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -1,5 +1,6 @@ import { slugify } from '~/lib/utils/text_utility'; import createGqClient, { fetchPolicies } from '~/lib/graphql'; +import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; export const gqClient = createGqClient( @@ -75,17 +76,37 @@ const mapToMetricsViewModel = (metrics, defaultLabel) => })); /** + * Maps an axis view model + * + * Defaults to a 2 digit precision and `number` format. It only allows + * formats in the SUPPORTED_FORMATS array. + * + * @param {Object} axis + */ +const mapToAxisViewModel = ({ name = '', format = SUPPORTED_FORMATS.number, precision = 2 }) => { + return { + name, + format: SUPPORTED_FORMATS[format] || SUPPORTED_FORMATS.number, + precision, + }; +}; + +/** * Maps a metrics panel to its view model * * @param {Object} panel - Metrics panel * @returns {Object} */ -const mapToPanelViewModel = ({ title = '', type, y_label, metrics = [] }) => { +const mapToPanelViewModel = ({ title = '', type, y_label, y_axis = {}, metrics = [] }) => { + // Both `y_axis.name` and `y_label` are supported for now + // https://gitlab.com/gitlab-org/gitlab/issues/208385 + const yAxis = mapToAxisViewModel({ name: y_label, ...y_axis }); // eslint-disable-line babel/camelcase return { title, type, - y_label, - metrics: mapToMetricsViewModel(metrics, y_label), + y_label: yAxis.name, // Changing y_label to yLabel is pending https://gitlab.com/gitlab-org/gitlab/issues/207198 + yAxis, + metrics: mapToMetricsViewModel(metrics, yAxis.name), }; }; diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js index 9c7c10d9864..f4e546e4d4e 100644 --- a/app/assets/javascripts/terminal/terminal.js +++ b/app/assets/javascripts/terminal/terminal.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import { throttle } from 'lodash'; import $ from 'jquery'; import { Terminal } from 'xterm'; import * as fit from 'xterm/lib/addons/fit/fit'; @@ -85,7 +85,7 @@ export default class GLTerminal { addScrollListener(onScrollLimit) { const viewport = this.container.querySelector('.xterm-viewport'); - const listener = _.throttle(() => { + const listener = throttle(() => { onScrollLimit({ canScrollUp: canScrollUp(viewport, SCROLL_MARGIN), canScrollDown: canScrollDown(viewport, SCROLL_MARGIN), diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index abfc81e681e..6244df1180e 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import _ from 'underscore'; +import { template as lodashTemplate, omit } from 'lodash'; import importU2FLibrary from './util'; import U2FError from './error'; @@ -37,7 +37,7 @@ export default class U2FAuthenticate { // Note: The server library fixes this behaviour in (unreleased) version 1.0.0. // This can be removed once we upgrade. // https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4 - this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge')); + this.signRequests = u2fParams.sign_requests.map(request => omit(request, 'challenge')); this.templates = { setup: '#js-authenticate-u2f-setup', @@ -74,7 +74,7 @@ export default class U2FAuthenticate { renderTemplate(name, params) { const templateString = $(this.templates[name]).html(); - const template = _.template(templateString); + const template = lodashTemplate(templateString); return this.container.html(template(params)); } diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 43c814c8070..f5a422727ad 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import _ from 'underscore'; +import { template as lodashTemplate } from 'lodash'; import importU2FLibrary from './util'; import U2FError from './error'; @@ -59,7 +59,7 @@ export default class U2FRegister { renderTemplate(name, params) { const templateString = $(this.templates[name]).html(); - const template = _.template(templateString); + const template = lodashTemplate(templateString); return this.container.html(template(params)); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue index 8e8e67228ed..ad80a51c5f9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue @@ -53,6 +53,7 @@ export default { .then(res => res.data) .then(data => { eventHub.$emit('UpdateWidgetData', data); + eventHub.$emit('MRWidgetUpdateRequested'); }) .catch(() => { this.isCancellingAutoMerge = false; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index ea83c61e275..91ac23f427d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -123,13 +123,15 @@ export default class MergeRequestStore { const currentUser = data.current_user; - this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path; - this.revertInForkPath = currentUser.revert_in_fork_path; - - this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; - this.canCreateIssue = currentUser.can_create_issue || false; - this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; - this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; + if (currentUser) { + this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path; + this.revertInForkPath = currentUser.revert_in_fork_path; + + this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; + this.canCreateIssue = currentUser.can_create_issue || false; + this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; + this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false; + } this.setState(data); } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3892d9dbd07..1c9bfe962f6 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -98,6 +98,14 @@ } } +.refresh-dashboard-button { + margin-top: 22px; + + @media(max-width: map-get($grid-breakpoints, sm)) { + margin-top: 0; + } +} + .metric-area { opacity: 0.25; } diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 2b7571e42b7..c9f46eb72c5 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -117,6 +117,7 @@ class ProfilesController < Profiles::ApplicationController :private_profile, :include_private_contributions, :timezone, + :job_title, status: [:emoji, :message] ) end diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index c4cc1adcd4e..aac6ecb07e4 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -66,7 +66,7 @@ module Projects [ :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_human_readable, :build_coverage_regex, :public_builds, - :auto_cancel_pending_pipelines, :ci_config_path, + :auto_cancel_pending_pipelines, :forward_deployment_enabled, :ci_config_path, auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy], ci_cd_settings_attributes: [:default_git_depth] ].tap do |list| diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 1104b676bc4..9da4dfd43b5 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -38,7 +38,7 @@ class Appearance < ApplicationRecord def single_appearance_row if self.class.any? - errors.add(:single_appearance_row, 'Only 1 appearances row can exist') + errors.add(:base, _('Only 1 appearances row can exist')) end end diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index a9856203cc0..98b8981754f 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -389,7 +389,7 @@ module ApplicationSettingImplementation def terms_exist return unless enforce_terms? - errors.add(:terms, "You need to set terms to be enforced") unless terms.present? + errors.add(:base, _('You need to set terms to be enforced')) unless terms.present? end def expire_performance_bar_allowed_user_ids_cache diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 8defe742ec4..f9a5f713814 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -148,7 +148,7 @@ module Ci def valid_file_format? unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym - errors.add(:file_format, 'Invalid file format with specified file type') + errors.add(:base, _('Invalid file format with specified file type')) end end diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index bdd7ad90fba..233920f4fe2 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Ingress < ApplicationRecord - VERSION = '1.29.3' + VERSION = '1.29.7' MODSECURITY_LOG_CONTAINER_NAME = 'modsecurity-log' self.table_name = 'clusters_applications_ingress' diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 6e890de924e..7f155a8d435 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -306,7 +306,7 @@ module Clusters .where.not(id: id) if duplicate_management_clusters.any? - errors.add(:environment_scope, "cannot add duplicated environment scope") + errors.add(:environment_scope, 'cannot add duplicated environment scope') end end @@ -380,7 +380,7 @@ module Clusters def restrict_modification if provider&.on_creation? - errors.add(:base, "cannot modify during creation") + errors.add(:base, _('Cannot modify provider during creation')) return false end diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb index d04a6408a21..0887236e65e 100644 --- a/app/models/concerns/has_repository.rb +++ b/app/models/concerns/has_repository.rb @@ -19,7 +19,7 @@ module HasRepository def valid_repo? repository.exists? rescue - errors.add(:path, _('Invalid repository path')) + errors.add(:base, _('Invalid repository path')) false end diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb index 7df6981a129..3ffb32f94fc 100644 --- a/app/models/concerns/milestoneable.rb +++ b/app/models/concerns/milestoneable.rb @@ -37,7 +37,7 @@ module Milestoneable private def milestone_is_valid - errors.add(:milestone_id, message: "is invalid") if respond_to?(:milestone_id) && milestone_id.present? && !milestone_available? + errors.add(:milestone_id, 'is invalid') if respond_to?(:milestone_id) && milestone_id.present? && !milestone_available? end end diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index f61a0bbc65b..dddf96837b7 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -77,7 +77,7 @@ module TimeTrackable return if time_spent.nil? || time_spent == :reset if time_spent < 0 && (time_spent.abs > original_total_time_spent) - errors.add(:time_spent, 'Time to subtract exceeds the total time spent') + errors.add(:base, _('Time to subtract exceeds the total time spent')) end end diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index 31c813edb67..a9844f627b7 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -105,7 +105,7 @@ class DeployToken < ApplicationRecord end def ensure_at_least_one_scope - errors.add(:base, "Scopes can't be blank") unless read_repository || read_registry + errors.add(:base, _("Scopes can't be blank")) unless read_repository || read_registry end def default_username diff --git a/app/models/description_version.rb b/app/models/description_version.rb index 05362a2f90b..f69564f4893 100644 --- a/app/models/description_version.rb +++ b/app/models/description_version.rb @@ -19,7 +19,13 @@ class DescriptionVersion < ApplicationRecord def exactly_one_issuable issuable_count = self.class.issuable_attrs.count { |attr| self["#{attr}_id"] } - errors.add(:base, "Exactly one of #{self.class.issuable_attrs.join(', ')} is required") if issuable_count != 1 + if issuable_count != 1 + errors.add( + :base, + _("Exactly one of %{attributes} is required") % + { attributes: self.class.issuable_attrs.join(', ') } + ) + end end end diff --git a/app/models/external_pull_request.rb b/app/models/external_pull_request.rb index 65ae8d95500..9c6d05f773a 100644 --- a/app/models/external_pull_request.rb +++ b/app/models/external_pull_request.rb @@ -78,7 +78,7 @@ class ExternalPullRequest < ApplicationRecord def not_from_fork if from_fork? - errors.add(:base, 'Pull requests from fork are not supported') + errors.add(:base, _('Pull requests from fork are not supported')) end end diff --git a/app/models/milestone_release.rb b/app/models/milestone_release.rb index 713c8ef7b94..0a6165c8254 100644 --- a/app/models/milestone_release.rb +++ b/app/models/milestone_release.rb @@ -11,6 +11,6 @@ class MilestoneRelease < ApplicationRecord def same_project_between_milestone_and_release return if milestone&.project_id == release&.project_id - errors.add(:base, 'does not have the same project as the milestone') + errors.add(:base, _('Release does not have the same project as the milestone')) end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index fbc010c6b7c..631bd930e2f 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -376,7 +376,7 @@ class Namespace < ApplicationRecord def nesting_level_allowed if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED - errors.add(:parent_id, "has too deep level of nesting") + errors.add(:parent_id, 'has too deep level of nesting') end end diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb index b26a3025b61..39e177e8bd8 100644 --- a/app/models/project_ci_cd_setting.rb +++ b/app/models/project_ci_cd_setting.rb @@ -31,7 +31,7 @@ class ProjectCiCdSetting < ApplicationRecord end def forward_deployment_enabled? - super && ::Feature.enabled?(:forward_deployment_enabled, project) + super && ::Feature.enabled?(:forward_deployment_enabled, project, default_enabled: true) end private diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 7cdbb124dee..2bf14a6ed25 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -168,7 +168,7 @@ class IssueTrackerService < Service return if project.blank? if project.services.external_issue_trackers.where.not(id: id).any? - errors.add(:base, 'Another issue tracker is already in use. Only one issue tracker service can be active at a time') + errors.add(:base, _('Another issue tracker is already in use. Only one issue tracker service can be active at a time')) end end end diff --git a/app/models/prometheus_alert.rb b/app/models/prometheus_alert.rb index 1014231102f..1dc7dc73e31 100644 --- a/app/models/prometheus_alert.rb +++ b/app/models/prometheus_alert.rb @@ -69,13 +69,13 @@ class PrometheusAlert < ApplicationRecord def require_valid_environment_project! return if project == environment&.project - errors.add(:environment, "invalid project") + errors.add(:environment, 'invalid project') end def require_valid_metric_project! return if prometheus_metric&.common? return if project == prometheus_metric&.project - errors.add(:prometheus_metric, "invalid project") + errors.add(:prometheus_metric, 'invalid project') end end diff --git a/app/models/resource_event.rb b/app/models/resource_event.rb index 9b3a211ad43..2c0052b0be3 100644 --- a/app/models/resource_event.rb +++ b/app/models/resource_event.rb @@ -37,6 +37,9 @@ class ResourceEvent < ApplicationRecord return true if issuable_count == 1 end - errors.add(:base, "Exactly one of #{self.class.issuable_attrs.join(', ')} is required") + errors.add( + :base, _("Exactly one of %{attributes} is required") % + { attributes: self.class.issuable_attrs.join(', ') } + ) end end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 0427d5b9ca7..f3a9293376f 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -111,7 +111,10 @@ class SentNotification < ApplicationRecord note = create_reply('Test', dryrun: true) unless note.valid? - self.errors.add(:base, "Note parameters are invalid: #{note.errors.full_messages.to_sentence}") + self.errors.add( + :base, _("Note parameters are invalid: %{errors}") % + { errors: note.errors.full_messages.to_sentence } + ) end end diff --git a/app/models/timelog.rb b/app/models/timelog.rb index 4ddaf6bcb86..f52dd74d4c9 100644 --- a/app/models/timelog.rb +++ b/app/models/timelog.rb @@ -28,9 +28,9 @@ class Timelog < ApplicationRecord def issuable_id_is_present if issue_id && merge_request_id - errors.add(:base, 'Only Issue ID or Merge Request ID is required') + errors.add(:base, _('Only Issue ID or Merge Request ID is required')) elsif issuable.nil? - errors.add(:base, 'Issue or Merge Request ID is required') + errors.add(:base, _('Issue or Merge Request ID is required')) end end diff --git a/app/models/user.rb b/app/models/user.rb index f3db0522edc..81cabc67c3b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -162,6 +162,7 @@ class User < ApplicationRecord has_one :status, class_name: 'UserStatus' has_one :user_preference + has_one :user_detail # # Validations @@ -259,8 +260,10 @@ class User < ApplicationRecord delegate :sourcegraph_enabled, :sourcegraph_enabled=, to: :user_preference delegate :setup_for_company, :setup_for_company=, to: :user_preference delegate :render_whitespace_in_code, :render_whitespace_in_code=, to: :user_preference + delegate :job_title, :job_title=, to: :user_detail, allow_nil: true accepts_nested_attributes_for :user_preference, update_only: true + accepts_nested_attributes_for :user_detail, update_only: true state_machine :state, initial: :active do event :block do @@ -1619,6 +1622,10 @@ class User < ApplicationRecord super.presence || build_user_preference end + def user_detail + super.presence || build_user_detail + end + def todos_limited_to(ids) todos.where(id: ids) end diff --git a/app/models/user_detail.rb b/app/models/user_detail.rb new file mode 100644 index 00000000000..1621f336111 --- /dev/null +++ b/app/models/user_detail.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class UserDetail < ApplicationRecord + belongs_to :user + + validates :job_title, presence: true, length: { maximum: 200 } +end diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index a72179f40ad..e6be364b48f 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -88,6 +88,15 @@ = _("New pipelines will cancel older, pending pipelines on the same branch") = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank' + .form-group + .form-check + = f.check_box :forward_deployment_enabled, { class: 'form-check-input' } + = f.label :forward_deployment_enabled, class: 'form-check-label' do + %strong= _("Skip older, pending deployment jobs") + .form-text.text-muted + = _("When a deployment job is successful, skip older deployment jobs that are still pending") + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'skip-older-pending-deployment-jobs'), target: '_blank' + %hr .form-group = f.label :build_coverage_regex, _("Test coverage parsing"), class: 'label-bold' |