Commit 133924c6 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent f607152a
File mode changed from 100755 to 100644
...@@ -99,7 +99,10 @@ export default { ...@@ -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 { ...@@ -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 { ...@@ -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"
......
...@@ -87,6 +87,14 @@ export function getLocationHash(url = window.location.href) { ...@@ -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
......
<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 { ...@@ -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 { ...@@ -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();
......
<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 { ...@@ -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 { ...@@ -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,
}; };
......
...@@ -8,8 +8,6 @@ export const OPENED = 'opened'; ...@@ -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'; ...@@ -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,
......
...@@ -28,4 +28,14 @@ module TagsHelper ...@@ -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
...@@ -33,9 +33,12 @@ class SlashCommandsService < Service ...@@ -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
......
...@@ -48,6 +48,7 @@ class GlobalPolicy < BasePolicy ...@@ -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
......
...@@ -9,6 +9,8 @@ ...@@ -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
......
...@@ -31,7 +31,7 @@ ...@@ -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'
......
...@@ -6,13 +6,13 @@ class PruneOldEventsWorker ...@@ -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
......
---
title: Hide redundant labels in issue boards
merge_request: 17937
author:
type: fixed
---
title: Fix notes race condition when linking to specific note
merge_request: 17777
author:
type: fixed
---
title: Do not allow deactivated users to use slash commands
merge_request: 18365
author:
type: fixed
...@@ -666,7 +666,7 @@ build: ...@@ -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
......
...@@ -55,6 +55,7 @@ A deactivated user: ...@@ -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.
......
...@@ -244,6 +244,12 @@ Sign in and re-enable two-factor authentication as soon as possible. ...@@ -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
......
...@@ -15,6 +15,15 @@ module Gitlab ...@@ -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
......
...@@ -375,6 +375,12 @@ msgstr "" ...@@ -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 "" ...@@ -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 "" ...@@ -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 "" ...@@ -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 "" ...@@ -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 "" ...@@ -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 "" ...@@ -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 "" ...@@ -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 ""
......
...@@ -251,7 +251,7 @@ describe 'Issue Boards', :js do ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -304,7 +304,8 @@ describe 'Issue Boards', :js do ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -136,6 +136,24 @@ describe('URL utility', () => { ...@@ -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');
......
...@@ -32,7 +32,10 @@ describe('Issue card component', () => { ...@@ -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', () => { ...@@ -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', () => { ...@@ -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();
......
...@@ -15,7 +15,7 @@ export const listObj = { ...@@ -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 = { ...@@ -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;',
}, },
......
...@@ -160,5 +160,28 @@ describe('DiscussionFilter component', () => { ...@@ -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();
});
});
}); });
}); });
...@@ -3,6 +3,13 @@ ...@@ -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 ...@@ -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
......
...@@ -288,6 +288,14 @@ describe GlobalPolicy do ...@@ -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!
......
...@@ -94,16 +94,32 @@ RSpec.shared_examples 'chat slash commands service' do ...@@ -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
......
...@@ -6,12 +6,12 @@ describe PruneOldEventsWorker do ...@@ -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 ...@@ -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
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