Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-10-11 00:06:24 +00:00
parent f607152a08
commit 133924c6cc
30 changed files with 228 additions and 55 deletions

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -99,7 +99,10 @@ export default {
return !groupId ? referencePath.split('#')[0] : null; return !groupId ? referencePath.split('#')[0] : null;
}, },
orderedLabels() { orderedLabels() {
return _.sortBy(this.issue.labels, 'title'); return _.chain(this.issue.labels)
.filter(this.isNonListLabel)
.sortBy('title')
.value();
}, },
helpLink() { helpLink() {
return boardsStore.scopedLabels.helpLink; return boardsStore.scopedLabels.helpLink;
@ -130,6 +133,9 @@ export default {
if (!label.id) return false; if (!label.id) return false;
return true; return true;
}, },
isNonListLabel(label) {
return label.id && !(this.list.type === 'label' && this.list.title === label.title);
},
filterByLabel(label) { filterByLabel(label) {
if (!this.updateFilters) return; if (!this.updateFilters) return;
const labelTitle = encodeURIComponent(label.title); const labelTitle = encodeURIComponent(label.title);
@ -167,7 +173,7 @@ export default {
</h4> </h4>
</div> </div>
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap"> <div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
<template v-for="label in orderedLabels" v-if="showLabel(label)"> <template v-for="label in orderedLabels">
<issue-card-inner-scoped-label <issue-card-inner-scoped-label
v-if="showScopedLabel(label)" v-if="showScopedLabel(label)"
:key="label.id" :key="label.id"

View File

@ -87,6 +87,14 @@ export function getLocationHash(url = window.location.href) {
return hashIndex === -1 ? null : url.substring(hashIndex + 1); return hashIndex === -1 ? null : url.substring(hashIndex + 1);
} }
/**
* Returns a boolean indicating whether the URL hash contains the given string value
*/
export function doesHashExistInUrl(hashName) {
const hash = getLocationHash();
return hash && hash.includes(hashName);
}
/** /**
* Apply the fragment to the given url by returning a new url string that includes * Apply the fragment to the given url by returning a new url string that includes
* the fragment. If the given url already contains a fragment, the original fragment * the fragment. If the given url already contains a fragment, the original fragment

View File

@ -1,13 +1,14 @@
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility'; import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { import {
DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTERS_DEFAULT_VALUE,
HISTORY_ONLY_FILTER_VALUE, HISTORY_ONLY_FILTER_VALUE,
DISCUSSION_TAB_LABEL, DISCUSSION_TAB_LABEL,
DISCUSSION_FILTER_TYPES, DISCUSSION_FILTER_TYPES,
NOTE_UNDERSCORE,
} from '../constants'; } from '../constants';
import notesEventHub from '../event_hub'; import notesEventHub from '../event_hub';
@ -28,7 +29,9 @@ export default {
}, },
data() { data() {
return { return {
currentValue: this.selectedValue, currentValue: doesHashExistInUrl(NOTE_UNDERSCORE)
? DISCUSSION_FILTERS_DEFAULT_VALUE
: this.selectedValue,
defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE, defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE,
displayFilters: true, displayFilters: true,
}; };
@ -50,7 +53,6 @@ export default {
notesEventHub.$on('dropdownSelect', this.selectFilter); notesEventHub.$on('dropdownSelect', this.selectFilter);
window.addEventListener('hashchange', this.handleLocationHash); window.addEventListener('hashchange', this.handleLocationHash);
this.handleLocationHash();
}, },
mounted() { mounted() {
this.toggleCommentsForm(); this.toggleCommentsForm();

View File

@ -1,7 +1,7 @@
<script> <script>
import { __ } from '~/locale'; import { __ } from '~/locale';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility'; import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash'; import Flash from '../../flash';
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
@ -156,19 +156,17 @@ export default {
this.isFetching = true; this.isFetching = true;
return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') }) return this.fetchDiscussions(this.getFetchDiscussionsConfig())
.then(() => { .then(this.initPolling)
this.initPolling();
})
.then(() => { .then(() => {
this.setLoadingState(false); this.setLoadingState(false);
this.setNotesFetchedState(true); this.setNotesFetchedState(true);
eventHub.$emit('fetchedNotesData'); eventHub.$emit('fetchedNotesData');
this.isFetching = false; this.isFetching = false;
}) })
.then(() => this.$nextTick()) .then(this.$nextTick)
.then(() => this.startTaskList()) .then(this.startTaskList)
.then(() => this.checkLocationHash()) .then(this.checkLocationHash)
.catch(() => { .catch(() => {
this.setLoadingState(false); this.setLoadingState(false);
this.setNotesFetchedState(true); this.setNotesFetchedState(true);
@ -199,9 +197,20 @@ export default {
}, },
startReplying(discussionId) { startReplying(discussionId) {
return this.convertToDiscussion(discussionId) return this.convertToDiscussion(discussionId)
.then(() => this.$nextTick()) .then(this.$nextTick)
.then(() => eventHub.$emit('startReplying', discussionId)); .then(() => eventHub.$emit('startReplying', discussionId));
}, },
getFetchDiscussionsConfig() {
const defaultConfig = { path: this.getNotesDataByProp('discussionsPath') };
if (doesHashExistInUrl(constants.NOTE_UNDERSCORE)) {
return Object.assign({}, defaultConfig, {
filter: constants.DISCUSSION_FILTERS_DEFAULT_VALUE,
persistFilter: false,
});
}
return defaultConfig;
},
}, },
systemNote: constants.SYSTEM_NOTE, systemNote: constants.SYSTEM_NOTE,
}; };

View File

@ -8,8 +8,6 @@ export const OPENED = 'opened';
export const REOPENED = 'reopened'; export const REOPENED = 'reopened';
export const CLOSED = 'closed'; export const CLOSED = 'closed';
export const MERGED = 'merged'; export const MERGED = 'merged';
export const EMOJI_THUMBSUP = 'thumbsup';
export const EMOJI_THUMBSDOWN = 'thumbsdown';
export const ISSUE_NOTEABLE_TYPE = 'issue'; export const ISSUE_NOTEABLE_TYPE = 'issue';
export const EPIC_NOTEABLE_TYPE = 'epic'; export const EPIC_NOTEABLE_TYPE = 'epic';
export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest'; export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest';
@ -19,6 +17,7 @@ export const DESCRIPTION_TYPE = 'changed the description';
export const HISTORY_ONLY_FILTER_VALUE = 2; export const HISTORY_ONLY_FILTER_VALUE = 2;
export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0; export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
export const DISCUSSION_TAB_LABEL = 'show'; export const DISCUSSION_TAB_LABEL = 'show';
export const NOTE_UNDERSCORE = 'note_';
export const NOTEABLE_TYPE_MAPPING = { export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE, Issue: ISSUE_NOTEABLE_TYPE,

View File

@ -28,4 +28,14 @@ module TagsHelper
def protected_tag?(project, tag) def protected_tag?(project, tag)
ProtectedTag.protected?(project, tag.name) ProtectedTag.protected?(project, tag.name)
end end
def tag_description_help_text
text = s_('TagsPage|Optionally, add a message to the tag. Leaving this blank creates '\
'a %{link_start}lightweight tag.%{link_end}') % {
link_start: '<a href="https://git-scm.com/book/en/v2/Git-Basics-Tagging\" target="_blank" rel="noopener noreferrer">',
link_end: '</a>'
}
text.html_safe
end
end end

View File

@ -33,9 +33,12 @@ class SlashCommandsService < Service
return unless valid_token?(params[:token]) return unless valid_token?(params[:token])
chat_user = find_chat_user(params) chat_user = find_chat_user(params)
user = chat_user&.user
if user
unless user.can?(:use_slash_commands)
return Gitlab::SlashCommands::Presenters::Access.new.deactivated if user.deactivated?
if chat_user&.user
unless chat_user.user.can?(:use_slash_commands)
return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project) return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project)
end end

View File

@ -48,6 +48,7 @@ class GlobalPolicy < BasePolicy
prevent :access_git prevent :access_git
prevent :access_api prevent :access_api
prevent :receive_notifications prevent :receive_notifications
prevent :use_slash_commands
end end
rule { required_terms_not_accepted }.policy do rule { required_terms_not_accepted }.policy do

View File

@ -9,6 +9,8 @@
= s_('AdminUsers|The user will not be able to access the API') = s_('AdminUsers|The user will not be able to access the API')
%li %li
= s_('AdminUsers|The user will not receive any notifications') = s_('AdminUsers|The user will not receive any notifications')
%li
= s_('AdminUsers|The user will not be able to use slash commands')
%li %li
= s_('AdminUsers|When the user logs back in, their account will reactivate as a fully active account') = s_('AdminUsers|When the user logs back in, their account will reactivate as a fully active account')
%li %li

View File

@ -31,7 +31,7 @@
.col-sm-10 .col-sm-10
= text_area_tag :message, @message, required: false, class: 'form-control', rows: 5 = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5
.form-text.text-muted .form-text.text-muted
= s_('TagsPage|Optionally, add a message to the tag.') = tag_description_help_text
%hr %hr
.form-group.row .form-group.row
= label_tag :release_description, s_('TagsPage|Release notes'), class: 'col-form-label col-sm-2' = label_tag :release_description, s_('TagsPage|Release notes'), class: 'col-form-label col-sm-2'

View File

@ -6,13 +6,13 @@ class PruneOldEventsWorker
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform def perform
# Contribution calendar shows maximum 12 months of events, we retain 2 years for data integrity. # Contribution calendar shows maximum 12 months of events, we retain 3 years for data integrity.
# Double nested query is used because MySQL doesn't allow DELETE subqueries on the same table. # Double nested query is used because MySQL doesn't allow DELETE subqueries on the same table.
Event.unscoped.where( Event.unscoped.where(
'(id IN (SELECT id FROM (?) ids_to_remove))', '(id IN (SELECT id FROM (?) ids_to_remove))',
Event.unscoped.where( Event.unscoped.where(
'created_at < ?', 'created_at < ?',
(2.years + 1.day).ago) (3.years + 1.day).ago)
.select(:id) .select(:id)
.limit(10_000)) .limit(10_000))
.delete_all .delete_all

View File

@ -0,0 +1,5 @@
---
title: Hide redundant labels in issue boards
merge_request: 17937
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix notes race condition when linking to specific note
merge_request: 17777
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Do not allow deactivated users to use slash commands
merge_request: 18365
author:
type: fixed

View File

@ -666,7 +666,7 @@ build:
CAUTION: **Warning:** CAUTION: **Warning:**
There are some points to be aware of when There are some points to be aware of when
[using this feature with new branches or tags *without* pipelines for merge requests](using-onlychanges-without-pipelines-for-merge-requests). [using this feature with new branches or tags *without* pipelines for merge requests](#using-onlychanges-without-pipelines-for-merge-requests).
##### Using `only:changes` with pipelines for merge requests ##### Using `only:changes` with pipelines for merge requests

View File

@ -55,6 +55,7 @@ A deactivated user:
- Cannot access Git repositories or the API. - Cannot access Git repositories or the API.
- Will not receive any notifications from GitLab. - Will not receive any notifications from GitLab.
- Will not be able to use [slash commands](../../../integration/slash_commands.md).
Personal projects, group and user history of the deactivated user will be left intact. Personal projects, group and user history of the deactivated user will be left intact.

View File

@ -244,6 +244,12 @@ Sign in and re-enable two-factor authentication as soon as possible.
- The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`. the U2F key has only been registered on `first.host.xyz`.
## Troubleshooting
If you are receiving an `invalid pin code` error, this may indicate that there is a time sync issue between the authentication application and the GitLab instance itself.
Most authentication apps have a feature in the settings for syncing the time for the codes themselves. For Google Authenticator for example, go to `Settings > Time correction for codes`.
<!-- ## Troubleshooting <!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -15,6 +15,15 @@ module Gitlab
MESSAGE MESSAGE
end end
def deactivated
ephemeral_response(text: <<~MESSAGE)
You are not allowed to perform the given chatops command since
your account has been deactivated by your administrator.
Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}
MESSAGE
end
def not_found def not_found
ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:")
end end

View File

@ -375,6 +375,12 @@ msgstr ""
msgid "%{title} changes" msgid "%{title} changes"
msgstr "" msgstr ""
msgid "%{total} open issue weight"
msgstr ""
msgid "%{total} open issues"
msgstr ""
msgid "%{unstaged} unstaged and %{staged} staged changes" msgid "%{unstaged} unstaged and %{staged} staged changes"
msgstr "" msgstr ""
@ -1252,6 +1258,9 @@ msgstr ""
msgid "AdminUsers|The user will not be able to access the API" msgid "AdminUsers|The user will not be able to access the API"
msgstr "" msgstr ""
msgid "AdminUsers|The user will not be able to use slash commands"
msgstr ""
msgid "AdminUsers|The user will not receive any notifications" msgid "AdminUsers|The user will not receive any notifications"
msgstr "" msgstr ""
@ -2649,7 +2658,7 @@ msgstr ""
msgid "Built-in" msgid "Built-in"
msgstr "" msgstr ""
msgid "BurndownChartLabel|Guideline" msgid "Burndown chart"
msgstr "" msgstr ""
msgid "BurndownChartLabel|Open issue weight" msgid "BurndownChartLabel|Open issue weight"
@ -2658,12 +2667,6 @@ msgstr ""
msgid "BurndownChartLabel|Open issues" msgid "BurndownChartLabel|Open issues"
msgstr "" msgstr ""
msgid "BurndownChartLabel|Progress"
msgstr ""
msgid "BurndownChartLabel|Remaining"
msgstr ""
msgid "Business" msgid "Business"
msgstr "" msgstr ""
@ -8303,6 +8306,9 @@ msgstr ""
msgid "GroupsTree|Search by name" msgid "GroupsTree|Search by name"
msgstr "" msgstr ""
msgid "Guideline"
msgstr ""
msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}" msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
msgstr "" msgstr ""
@ -8943,6 +8949,9 @@ msgstr ""
msgid "Issue was closed by %{name} %{reason}" msgid "Issue was closed by %{name} %{reason}"
msgstr "" msgstr ""
msgid "Issue weight"
msgstr ""
msgid "IssueBoards|Board" msgid "IssueBoards|Board"
msgstr "" msgstr ""
@ -15814,7 +15823,7 @@ msgstr ""
msgid "TagsPage|New tag" msgid "TagsPage|New tag"
msgstr "" msgstr ""
msgid "TagsPage|Optionally, add a message to the tag." msgid "TagsPage|Optionally, add a message to the tag. Leaving this blank creates a %{link_start}lightweight tag.%{link_end}"
msgstr "" msgstr ""
msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page." msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
@ -17062,9 +17071,15 @@ msgstr ""
msgid "Total artifacts size: %{total_size}" msgid "Total artifacts size: %{total_size}"
msgstr "" msgstr ""
msgid "Total issues"
msgstr ""
msgid "Total test time for all commits/merges" msgid "Total test time for all commits/merges"
msgstr "" msgstr ""
msgid "Total weight"
msgstr ""
msgid "Total: %{total}" msgid "Total: %{total}"
msgstr "" msgstr ""

View File

@ -251,7 +251,7 @@ describe 'Issue Boards', :js do
expect(page).to have_selector(selector, text: development.title, count: 1) expect(page).to have_selector(selector, text: development.title, count: 1)
end end
it 'issue moves between lists' do it 'issue moves between lists and does not show the "Development" label since the card is in the "Development" list label' do
drag(list_from_index: 1, from_index: 1, list_to_index: 2) drag(list_from_index: 1, from_index: 1, list_to_index: 2)
wait_for_board_cards(2, 7) wait_for_board_cards(2, 7)
@ -259,10 +259,10 @@ describe 'Issue Boards', :js do
wait_for_board_cards(4, 1) wait_for_board_cards(4, 1)
expect(find('.board:nth-child(3)')).to have_content(issue6.title) expect(find('.board:nth-child(3)')).to have_content(issue6.title)
expect(find('.board:nth-child(3)').all('.board-card').last).to have_content(development.title) expect(find('.board:nth-child(3)').all('.board-card').last).not_to have_content(development.title)
end end
it 'issue moves between lists' do it 'issue moves between lists and does not show the "Planning" label since the card is in the "Planning" list label' do
drag(list_from_index: 2, list_to_index: 1) drag(list_from_index: 2, list_to_index: 1)
wait_for_board_cards(2, 9) wait_for_board_cards(2, 9)
@ -270,7 +270,7 @@ describe 'Issue Boards', :js do
wait_for_board_cards(4, 1) wait_for_board_cards(4, 1)
expect(find('.board:nth-child(2)')).to have_content(issue7.title) expect(find('.board:nth-child(2)')).to have_content(issue7.title)
expect(find('.board:nth-child(2)').all('.board-card').first).to have_content(planning.title) expect(find('.board:nth-child(2)').all('.board-card').first).not_to have_content(planning.title)
end end
it 'issue moves from closed' do it 'issue moves from closed' do

View File

@ -304,7 +304,8 @@ describe 'Issue Boards', :js do
end end
end end
expect(card).to have_selector('.badge', count: 3) # 'Development' label does not show since the card is in a 'Development' list label
expect(card).to have_selector('.badge', count: 2)
expect(card).to have_content(bug.title) expect(card).to have_content(bug.title)
end end
@ -330,7 +331,8 @@ describe 'Issue Boards', :js do
end end
end end
expect(card).to have_selector('.badge', count: 4) # 'Development' label does not show since the card is in a 'Development' list label
expect(card).to have_selector('.badge', count: 3)
expect(card).to have_content(bug.title) expect(card).to have_content(bug.title)
expect(card).to have_content(regression.title) expect(card).to have_content(regression.title)
end end
@ -357,7 +359,8 @@ describe 'Issue Boards', :js do
end end
end end
expect(card).to have_selector('.badge', count: 1) # 'Development' label does not show since the card is in a 'Development' list label
expect(card).to have_selector('.badge', count: 0)
expect(card).not_to have_content(stretch.title) expect(card).not_to have_content(stretch.title)
end end

View File

@ -136,6 +136,24 @@ describe('URL utility', () => {
}); });
}); });
describe('doesHashExistInUrl', () => {
it('should return true when the given string exists in the URL hash', () => {
setWindowLocation({
href: 'https://gitlab.com/gitlab-org/gitlab-test/issues/1#note_1',
});
expect(urlUtils.doesHashExistInUrl('note_')).toBe(true);
});
it('should return false when the given string does not exist in the URL hash', () => {
setWindowLocation({
href: 'https://gitlab.com/gitlab-org/gitlab-test/issues/1#note_1',
});
expect(urlUtils.doesHashExistInUrl('doesnotexist')).toBe(false);
});
});
describe('setUrlFragment', () => { describe('setUrlFragment', () => {
it('should set fragment when url has no fragment', () => { it('should set fragment when url has no fragment', () => {
const url = urlUtils.setUrlFragment('/home/feature', 'usage'); const url = urlUtils.setUrlFragment('/home/feature', 'usage');

View File

@ -32,7 +32,10 @@ describe('Issue card component', () => {
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="test-container"></div>'); setFixtures('<div class="test-container"></div>');
list = listObj; list = {
...listObj,
type: 'label',
};
issue = new ListIssue({ issue = new ListIssue({
title: 'Testing', title: 'Testing',
id: 1, id: 1,
@ -241,8 +244,8 @@ describe('Issue card component', () => {
Vue.nextTick(() => done()); Vue.nextTick(() => done());
}); });
it('renders list label', () => { it('does not render list label but renders all other labels', () => {
expect(component.$el.querySelectorAll('.badge').length).toBe(2); expect(component.$el.querySelectorAll('.badge').length).toBe(1);
}); });
it('renders label', () => { it('renders label', () => {
@ -278,7 +281,7 @@ describe('Issue card component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(component.$el.querySelectorAll('.badge').length).toBe(2); expect(component.$el.querySelectorAll('.badge').length).toBe(1);
expect(component.$el.textContent).not.toContain('closed'); expect(component.$el.textContent).not.toContain('closed');
done(); done();

View File

@ -15,7 +15,7 @@ export const listObj = {
weight: 3, weight: 3,
label: { label: {
id: 5000, id: 5000,
title: 'Testing', title: 'Test',
color: 'red', color: 'red',
description: 'testing;', description: 'testing;',
textColor: 'white', textColor: 'white',
@ -30,7 +30,7 @@ export const listObjDuplicate = {
weight: 3, weight: 3,
label: { label: {
id: listObj.label.id, id: listObj.label.id,
title: 'Testing', title: 'Test',
color: 'red', color: 'red',
description: 'testing;', description: 'testing;',
}, },

View File

@ -160,5 +160,28 @@ describe('DiscussionFilter component', () => {
done(); done();
}); });
}); });
it('fetches discussions when there is a hash', done => {
window.location.hash = `note_${discussionMock.notes[0].id}`;
vm.currentValue = discussionFiltersMock[2].value;
spyOn(vm, 'selectFilter');
vm.handleLocationHash();
vm.$nextTick(() => {
expect(vm.selectFilter).toHaveBeenCalled();
done();
});
});
it('does not fetch discussions when there is no hash', done => {
window.location.hash = '';
spyOn(vm, 'selectFilter');
vm.handleLocationHash();
vm.$nextTick(() => {
expect(vm.selectFilter).not.toHaveBeenCalled();
done();
});
});
}); });
}); });

View File

@ -3,6 +3,13 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Access do describe Gitlab::SlashCommands::Presenters::Access do
shared_examples_for 'displays an error message' do
it do
expect(subject[:text]).to match(error_message)
expect(subject[:response_type]).to be(:ephemeral)
end
end
describe '#access_denied' do describe '#access_denied' do
let(:project) { build(:project) } let(:project) { build(:project) }
@ -10,9 +17,18 @@ describe Gitlab::SlashCommands::Presenters::Access do
it { is_expected.to be_a(Hash) } it { is_expected.to be_a(Hash) }
it 'displays an error message' do it_behaves_like 'displays an error message' do
expect(subject[:text]).to match('are not allowed') let(:error_message) { 'you do not have access to the GitLab project' }
expect(subject[:response_type]).to be(:ephemeral) end
end
describe '#deactivated' do
subject { described_class.new.deactivated }
it { is_expected.to be_a(Hash) }
it_behaves_like 'displays an error message' do
let(:error_message) { 'your account has been deactivated by your administrator' }
end end
end end

View File

@ -288,6 +288,14 @@ describe GlobalPolicy do
it { is_expected.not_to be_allowed(:use_slash_commands) } it { is_expected.not_to be_allowed(:use_slash_commands) }
end end
context 'when deactivated' do
before do
current_user.deactivate
end
it { is_expected.not_to be_allowed(:use_slash_commands) }
end
context 'when access locked' do context 'when access locked' do
before do before do
current_user.lock_access! current_user.lock_access!

View File

@ -94,16 +94,32 @@ RSpec.shared_examples 'chat slash commands service' do
subject.trigger(params) subject.trigger(params)
end end
shared_examples_for 'blocks command execution' do
it do
expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
result = subject.trigger(params)
expect(result[:text]).to match(error_message)
end
end
context 'when user is blocked' do context 'when user is blocked' do
before do before do
chat_name.user.block chat_name.user.block
end end
it 'blocks command execution' do it_behaves_like 'blocks command execution' do
expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute) let(:error_message) { 'you do not have access to the GitLab project' }
end
end
result = subject.trigger(params) context 'when user is deactivated' do
expect(result).to include(text: /^You are not allowed/) before do
chat_name.user.deactivate
end
it_behaves_like 'blocks command execution' do
let(:error_message) { 'your account has been deactivated by your administrator' }
end end
end end
end end

View File

@ -6,12 +6,12 @@ describe PruneOldEventsWorker do
describe '#perform' do describe '#perform' do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:expired_event) { create(:event, :closed, author: user, created_at: 25.months.ago) } let!(:expired_event) { create(:event, :closed, author: user, created_at: 37.months.ago) }
let!(:not_expired_1_day_event) { create(:event, :closed, author: user, created_at: 1.day.ago) } let!(:not_expired_1_day_event) { create(:event, :closed, author: user, created_at: 1.day.ago) }
let!(:not_expired_13_month_event) { create(:event, :closed, author: user, created_at: 13.months.ago) } let!(:not_expired_13_month_event) { create(:event, :closed, author: user, created_at: 13.months.ago) }
let!(:not_expired_2_years_event) { create(:event, :closed, author: user, created_at: 2.years.ago) } let!(:not_expired_3_years_event) { create(:event, :closed, author: user, created_at: 3.years.ago) }
it 'prunes events older than 2 years' do it 'prunes events older than 3 years' do
expect { subject.perform }.to change { Event.count }.by(-1) expect { subject.perform }.to change { Event.count }.by(-1)
expect(Event.find_by(id: expired_event.id)).to be_nil expect(Event.find_by(id: expired_event.id)).to be_nil
end end
@ -26,9 +26,9 @@ describe PruneOldEventsWorker do
expect(not_expired_13_month_event.reload).to be_present expect(not_expired_13_month_event.reload).to be_present
end end
it 'leaves events from 2 years ago' do it 'leaves events from 3 years ago' do
subject.perform subject.perform
expect(not_expired_2_years_event).to be_present expect(not_expired_3_years_event).to be_present
end end
end end
end end