Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-01-24 09:08:32 +00:00
parent 83a9f472b8
commit 9b984f55ee
37 changed files with 226 additions and 166 deletions

View file

@ -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;
},

View file

@ -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: {

View file

@ -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"

View file

@ -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;

View file

@ -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));

View file

@ -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),

View file

@ -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"

View file

@ -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() {

View file

@ -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]) {

View file

@ -0,0 +1,5 @@
.date-time-picker {
.date-time-picker-menu {
width: 400px;
}
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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']

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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>',
})
}}

View file

@ -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

View file

@ -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)** | | | | | ✓ |

View file

@ -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

View file

@ -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

View file

@ -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).

View file

@ -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)**

View file

@ -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).

View file

@ -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';

View file

@ -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 =>

View file

@ -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';

View file

@ -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', () => {

View file

@ -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';

View file

@ -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') }

View file

@ -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) }
it { expect(notification.new_key(key)).to be_truthy }
subject { notification.new_key(key) }
describe 'never emails the ghost user' do
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
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) }
it { expect(notification.new_gpg_key(key)).to be_truthy }
subject { notification.new_gpg_key(key) }
it 'sends email to key owner' do
expect { notification.new_gpg_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1)
it "sends email to key owner" do
expect { subject }.to have_enqueued_email(key.id, mail: "new_gpg_key_email")
end
describe "never emails the ghost user" do
let(:key_options) { { user: User.ghost } }
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
it 'sends emails to recipients' do
subject
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)
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) }

View file

@ -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)

View file

@ -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

View file

@ -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