Commit 9b984f55 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 83a9f472
<script> <script>
import { flatten, isNumber } from 'underscore'; import { flattenDeep, isNumber } from 'lodash';
import { GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { roundOffFloat } from '~/lib/utils/common_utils'; import { roundOffFloat } from '~/lib/utils/common_utils';
import { hexToRgb } from '~/lib/utils/color_utils'; import { hexToRgb } from '~/lib/utils/color_utils';
...@@ -77,7 +77,7 @@ export default { ...@@ -77,7 +77,7 @@ export default {
* This offset is the lowest value. * This offset is the lowest value.
*/ */
yOffset() { 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; const min = values.length ? Math.floor(Math.min(...values)) : 0;
return min < 0 ? -min : 0; return min < 0 ? -min : 0;
}, },
......
<script> <script>
import _ from 'underscore'; import { omit } from 'lodash';
import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui'; import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
...@@ -140,7 +140,7 @@ export default { ...@@ -140,7 +140,7 @@ export default {
return (this.option.series || []).concat(this.scatterSeries ? [this.scatterSeries] : []); return (this.option.series || []).concat(this.scatterSeries ? [this.scatterSeries] : []);
}, },
chartOptions() { chartOptions() {
const option = _.omit(this.option, 'series'); const option = omit(this.option, 'series');
return { return {
series: this.chartOptionSeries, series: this.chartOptionSeries,
xAxis: { xAxis: {
......
<script> <script>
import _ from 'underscore'; import { debounce, pickBy } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import VueDraggable from 'vuedraggable'; import VueDraggable from 'vuedraggable';
import { import {
...@@ -15,12 +15,13 @@ import { ...@@ -15,12 +15,13 @@ import {
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url'; 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 GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import GroupEmptyState from './group_empty_state.vue'; import GroupEmptyState from './group_empty_state.vue';
...@@ -28,11 +29,10 @@ import DashboardsDropdown from './dashboards_dropdown.vue'; ...@@ -28,11 +29,10 @@ import DashboardsDropdown from './dashboards_dropdown.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event'; import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getAddMetricTrackingOptions } from '../utils'; import { getAddMetricTrackingOptions } from '../utils';
import { getTimeRange } from './date_time_picker/date_time_picker_lib';
import { datePickerTimeWindows, metricStates } from '../constants'; import { datePickerTimeWindows, metricStates } from '../constants';
const defaultTimeDiff = getTimeRange(); const defaultTimeRange = getTimeRange();
export default { export default {
components: { components: {
...@@ -190,8 +190,8 @@ export default { ...@@ -190,8 +190,8 @@ export default {
return { return {
state: 'gettingStarted', state: 'gettingStarted',
formIsValid: null, formIsValid: null,
startDate: getParameterValues('start')[0] || defaultTimeDiff.start, startDate: getParameterValues('start')[0] || defaultTimeRange.start,
endDate: getParameterValues('end')[0] || defaultTimeDiff.end, endDate: getParameterValues('end')[0] || defaultTimeRange.end,
hasValidDates: true, hasValidDates: true,
datePickerTimeWindows, datePickerTimeWindows,
isRearrangingPanels: false, isRearrangingPanels: false,
...@@ -288,13 +288,13 @@ export default { ...@@ -288,13 +288,13 @@ export default {
'Metrics|Link contains an invalid time window, please verify the link to see the requested time range.', 'Metrics|Link contains an invalid time window, please verify the link to see the requested time range.',
), ),
); );
this.startDate = defaultTimeDiff.start; this.startDate = defaultTimeRange.start;
this.endDate = defaultTimeDiff.end; this.endDate = defaultTimeRange.end;
}, },
generateLink(group, title, yLabel) { generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path; 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); return mergeUrlParams(params, window.location.href);
}, },
hideAddMetricModal() { hideAddMetricModal() {
...@@ -306,7 +306,7 @@ export default { ...@@ -306,7 +306,7 @@ export default {
setFormValidity(isValid) { setFormValidity(isValid) {
this.formIsValid = isValid; this.formIsValid = isValid;
}, },
debouncedEnvironmentsSearch: _.debounce(function environmentsSearchOnInput(searchTerm) { debouncedEnvironmentsSearch: debounce(function environmentsSearchOnInput(searchTerm) {
this.setEnvironmentsSearchTerm(searchTerm); this.setEnvironmentsSearchTerm(searchTerm);
}, 500), }, 500),
submitCustomMetricsForm() { submitCustomMetricsForm() {
...@@ -427,6 +427,7 @@ export default { ...@@ -427,6 +427,7 @@ export default {
class="col-sm-6 col-md-6 col-lg-4" class="col-sm-6 col-md-6 col-lg-4"
> >
<date-time-picker <date-time-picker
ref="dateTimePicker"
:start="startDate" :start="startDate"
:end="endDate" :end="endDate"
:time-windows="datePickerTimeWindows" :time-windows="datePickerTimeWindows"
......
...@@ -3,7 +3,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; ...@@ -3,7 +3,7 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import { sidebarAnimationDuration } from '../constants'; 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; let sidebarMutationObserver;
......
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import _ from 'underscore'; import { pickBy } from 'lodash';
import { import {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
...@@ -90,7 +90,7 @@ export default { ...@@ -90,7 +90,7 @@ export default {
getGraphAlerts(queries) { getGraphAlerts(queries) {
if (!this.allAlerts) return {}; if (!this.allAlerts) return {};
const metricIdsForChart = queries.map(q => q.metricId); 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) { getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries)); return Object.values(this.getGraphAlerts(queries));
......
import _ from 'underscore'; import { omit } from 'lodash';
export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`; export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`;
...@@ -11,7 +11,7 @@ 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 = {}) => export const normalizeMetric = (metric = {}) =>
_.omit( omit(
{ {
...metric, ...metric,
metric_id: uniqMetricsId(metric), metric_id: uniqMetricsId(metric),
......
...@@ -122,30 +122,28 @@ export default { ...@@ -122,30 +122,28 @@ export default {
}; };
</script> </script>
<template> <template>
<gl-dropdown <gl-dropdown :text="timeWindowText" class="date-time-picker" menu-class="date-time-picker-menu">
ref="dropdown" <div class="d-flex justify-content-between gl-p-2">
: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-form-group <gl-form-group
:label="__('Custom range')" :label="__('Custom range')"
label-for="custom-from-time" 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 <div class="gl-pt-2">
id="custom-time-from" <date-time-picker-input
v-model="startInput" id="custom-time-from"
:label="__('From')" v-model="startInput"
:state="startInputValid" :label="__('From')"
/> :state="startInputValid"
<date-time-picker-input />
id="custom-time-to" <date-time-picker-input
v-model="endInput" id="custom-time-to"
:label="__('To')" v-model="endInput"
:state="endInputValid" :label="__('To')"
/> :state="endInputValid"
/>
</div>
<gl-form-group> <gl-form-group>
<gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button> <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button>
<gl-button variant="success" :disabled="!isValid" @click="apply()"> <gl-button variant="success" :disabled="!isValid" @click="apply()">
...@@ -153,12 +151,10 @@ export default { ...@@ -153,12 +151,10 @@ export default {
</gl-button> </gl-button>
</gl-form-group> </gl-form-group>
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group label-for="group-id-dropdown" class="col-md-5 gl-pl-1 gl-pr-1 m-0">
:label="__('Quick range')" <template #label>
label-for="group-id-dropdown" <span class="gl-pl-5">{{ __('Quick range') }}</span>
label-align="center" </template>
class="col-md-4 p-0 m-0"
>
<gl-dropdown-item <gl-dropdown-item
v-for="(timeWindow, key) in timeWindows" v-for="(timeWindow, key) in timeWindows"
:key="key" :key="key"
......
<script> <script>
import _ from 'underscore'; import { uniqueId } from 'lodash';
import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { dateFormats } from './date_time_picker_lib'; import { dateFormats } from './date_time_picker_lib';
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
id: { id: {
type: String, type: String,
required: false, required: false,
default: () => _.uniqueId('dateTimePicker_'), default: () => uniqueId('dateTimePicker_'),
}, },
}, },
data() { data() {
......
...@@ -8,6 +8,15 @@ import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; ...@@ -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]))?$/; 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 = { export const defaultTimeWindows = {
thirtyMinutes: { thirtyMinutes: {
label: __('30 minutes'), label: __('30 minutes'),
...@@ -58,6 +67,17 @@ export const isValidDate = dateString => { ...@@ -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) => { export const getTimeRange = (timeWindowKey, timeWindows = defaultTimeWindows) => {
let difference; let difference;
if (timeWindows[timeWindowKey]) { if (timeWindows[timeWindowKey]) {
......
.date-time-picker {
.date-time-picker-menu {
width: 400px;
}
}
...@@ -46,32 +46,6 @@ ...@@ -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 { .prometheus-panel {
margin-top: 20px; margin-top: 20px;
} }
......
...@@ -105,7 +105,7 @@ The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb` ...@@ -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) 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 ### Example `gitlab.rb` LDAP
......
...@@ -134,7 +134,7 @@ sudo gitlab-ctl reconfigure ...@@ -134,7 +134,7 @@ sudo gitlab-ctl reconfigure
``` ```
If you do not perform this step, you may find that two-factor authentication 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 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 due to SSH host key mismatch when updating the **primary** node domain's DNS record
......
...@@ -369,7 +369,7 @@ If you need to manually remove job artifacts associated with multiple jobs while ...@@ -369,7 +369,7 @@ If you need to manually remove job artifacts associated with multiple jobs while
NOTE: **NOTE:** NOTE: **NOTE:**
This step will also erase artifacts that users have chosen to 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 ```ruby
builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago) builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago)
......
...@@ -133,7 +133,7 @@ To use an external Prometheus server: ...@@ -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. 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 ```ruby
gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1'] gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
......
...@@ -744,7 +744,7 @@ project or branch name. Special characters can include: ...@@ -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), 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 [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. this at the instance level.
### Image push errors ### Image push errors
......
...@@ -120,7 +120,7 @@ Example response: ...@@ -120,7 +120,7 @@ Example response:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34078) in GitLab 12.5. > [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. To retrieve group audit events using the API, you must [authenticate yourself](README.html#authentication) as an Administrator or an owner of the group.
......
...@@ -10,7 +10,7 @@ Emoji can be awarded on the following (known as "awardables"): ...@@ -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)). - [Merge requests](../user/project/merge_requests/index.md) ([API](merge_requests.md)).
- [Snippets](../user/snippets.md) ([API](snippets.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 ## Issues, merge requests, and snippets
......
...@@ -128,7 +128,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.com/api/v4/pro ...@@ -128,7 +128,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.com/api/v4/pro
## Compare branches, tags or commits ## Compare branches, tags or commits
This endpoint can be accessed without authentication if the repository is 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 GET /projects/:id/repository/compare
......
...@@ -341,7 +341,7 @@ This also applies when using links in between translated sentences, otherwise th ...@@ -341,7 +341,7 @@ This also applies when using links in between translated sentences, otherwise th
```js ```js
{{ {{
sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), { 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>', linkEnd: '</a>',
}) })
}} }}
......
...@@ -93,7 +93,7 @@ This creates a `.git` directory that contains the Git configuration files. ...@@ -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) 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 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. for your Git repository.
### Clone a repository ### Clone a repository
......
...@@ -119,7 +119,7 @@ The following table depicts the various user permission levels in a project. ...@@ -119,7 +119,7 @@ The following table depicts the various user permission levels in a project.
| Configure project hooks | | | | ✓ | ✓ | | Configure project hooks | | | | ✓ | ✓ |
| Manage Runners | | | | ✓ | ✓ | | Manage Runners | | | | ✓ | ✓ |
| Manage job triggers | | | | ✓ | ✓ | | Manage job triggers | | | | ✓ | ✓ |
| Manage variables | | | | ✓ | ✓ | | Manage CI/CD variables | | | | ✓ | ✓ |
| Manage GitLab Pages | | | | ✓ | ✓ | | Manage GitLab Pages | | | | ✓ | ✓ |
| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | | Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
| Remove GitLab Pages | | | | ✓ | ✓ | | Remove GitLab Pages | | | | ✓ | ✓ |
...@@ -223,6 +223,7 @@ group. ...@@ -223,6 +223,7 @@ group.
| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ | | Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Create subgroup | | | | ✓ (1) | ✓ | | Create subgroup | | | | ✓ (1) | ✓ |
| Edit group | | | | | ✓ | | Edit group | | | | | ✓ |
| Manage group level CI/CD variables | | | | | ✓ |
| Manage group members | | | | | ✓ | | Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ | | Remove group | | | | | ✓ |
| Delete group epic **(ULTIMATE)** | | | | | ✓ | | Delete group epic **(ULTIMATE)** | | | | | ✓ |
......
...@@ -134,7 +134,7 @@ Multiple metrics can be displayed on the same chart if the fields **Name**, **Ty ...@@ -134,7 +134,7 @@ Multiple metrics can be displayed on the same chart if the fields **Name**, **Ty
#### Query Variables #### 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 - CI_ENVIRONMENT_SLUG
- KUBE_NAMESPACE - KUBE_NAMESPACE
......
...@@ -34,7 +34,7 @@ to be enabled: ...@@ -34,7 +34,7 @@ to be enabled:
and enable **Git Large File Storage**. and enable **Git Large File Storage**.
Design Management requires that projects are using 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). (the default storage type since v10.0).
### Feature Flags ### Feature Flags
......
...@@ -54,4 +54,4 @@ It is possible to prevent projects in a group from [sharing ...@@ -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). a project with another group](../members/share_project_with_groups.md).
This allows for tighter control over project access. 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).
...@@ -58,7 +58,7 @@ if the job surpasses the threshold, it is marked as failed. ...@@ -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. > [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 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)** ## Maximum artifacts size **(CORE ONLY)**
......
...@@ -86,7 +86,7 @@ related to the project by selecting the **Disable email notifications** checkbox ...@@ -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 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). - Add merge request [description templates](../description_templates.md#description-templates).
- Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **(STARTER)** - Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **(STARTER)**
- Enable [merge only if pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md). - Enable [merge only if pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
......
...@@ -7,8 +7,8 @@ import statusCodes from '~/lib/utils/http_status'; ...@@ -7,8 +7,8 @@ import statusCodes from '~/lib/utils/http_status';
import { metricStates } from '~/monitoring/constants'; import { metricStates } from '~/monitoring/constants';
import Dashboard from '~/monitoring/components/dashboard.vue'; 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 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 GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
......
...@@ -53,7 +53,7 @@ describe('dashboard time window', () => { ...@@ -53,7 +53,7 @@ describe('dashboard time window', () => {
.$nextTick() .$nextTick()
.then(() => { .then(() => {
const timeWindowDropdownItems = wrapper const timeWindowDropdownItems = wrapper
.find('.js-time-window-dropdown') .find({ ref: 'dateTimePicker' })
.findAll(GlDropdownItem); .findAll(GlDropdownItem);
const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper => const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper =>
......
import { mount } from '@vue/test-utils'; 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 inputLabel = 'This is a label';
const inputValue = 'something'; const inputValue = 'something';
......
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('date time picker lib', () => {
describe('isValidDate', () => { describe('isValidDate', () => {
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { defaultTimeWindows } from '~/monitoring/components/date_time_picker/date_time_picker_lib'; import { defaultTimeWindows } from '~/vue_shared/components/date_time_picker/date_time_picker_lib';
const timeWindowsCount = Object.entries(defaultTimeWindows).length; const timeWindowsCount = Object.entries(defaultTimeWindows).length;
const start = '2019-10-10T07:00:00.000Z'; const start = '2019-10-10T07:00:00.000Z';
......
...@@ -482,6 +482,18 @@ describe Repository do ...@@ -482,6 +482,18 @@ describe Repository do
end end
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 describe '#can_be_merged?' do
context 'mergeable branches' do context 'mergeable branches' do
subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') } subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
......
This diff is collapsed.
...@@ -6,7 +6,12 @@ module EmailHelpers ...@@ -6,7 +6,12 @@ module EmailHelpers
end end
def reset_delivered_emails! 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 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 end
def should_only_email(*users, kind: :to) def should_only_email(*users, kind: :to)
......
...@@ -36,4 +36,28 @@ module NotificationHelpers ...@@ -36,4 +36,28 @@ module NotificationHelpers
setting = user.notification_settings_for(resource) setting = user.notification_settings_for(resource)
setting.update!(event => value) setting.update!(event => value)
end 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 end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Note that we actually update the attribute on the target_project/group, rather than # 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 # 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. # 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 } let(:target_project) { notification_target.is_a?(Project) ? notification_target : notification_target.project }
before do before do
...@@ -16,7 +16,13 @@ RSpec.shared_examples 'project emails are disabled' do ...@@ -16,7 +16,13 @@ RSpec.shared_examples 'project emails are disabled' do
notification_trigger 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 end
it 'sends emails to someone' do it 'sends emails to someone' do
...@@ -24,7 +30,13 @@ RSpec.shared_examples 'project emails are disabled' do ...@@ -24,7 +30,13 @@ RSpec.shared_examples 'project emails are disabled' do
notification_trigger 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
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment