Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f607152a08
commit
133924c6cc
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
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
|
||||||
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 @@
|
||||||
= 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 @@
|
||||||
.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
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Hide redundant labels in issue boards
|
||||||
|
merge_request: 17937
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix notes race condition when linking to specific note
|
||||||
|
merge_request: 17777
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Do not allow deactivated users to use slash commands
|
||||||
|
merge_request: 18365
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -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:
|
||||||
|
|
||||||
- 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.
|
||||||
- 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
|
||||||
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 ""
|
||||||
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 ""
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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', () => {
|
||||||
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();
|
||||||
|
|
|
@ -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;',
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
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
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue