diff options
37 files changed, 227 insertions, 167 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/anomaly.vue b/app/assets/javascripts/monitoring/components/charts/anomaly.vue index 64704701d1a..bcbc1dad89d 100644 --- a/app/assets/javascripts/monitoring/components/charts/anomaly.vue +++ b/app/assets/javascripts/monitoring/components/charts/anomaly.vue @@ -1,5 +1,5 @@ <script> -import { flatten, isNumber } from 'underscore'; +import { flattenDeep, isNumber } from 'lodash'; import { GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { roundOffFloat } from '~/lib/utils/common_utils'; import { hexToRgb } from '~/lib/utils/color_utils'; @@ -77,7 +77,7 @@ export default { * This offset is the lowest value. */ yOffset() { - const values = flatten(this.series.map(ser => ser.data.map(([, y]) => y))); + const values = flattenDeep(this.series.map(ser => ser.data.map(([, y]) => y))); const min = values.length ? Math.floor(Math.min(...values)) : 0; return min < 0 ? -min : 0; }, diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue index 0d442f14aea..e77bf08be7f 100644 --- a/app/assets/javascripts/monitoring/components/charts/time_series.vue +++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue @@ -1,5 +1,5 @@ <script> -import _ from 'underscore'; +import { omit } from 'lodash'; import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import dateFormat from 'dateformat'; @@ -140,7 +140,7 @@ export default { return (this.option.series || []).concat(this.scatterSeries ? [this.scatterSeries] : []); }, chartOptions() { - const option = _.omit(this.option, 'series'); + const option = omit(this.option, 'series'); return { series: this.chartOptionSeries, xAxis: { diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 211990f3d7c..14f39c50a2d 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,5 +1,5 @@ <script> -import _ from 'underscore'; +import { debounce, pickBy } from 'lodash'; import { mapActions, mapState, mapGetters } from 'vuex'; import VueDraggable from 'vuedraggable'; import { @@ -15,12 +15,13 @@ import { import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import { s__ } from '~/locale'; import createFlash from '~/flash'; -import Icon from '~/vue_shared/components/icon.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import invalidUrl from '~/lib/utils/invalid_url'; +import Icon from '~/vue_shared/components/icon.vue'; +import { getTimeRange } from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; +import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; -import DateTimePicker from './date_time_picker/date_time_picker.vue'; import GraphGroup from './graph_group.vue'; import EmptyState from './empty_state.vue'; import GroupEmptyState from './group_empty_state.vue'; @@ -28,11 +29,10 @@ import DashboardsDropdown from './dashboards_dropdown.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; import { getAddMetricTrackingOptions } from '../utils'; -import { getTimeRange } from './date_time_picker/date_time_picker_lib'; import { datePickerTimeWindows, metricStates } from '../constants'; -const defaultTimeDiff = getTimeRange(); +const defaultTimeRange = getTimeRange(); export default { components: { @@ -190,8 +190,8 @@ export default { return { state: 'gettingStarted', formIsValid: null, - startDate: getParameterValues('start')[0] || defaultTimeDiff.start, - endDate: getParameterValues('end')[0] || defaultTimeDiff.end, + startDate: getParameterValues('start')[0] || defaultTimeRange.start, + endDate: getParameterValues('end')[0] || defaultTimeRange.end, hasValidDates: true, datePickerTimeWindows, isRearrangingPanels: false, @@ -288,13 +288,13 @@ export default { 'Metrics|Link contains an invalid time window, please verify the link to see the requested time range.', ), ); - this.startDate = defaultTimeDiff.start; - this.endDate = defaultTimeDiff.end; + this.startDate = defaultTimeRange.start; + this.endDate = defaultTimeRange.end; }, generateLink(group, title, yLabel) { const dashboard = this.currentDashboard || this.firstDashboard.path; - const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null); + const params = pickBy({ dashboard, group, title, y_label: yLabel }, value => value != null); return mergeUrlParams(params, window.location.href); }, hideAddMetricModal() { @@ -306,7 +306,7 @@ export default { setFormValidity(isValid) { this.formIsValid = isValid; }, - debouncedEnvironmentsSearch: _.debounce(function environmentsSearchOnInput(searchTerm) { + debouncedEnvironmentsSearch: debounce(function environmentsSearchOnInput(searchTerm) { this.setEnvironmentsSearchTerm(searchTerm); }, 500), submitCustomMetricsForm() { @@ -427,6 +427,7 @@ export default { class="col-sm-6 col-md-6 col-lg-4" > <date-time-picker + ref="dateTimePicker" :start="startDate" :end="endDate" :time-windows="datePickerTimeWindows" diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue index 10c081070d7..c79e43c7c29 100644 --- a/app/assets/javascripts/monitoring/components/embed.vue +++ b/app/assets/javascripts/monitoring/components/embed.vue @@ -3,7 +3,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; import { sidebarAnimationDuration } from '../constants'; -import { getTimeRange } from './date_time_picker/date_time_picker_lib'; +import { getTimeRange } from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; let sidebarMutationObserver; diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index ec6a41d0540..a2849dcfddb 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -1,6 +1,6 @@ <script> import { mapState } from 'vuex'; -import _ from 'underscore'; +import { pickBy } from 'lodash'; import { GlDropdown, GlDropdownItem, @@ -90,7 +90,7 @@ export default { getGraphAlerts(queries) { if (!this.allAlerts) return {}; const metricIdsForChart = queries.map(q => q.metricId); - return _.pick(this.allAlerts, alert => metricIdsForChart.includes(alert.metricId)); + return pickBy(this.allAlerts, alert => metricIdsForChart.includes(alert.metricId)); }, getGraphAlertValues(queries) { return Object.values(this.getGraphAlerts(queries)); diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index 3300d2032d0..616d2e9bfd8 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import { omit } from 'lodash'; export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`; @@ -11,7 +11,7 @@ export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`; */ export const normalizeMetric = (metric = {}) => - _.omit( + omit( { ...metric, metric_id: uniqMetricsId(metric), diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index 3a18a494cad..7d4c162473f 100644 --- a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -122,30 +122,28 @@ export default { }; </script> <template> - <gl-dropdown - ref="dropdown" - :text="timeWindowText" - menu-class="time-window-dropdown-menu" - class="js-time-window-dropdown" - > - <div class="d-flex justify-content-between time-window-dropdown-menu-container"> + <gl-dropdown :text="timeWindowText" class="date-time-picker" menu-class="date-time-picker-menu"> + <div class="d-flex justify-content-between gl-p-2"> <gl-form-group :label="__('Custom range')" label-for="custom-from-time" - class="custom-time-range-form-group col-md-7 p-0 m-0" + label-class="gl-pb-1" + class="custom-time-range-form-group col-md-7 gl-pl-1 gl-pr-0 m-0" > - <date-time-picker-input - id="custom-time-from" - v-model="startInput" - :label="__('From')" - :state="startInputValid" - /> - <date-time-picker-input - id="custom-time-to" - v-model="endInput" - :label="__('To')" - :state="endInputValid" - /> + <div class="gl-pt-2"> + <date-time-picker-input + id="custom-time-from" + v-model="startInput" + :label="__('From')" + :state="startInputValid" + /> + <date-time-picker-input + id="custom-time-to" + v-model="endInput" + :label="__('To')" + :state="endInputValid" + /> + </div> <gl-form-group> <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button> <gl-button variant="success" :disabled="!isValid" @click="apply()"> @@ -153,12 +151,10 @@ export default { </gl-button> </gl-form-group> </gl-form-group> - <gl-form-group - :label="__('Quick range')" - label-for="group-id-dropdown" - label-align="center" - class="col-md-4 p-0 m-0" - > + <gl-form-group label-for="group-id-dropdown" class="col-md-5 gl-pl-1 gl-pr-1 m-0"> + <template #label> + <span class="gl-pl-5">{{ __('Quick range') }}</span> + </template> <gl-dropdown-item v-for="(timeWindow, key) in timeWindows" :key="key" diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_input.vue index b27a379c46a..f19f8bd46b3 100644 --- a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_input.vue @@ -1,5 +1,5 @@ <script> -import _ from 'underscore'; +import { uniqueId } from 'lodash'; import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; import { dateFormats } from './date_time_picker_lib'; @@ -35,7 +35,7 @@ export default { id: { type: String, required: false, - default: () => _.uniqueId('dateTimePicker_'), + default: () => uniqueId('dateTimePicker_'), }, }, data() { diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_lib.js b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js index 604b17baab9..685115b92dd 100644 --- a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_lib.js +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js @@ -8,6 +8,15 @@ import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; */ const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/; +/** + * A key-value pair of "time windows". + * + * A time window is a representation of period of time that starts + * some time in past until now. Keys are only used for easy reference. + * + * It is represented as user friendly `label` and number of `seconds` + * to be substracted from now. + */ export const defaultTimeWindows = { thirtyMinutes: { label: __('30 minutes'), @@ -58,6 +67,17 @@ export const isValidDate = dateString => { } }; +/** + * For a given time window key (e.g. `threeHours`) and key-value pair + * object of time windows. + * + * Returns a date time range with start and end. + * + * @param {String} timeWindowKey - A key in the object of time windows. + * @param {Object} timeWindows - A key-value pair of time windows, + * with a second duration and a label. + * @returns An object with time range, start and end dates, in ISO format. + */ export const getTimeRange = (timeWindowKey, timeWindows = defaultTimeWindows) => { let difference; if (timeWindows[timeWindowKey]) { diff --git a/app/assets/stylesheets/components/date_time_picker.scss b/app/assets/stylesheets/components/date_time_picker.scss new file mode 100644 index 00000000000..21f085cdaf1 --- /dev/null +++ b/app/assets/stylesheets/components/date_time_picker.scss @@ -0,0 +1,5 @@ +.date-time-picker { + .date-time-picker-menu { + width: 400px; + } +} diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss index e759423f6e5..3e6313173b8 100644 --- a/app/assets/stylesheets/pages/prometheus.scss +++ b/app/assets/stylesheets/pages/prometheus.scss @@ -46,32 +46,6 @@ } } -.prometheus-graphs-header { - .time-window-dropdown-menu { - padding: $gl-padding $gl-padding 0 $gl-padding-12; - } - - .time-window-dropdown-menu-container { - width: 360px; - } - - .custom-time-range-form-group > label { - padding-bottom: $gl-padding; - } - - .monitor-environment-dropdown-menu { - &.show { - display: flex; - flex-direction: column; - overflow: hidden; - } - - .no-matches-message { - padding: $gl-padding-8 $gl-padding-12; - } - } -} - .prometheus-panel { margin-top: 20px; } diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md index dd0e4658ee9..a5a102d888b 100644 --- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md +++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md @@ -105,7 +105,7 @@ The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb` The two Active Directory specific values are `active_directory: true` and `uid: 'sAMAccountName'`. `sAMAccountName` is an attribute returned by Active Directory used for GitLab usernames. See the example output from `ldapsearch` for a full list of attributes a "person" object (user) has in **AD** - [`ldapsearch` example](#using-ldapsearch-unix) -> Both group_base and admin_group configuration options are only available in GitLab Enterprise Edition. See [GitLab EE - LDAP Features](../how_to_configure_ldap_gitlab_ee/index.html#gitlab-enterprise-edition---ldap-features) **(STARTER ONLY)** +> Both group_base and admin_group configuration options are only available in GitLab Enterprise Edition. See [GitLab EE - LDAP Features](../how_to_configure_ldap_gitlab_ee/index.md#gitlab-enterprise-edition---ldap-features) **(STARTER ONLY)** ### Example `gitlab.rb` LDAP diff --git a/doc/administration/geo/replication/version_specific_updates.md b/doc/administration/geo/replication/version_specific_updates.md index 7ce38d80c88..772defe0191 100644 --- a/doc/administration/geo/replication/version_specific_updates.md +++ b/doc/administration/geo/replication/version_specific_updates.md @@ -134,7 +134,7 @@ sudo gitlab-ctl reconfigure ``` If you do not perform this step, you may find that two-factor authentication -[is broken following DR](../disaster_recovery/index.html#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken). +[is broken following DR](../disaster_recovery/index.md#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken). To prevent SSH requests to the newly promoted **primary** node from failing due to SSH host key mismatch when updating the **primary** node domain's DNS record diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 99f4033e8fd..04974c6ea8b 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -369,7 +369,7 @@ If you need to manually remove job artifacts associated with multiple jobs while NOTE: **NOTE:** This step will also erase artifacts that users have chosen to - ["keep"](../user/project/pipelines/job_artifacts.html#browsing-artifacts). + ["keep"](../user/project/pipelines/job_artifacts.md#browsing-artifacts). ```ruby builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 62bacf9791e..14f2d254a7c 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -133,7 +133,7 @@ To use an external Prometheus server: ``` 1. Install and set up a dedicated Prometheus instance, if necessary, using the [official installation instructions](https://prometheus.io/docs/prometheus/latest/installation/). -1. Add the Prometheus server IP address to the [monitoring IP whitelist](../ip_whitelist.html). For example: +1. Add the Prometheus server IP address to the [monitoring IP whitelist](../ip_whitelist.md). For example: ```ruby gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1'] diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index d3415913bab..3804319f60d 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -744,7 +744,7 @@ project or branch name. Special characters can include: To get around this, you can [change the group path](../../user/group/index.md#changing-a-groups-path), [change the project path](../../user/project/settings/index.md#renaming-a-repository) or change the -branch name. Another option is to create a [push rule](../../push_rules/push_rules.html) to prevent +branch name. Another option is to create a [push rule](../../push_rules/push_rules.md) to prevent this at the instance level. ### Image push errors diff --git a/doc/api/audit_events.md b/doc/api/audit_events.md index e451b975d42..a06a300e149 100644 --- a/doc/api/audit_events.md +++ b/doc/api/audit_events.md @@ -120,7 +120,7 @@ Example response: > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34078) in GitLab 12.5. -The Group Audit Events API allows you to retrieve [group audit events](../administration/audit_events.html#group-events-starter). +The Group Audit Events API allows you to retrieve [group audit events](../administration/audit_events.md#group-events-starter). To retrieve group audit events using the API, you must [authenticate yourself](README.html#authentication) as an Administrator or an owner of the group. diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md index 22ddab4bf79..41dbedf1ee2 100644 --- a/doc/api/award_emoji.md +++ b/doc/api/award_emoji.md @@ -10,7 +10,7 @@ Emoji can be awarded on the following (known as "awardables"): - [Merge requests](../user/project/merge_requests/index.md) ([API](merge_requests.md)). - [Snippets](../user/snippets.md) ([API](snippets.md)). -Emoji can also [be awarded](../user/award_emojis.html#award-emoji-for-comments) on comments (also known as notes). See also [Notes API](notes.md). +Emoji can also [be awarded](../user/award_emojis.md#award-emoji-for-comments) on comments (also known as notes). See also [Notes API](notes.md). ## Issues, merge requests, and snippets diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 4bc28dd342f..faa30043423 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -128,7 +128,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.com/api/v4/pro ## Compare branches, tags or commits This endpoint can be accessed without authentication if the repository is -publicly accessible. Note that diffs could have an empty diff string if [diff limits](../development/diffs.html#diff-limits) are reached. +publicly accessible. Note that diffs could have an empty diff string if [diff limits](../development/diffs.md#diff-limits) are reached. ``` GET /projects/:id/repository/compare diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index 488d7ae6762..86ac70ecef6 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -341,7 +341,7 @@ This also applies when using links in between translated sentences, otherwise th ```js {{ sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), { - linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">' + linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">', linkEnd: '</a>', }) }} diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 7fa84bf45bd..38376f4334f 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -93,7 +93,7 @@ This creates a `.git` directory that contains the Git configuration files. Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository) and [send changes to GitLab.com](#send-changes-to-gitlabcom). You will also need to -[create a new project in GitLab](../gitlab-basics/create-project.html#push-to-create-a-new-project) +[create a new project in GitLab](../gitlab-basics/create-project.md#push-to-create-a-new-project) for your Git repository. ### Clone a repository diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 9a32f0adfaa..599d65805fc 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -119,7 +119,7 @@ The following table depicts the various user permission levels in a project. | Configure project hooks | | | | ✓ | ✓ | | Manage Runners | | | | ✓ | ✓ | | Manage job triggers | | | | ✓ | ✓ | -| Manage variables | | | | ✓ | ✓ | +| Manage CI/CD variables | | | | ✓ | ✓ | | Manage GitLab Pages | | | | ✓ | ✓ | | Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | | Remove GitLab Pages | | | | ✓ | ✓ | @@ -223,6 +223,7 @@ group. | Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ | | Create subgroup | | | | ✓ (1) | ✓ | | Edit group | | | | | ✓ | +| Manage group level CI/CD variables | | | | | ✓ | | Manage group members | | | | | ✓ | | Remove group | | | | | ✓ | | Delete group epic **(ULTIMATE)** | | | | | ✓ | diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 6c6bb4034cf..d8938d81637 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -134,7 +134,7 @@ Multiple metrics can be displayed on the same chart if the fields **Name**, **Ty #### Query Variables -GitLab supports a limited set of [CI variables](../../../ci/variables/README.html) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `CI_ENVIRONMENT_SLUG`. The supported variables are: +GitLab supports a limited set of [CI variables](../../../ci/variables/README.md) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `CI_ENVIRONMENT_SLUG`. The supported variables are: - CI_ENVIRONMENT_SLUG - KUBE_NAMESPACE diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md index fd4a01043ef..b8983a06180 100644 --- a/doc/user/project/issues/design_management.md +++ b/doc/user/project/issues/design_management.md @@ -34,7 +34,7 @@ to be enabled: and enable **Git Large File Storage**. Design Management requires that projects are using -[hashed storage](../../../administration/repository_storage_types.html#hashed-storage) +[hashed storage](../../../administration/repository_storage_types.md#hashed-storage) (the default storage type since v10.0). ### Feature Flags diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md index 79fb2ea50a0..214937d4db2 100644 --- a/doc/user/project/members/share_project_with_groups.md +++ b/doc/user/project/members/share_project_with_groups.md @@ -54,4 +54,4 @@ It is possible to prevent projects in a group from [sharing a project with another group](../members/share_project_with_groups.md). This allows for tighter control over project access. -Learn more about [Share with group lock](../../group/index.html#share-with-group-lock). +Learn more about [Share with group lock](../../group/index.md#share-with-group-lock). diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index ca888c69b37..3998dc69ad5 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -58,7 +58,7 @@ if the job surpasses the threshold, it is marked as failed. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/17221) in GitLab 10.7. Project defined timeout (either specific timeout set by user or the default -60 minutes timeout) may be [overridden on Runner level](../../../ci/runners/README.html#setting-maximum-job-timeout-for-a-runner). +60 minutes timeout) may be [overridden on Runner level](../../../ci/runners/README.md#setting-maximum-job-timeout-for-a-runner). ## Maximum artifacts size **(CORE ONLY)** diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 4e55f55dd28..bc1053f5bf2 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -86,7 +86,7 @@ related to the project by selecting the **Disable email notifications** checkbox Set up your project's merge request settings: -- Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)). +- Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.md)). - Add merge request [description templates](../description_templates.md#description-templates). - Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **(STARTER)** - Enable [merge only if pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md). diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 195abae3783..c2cd6deb502 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -7,8 +7,8 @@ import statusCodes from '~/lib/utils/http_status'; import { metricStates } from '~/monitoring/constants'; import Dashboard from '~/monitoring/components/dashboard.vue'; +import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue'; -import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue'; import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; diff --git a/spec/frontend/monitoring/components/dashboard_time_window_spec.js b/spec/frontend/monitoring/components/dashboard_time_window_spec.js index 29cca695093..e9f2a67983a 100644 --- a/spec/frontend/monitoring/components/dashboard_time_window_spec.js +++ b/spec/frontend/monitoring/components/dashboard_time_window_spec.js @@ -53,7 +53,7 @@ describe('dashboard time window', () => { .$nextTick() .then(() => { const timeWindowDropdownItems = wrapper - .find('.js-time-window-dropdown') + .find({ ref: 'dateTimePicker' }) .findAll(GlDropdownItem); const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper => diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js index 9cac63ad725..2c5bb86d8a5 100644 --- a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js +++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils'; -import DateTimePickerInput from '~/monitoring/components/date_time_picker/date_time_picker_input.vue'; +import DateTimePickerInput from '~/vue_shared/components/date_time_picker/date_time_picker_input.vue'; const inputLabel = 'This is a label'; const inputValue = 'something'; diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_lib_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_lib_spec.js index 9c0f66427ae..b7b024183e1 100644 --- a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_lib_spec.js +++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_lib_spec.js @@ -1,4 +1,4 @@ -import * as dateTimePickerLib from '~/monitoring/components/date_time_picker/date_time_picker_lib'; +import * as dateTimePickerLib from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; describe('date time picker lib', () => { describe('isValidDate', () => { diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js index 3b37da5bcd6..98dfbe9cd14 100644 --- a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js +++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; -import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue'; -import { defaultTimeWindows } from '~/monitoring/components/date_time_picker/date_time_picker_lib'; +import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; +import { defaultTimeWindows } from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; const timeWindowsCount = Object.entries(defaultTimeWindows).length; const start = '2019-10-10T07:00:00.000Z'; diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index d08ca3983af..8b62e332407 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -482,6 +482,18 @@ describe Repository do end end + describe "#root_ref_sha" do + let(:commit) { double("commit", sha: "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3") } + + subject { repository.root_ref_sha } + + before do + allow(repository).to receive(:commit).with(repository.root_ref) { commit } + end + + it { is_expected.to eq(commit.sha) } + end + describe '#can_be_merged?' do context 'mergeable branches' do subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 80b8d36aa07..1f1ccff2ba8 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -10,10 +10,12 @@ describe NotificationService, :mailer do let(:notification) { described_class.new } let(:assignee) { create(:user) } - around do |example| - perform_enqueued_jobs do - example.run - end + around(:example, :deliver_mails_inline) do |example| + # This is a temporary `around` hook until all the examples check the + # background jobs queue instead of the delivered emails array. + # `perform_enqueued_jobs` makes the ActiveJob jobs (e.g. mailer jobs) run inline + # compared to `Sidekiq::Testing.inline!` which makes the Sidekiq jobs run inline. + perform_enqueued_jobs { example.run } end shared_examples 'altered milestone notification on issue' do @@ -187,26 +189,41 @@ describe NotificationService, :mailer do describe 'Keys' do describe '#new_key' do let(:key_options) { {} } - let!(:key) { create(:personal_key, key_options) } + let!(:key) { build_stubbed(:personal_key, key_options) } + + subject { notification.new_key(key) } - it { expect(notification.new_key(key)).to be_truthy } + it "sends email to key owner" do + expect { subject }.to have_enqueued_email(key.id, mail: "new_ssh_key_email") + end - describe 'never emails the ghost user' do + describe "never emails the ghost user" do let(:key_options) { { user: User.ghost } } - it { should_not_email_anyone } + it "does not send email to key owner" do + expect { subject }.not_to have_enqueued_email(key.id, mail: "new_ssh_key_email") + end end end end describe 'GpgKeys' do describe '#new_gpg_key' do - let!(:key) { create(:gpg_key) } + let(:key_options) { {} } + let(:key) { create(:gpg_key, key_options) } + + subject { notification.new_gpg_key(key) } + + it "sends email to key owner" do + expect { subject }.to have_enqueued_email(key.id, mail: "new_gpg_key_email") + end - it { expect(notification.new_gpg_key(key)).to be_truthy } + describe "never emails the ghost user" do + let(:key_options) { { user: User.ghost } } - it 'sends email to key owner' do - expect { notification.new_gpg_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1) + it "does not send email to key owner" do + expect { subject }.not_to have_enqueued_email(key.id, mail: "new_gpg_key_email") + end end end end @@ -215,10 +232,10 @@ describe NotificationService, :mailer do describe '#access_token_about_to_expire' do let_it_be(:user) { create(:user) } - it 'sends email to the token owner' do - expect(notification.access_token_about_to_expire(user)).to be_truthy + subject { notification.access_token_about_to_expire(user) } - should_email user + it 'sends email to the token owner' do + expect { subject }.to have_enqueued_email(user, mail: "access_token_about_to_expire_email") end end end @@ -231,6 +248,8 @@ describe NotificationService, :mailer do let(:author) { create(:user) } let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') } + subject { notification.new_note(note) } + before do build_team(project) project.add_maintainer(issue.author) @@ -260,32 +279,23 @@ describe NotificationService, :mailer do reset_delivered_emails! end - it do - expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times - - notification.new_note(note) - - should_email(@u_watcher) - should_email(note.noteable.author) - should_email(note.noteable.assignees.first) - should_email(@u_custom_global) - should_email(@u_mentioned) - should_email(@subscriber) - should_email(@watcher_and_subscriber) - should_email(@subscribed_participant) - should_email(@u_custom_off) - should_email(@unsubscribed_mentioned) - should_not_email(@u_guest_custom) - should_not_email(@u_guest_watcher) - should_not_email(note.author) - should_not_email(@u_participating) - should_not_email(@u_disabled) - should_not_email(@unsubscriber) - should_not_email(@u_outsider_mentioned) - should_not_email(@u_lazy_participant) + it 'sends emails to recipients' do + subject + + expect_delivery_jobs_count(10) + expect_enqueud_email(@u_watcher.id, note.id, nil, mail: "note_issue_email") + expect_enqueud_email(note.noteable.author.id, note.id, nil, mail: "note_issue_email") + expect_enqueud_email(note.noteable.assignees.first.id, note.id, nil, mail: "note_issue_email") + expect_enqueud_email(@u_custom_global.id, note.id, nil, mail: "note_issue_email") + expect_enqueud_email(@u_mentioned.id, note.id, "mentioned", mail: "note_issue_email") + expect_enqueud_email(@subscriber.id, note.id, "subscribed", mail: "note_issue_email") + expect_enqueud_email(@watcher_and_subscriber.id, note.id, "subscribed", mail: "note_issue_email") + expect_enqueud_email(@subscribed_participant.id, note.id, "subscribed", mail: "note_issue_email") + expect_enqueud_email(@u_custom_off.id, note.id, nil, mail: "note_issue_email") + expect_enqueud_email(@unsubscribed_mentioned.id, note.id, "mentioned", mail: "note_issue_email") end - it "emails the note author if they've opted into notifications about their activity" do + it "emails the note author if they've opted into notifications about their activity", :deliver_mails_inline do note.author.notified_of_own_activity = true notification.new_note(note) @@ -294,7 +304,7 @@ describe NotificationService, :mailer do expect(find_email_for(note.author)).to have_header('X-GitLab-NotificationReason', 'own_activity') end - it_behaves_like 'project emails are disabled' do + it_behaves_like 'project emails are disabled', check_delivery_jobs_queue: true do let(:notification_target) { note } let(:notification_trigger) { notification.new_note(note) } end @@ -302,21 +312,21 @@ describe NotificationService, :mailer do it 'filters out "mentioned in" notes' do mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author) + reset_delivered_emails! - expect(Notify).not_to receive(:note_issue_email) notification.new_note(mentioned_note) + + expect_no_delivery_jobs end context 'participating' do context 'by note' do before do - reset_delivered_emails! note.author = @u_lazy_participant note.save - notification.new_note(note) end - it { should_not_email(@u_lazy_participant) } + it { expect { subject }.not_to have_enqueued_email(@u_lazy_participant.id, note.id, mail: "note_issue_email") } end end end @@ -335,7 +345,7 @@ describe NotificationService, :mailer do end shared_examples 'new note notifications' do - it do + it 'sends notifications', :deliver_mails_inline do notification.new_note(note) should_email(note.noteable.author) @@ -359,7 +369,7 @@ describe NotificationService, :mailer do it_behaves_like 'new note notifications' - it_behaves_like 'project emails are disabled' do + it_behaves_like 'project emails are disabled', check_delivery_jobs_queue: true do let(:notification_target) { note } let(:notification_trigger) { notification.new_note(note) } end @@ -378,13 +388,13 @@ describe NotificationService, :mailer do notification.new_note(note) - should_email(user) + expect_enqueud_email(user.id, note.id, nil, mail: "note_issue_email") end end end end - context 'confidential issue note' do + context 'confidential issue note', :deliver_mails_inline do let(:project) { create(:project, :public) } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -441,7 +451,7 @@ describe NotificationService, :mailer do end end - context 'issue note mention' do + context 'issue note mention', :deliver_mails_inline do let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:mentioned_issue) { create(:issue, assignees: issue.assignees) } @@ -507,7 +517,7 @@ describe NotificationService, :mailer do end end - context 'project snippet note' do + context 'project snippet note', :deliver_mails_inline do let!(:project) { create(:project, :public) } let(:snippet) { create(:project_snippet, project: project, author: create(:user)) } let(:author) { create(:user) } @@ -551,7 +561,7 @@ describe NotificationService, :mailer do end end - context 'personal snippet note' do + context 'personal snippet note', :deliver_mails_inline do let(:snippet) { create(:personal_snippet, :public, author: @u_snippet_author) } let(:note) { create(:note_on_personal_snippet, noteable: snippet, note: '@mentioned note', author: @u_note_author) } @@ -600,7 +610,7 @@ describe NotificationService, :mailer do end end - context 'commit note' do + context 'commit note', :deliver_mails_inline do let(:project) { create(:project, :public, :repository) } let(:note) { create(:note_on_commit, project: project) } @@ -659,7 +669,7 @@ describe NotificationService, :mailer do end end - context "merge request diff note" do + context "merge request diff note", :deliver_mails_inline do let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request, source_project: project, assignees: [user], author: create(:user)) } @@ -691,11 +701,11 @@ describe NotificationService, :mailer do end end - describe '#send_new_release_notifications' do + describe '#send_new_release_notifications', :deliver_mails_inline, :sidekiq_inline do context 'when recipients for a new release exist' do let(:release) { create(:release) } - it 'calls new_release_email for each relevant recipient', :sidekiq_might_not_need_inline do + it 'calls new_release_email for each relevant recipient' do user_1 = create(:user) user_2 = create(:user) user_3 = create(:user) @@ -712,7 +722,7 @@ describe NotificationService, :mailer do end end - describe 'Participating project notification settings have priority over group and global settings if available' do + describe 'Participating project notification settings have priority over group and global settings if available', :deliver_mails_inline do let!(:group) { create(:group) } let!(:maintainer) { group.add_owner(create(:user, username: 'maintainer')).user } let!(:user1) { group.add_developer(create(:user, username: 'user_with_project_and_custom_setting')).user } @@ -770,7 +780,7 @@ describe NotificationService, :mailer do end end - describe 'Issues' do + describe 'Issues', :deliver_mails_inline do let(:group) { create(:group) } let(:project) { create(:project, :public, namespace: group) } let(:another_project) { create(:project, :public, namespace: group) } @@ -1423,7 +1433,7 @@ describe NotificationService, :mailer do end end - describe 'Merge Requests' do + describe 'Merge Requests', :deliver_mails_inline do let(:group) { create(:group) } let(:project) { create(:project, :public, :repository, namespace: group) } let(:another_project) { create(:project, :public, namespace: group) } @@ -1898,7 +1908,7 @@ describe NotificationService, :mailer do end end - describe 'Projects' do + describe 'Projects', :deliver_mails_inline do let(:project) { create(:project) } before do @@ -1989,7 +1999,7 @@ describe NotificationService, :mailer do end end - describe 'GroupMember' do + describe 'GroupMember', :deliver_mails_inline do let(:added_user) { create(:user) } describe '#new_access_request' do @@ -2075,7 +2085,7 @@ describe NotificationService, :mailer do end end - describe 'ProjectMember' do + describe 'ProjectMember', :deliver_mails_inline do let(:project) { create(:project) } let(:added_user) { create(:user) } @@ -2236,7 +2246,7 @@ describe NotificationService, :mailer do end end - context 'guest user in private project' do + context 'guest user in private project', :deliver_mails_inline do let(:private_project) { create(:project, :private) } let(:guest) { create(:user) } let(:developer) { create(:user) } @@ -2291,7 +2301,7 @@ describe NotificationService, :mailer do end end - describe 'Pipelines' do + describe 'Pipelines', :deliver_mails_inline do describe '#pipeline_finished' do let(:project) { create(:project, :public, :repository) } let(:u_member) { create(:user) } @@ -2507,7 +2517,7 @@ describe NotificationService, :mailer do end end - describe 'Pages domains' do + describe 'Pages domains', :deliver_mails_inline do let_it_be(:project, reload: true) { create(:project) } let_it_be(:domain, reload: true) { create(:pages_domain, project: project) } let_it_be(:u_blocked) { create(:user, :blocked) } @@ -2560,7 +2570,7 @@ describe NotificationService, :mailer do end end - context 'Auto DevOps notifications' do + context 'Auto DevOps notifications', :deliver_mails_inline do describe '#autodevops_disabled' do let(:owner) { create(:user) } let(:namespace) { create(:namespace, owner: owner) } @@ -2584,7 +2594,7 @@ describe NotificationService, :mailer do end end - describe 'Repository cleanup' do + describe 'Repository cleanup', :deliver_mails_inline do let(:user) { create(:user) } let(:project) { create(:project) } @@ -2615,7 +2625,7 @@ describe NotificationService, :mailer do end end - context 'Remote mirror notifications' do + context 'Remote mirror notifications', :deliver_mails_inline do describe '#remote_mirror_update_failed' do let(:project) { create(:project) } let(:remote_mirror) { create(:remote_mirror, project: project) } @@ -2653,7 +2663,7 @@ describe NotificationService, :mailer do end end - context 'with external authorization service' do + context 'with external authorization service', :deliver_mails_inline do let(:issue) { create(:issue) } let(:project) { issue.project } let(:note) { create(:note, noteable: issue, project: project) } diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index 024340310a1..6df33e68629 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -6,7 +6,12 @@ module EmailHelpers end def reset_delivered_emails! + # We shouldn't actually send the emails, but we keep the following line for + # back-compatibility until we only check the mailer jobs enqueued in Sidekiq ActionMailer::Base.deliveries.clear + # We should only check that the mailer jobs are enqueued in Sidekiq, hence + # clearing the background jobs queue + ActiveJob::Base.queue_adapter.enqueued_jobs.clear end def should_only_email(*users, kind: :to) diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb index 16ecb338f6e..aee76b8be4a 100644 --- a/spec/support/helpers/notification_helpers.rb +++ b/spec/support/helpers/notification_helpers.rb @@ -36,4 +36,28 @@ module NotificationHelpers setting = user.notification_settings_for(resource) setting.update!(event => value) end + + def expect_delivery_jobs_count(count) + expect(ActionMailer::DeliveryJob).to have_been_enqueued.exactly(count).times + end + + def expect_no_delivery_jobs + expect(ActionMailer::DeliveryJob).not_to have_been_enqueued + end + + def expect_any_delivery_jobs + expect(ActionMailer::DeliveryJob).to have_been_enqueued.at_least(:once) + end + + def have_enqueued_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now") + have_enqueued_job(ActionMailer::DeliveryJob).with(mailer, mail, delivery, *args) + end + + def expect_enqueud_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now") + expect(ActionMailer::DeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, *args) + end + + def expect_not_enqueud_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now") + expect(ActionMailer::DeliveryJob).not_to have_been_enqueued.with(mailer, mail, *args, any_args) + end end diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb index b4d52d24de4..43fe6789145 100644 --- a/spec/support/shared_examples/services/notification_service_shared_examples.rb +++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb @@ -3,7 +3,7 @@ # Note that we actually update the attribute on the target_project/group, rather than # using `allow`. This is because there are some specs where, based on how the notification # is done, using an `allow` doesn't change the correct object. -RSpec.shared_examples 'project emails are disabled' do +RSpec.shared_examples 'project emails are disabled' do |check_delivery_jobs_queue: false| let(:target_project) { notification_target.is_a?(Project) ? notification_target : notification_target.project } before do @@ -16,7 +16,13 @@ RSpec.shared_examples 'project emails are disabled' do notification_trigger - should_not_email_anyone + if check_delivery_jobs_queue + # Only check enqueud jobs, not delivered emails + expect_no_delivery_jobs + else + # Deprecated: Check actual delivered emails + should_not_email_anyone + end end it 'sends emails to someone' do @@ -24,7 +30,13 @@ RSpec.shared_examples 'project emails are disabled' do notification_trigger - should_email_anyone + if check_delivery_jobs_queue + # Only check enqueud jobs, not delivered emails + expect_any_delivery_jobs + else + # Deprecated: Check actual delivered emails + should_email_anyone + end end end |