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; | ||||
|     }, | ||||
|     orderedLabels() { | ||||
|       return _.sortBy(this.issue.labels, 'title'); | ||||
|       return _.chain(this.issue.labels) | ||||
|         .filter(this.isNonListLabel) | ||||
|         .sortBy('title') | ||||
|         .value(); | ||||
|     }, | ||||
|     helpLink() { | ||||
|       return boardsStore.scopedLabels.helpLink; | ||||
|  | @ -130,6 +133,9 @@ export default { | |||
|       if (!label.id) return false; | ||||
|       return true; | ||||
|     }, | ||||
|     isNonListLabel(label) { | ||||
|       return label.id && !(this.list.type === 'label' && this.list.title === label.title); | ||||
|     }, | ||||
|     filterByLabel(label) { | ||||
|       if (!this.updateFilters) return; | ||||
|       const labelTitle = encodeURIComponent(label.title); | ||||
|  | @ -167,7 +173,7 @@ export default { | |||
|       </h4> | ||||
|     </div> | ||||
|     <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 | ||||
|           v-if="showScopedLabel(label)" | ||||
|           :key="label.id" | ||||
|  |  | |||
|  | @ -87,6 +87,14 @@ export function getLocationHash(url = window.location.href) { | |||
|   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 | ||||
|  * the fragment. If the given url already contains a fragment, the original fragment | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| <script> | ||||
| import $ from 'jquery'; | ||||
| 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 { | ||||
|   DISCUSSION_FILTERS_DEFAULT_VALUE, | ||||
|   HISTORY_ONLY_FILTER_VALUE, | ||||
|   DISCUSSION_TAB_LABEL, | ||||
|   DISCUSSION_FILTER_TYPES, | ||||
|   NOTE_UNDERSCORE, | ||||
| } from '../constants'; | ||||
| import notesEventHub from '../event_hub'; | ||||
| 
 | ||||
|  | @ -28,7 +29,9 @@ export default { | |||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       currentValue: this.selectedValue, | ||||
|       currentValue: doesHashExistInUrl(NOTE_UNDERSCORE) | ||||
|         ? DISCUSSION_FILTERS_DEFAULT_VALUE | ||||
|         : this.selectedValue, | ||||
|       defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE, | ||||
|       displayFilters: true, | ||||
|     }; | ||||
|  | @ -50,7 +53,6 @@ export default { | |||
| 
 | ||||
|     notesEventHub.$on('dropdownSelect', this.selectFilter); | ||||
|     window.addEventListener('hashchange', this.handleLocationHash); | ||||
|     this.handleLocationHash(); | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.toggleCommentsForm(); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| <script> | ||||
| import { __ } from '~/locale'; | ||||
| 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 * as constants from '../constants'; | ||||
| import eventHub from '../event_hub'; | ||||
|  | @ -156,19 +156,17 @@ export default { | |||
| 
 | ||||
|       this.isFetching = true; | ||||
| 
 | ||||
|       return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') }) | ||||
|         .then(() => { | ||||
|           this.initPolling(); | ||||
|         }) | ||||
|       return this.fetchDiscussions(this.getFetchDiscussionsConfig()) | ||||
|         .then(this.initPolling) | ||||
|         .then(() => { | ||||
|           this.setLoadingState(false); | ||||
|           this.setNotesFetchedState(true); | ||||
|           eventHub.$emit('fetchedNotesData'); | ||||
|           this.isFetching = false; | ||||
|         }) | ||||
|         .then(() => this.$nextTick()) | ||||
|         .then(() => this.startTaskList()) | ||||
|         .then(() => this.checkLocationHash()) | ||||
|         .then(this.$nextTick) | ||||
|         .then(this.startTaskList) | ||||
|         .then(this.checkLocationHash) | ||||
|         .catch(() => { | ||||
|           this.setLoadingState(false); | ||||
|           this.setNotesFetchedState(true); | ||||
|  | @ -199,9 +197,20 @@ export default { | |||
|     }, | ||||
|     startReplying(discussionId) { | ||||
|       return this.convertToDiscussion(discussionId) | ||||
|         .then(() => this.$nextTick()) | ||||
|         .then(this.$nextTick) | ||||
|         .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, | ||||
| }; | ||||
|  |  | |||
|  | @ -8,8 +8,6 @@ export const OPENED = 'opened'; | |||
| export const REOPENED = 'reopened'; | ||||
| export const CLOSED = 'closed'; | ||||
| export const MERGED = 'merged'; | ||||
| export const EMOJI_THUMBSUP = 'thumbsup'; | ||||
| export const EMOJI_THUMBSDOWN = 'thumbsdown'; | ||||
| export const ISSUE_NOTEABLE_TYPE = 'issue'; | ||||
| export const EPIC_NOTEABLE_TYPE = 'epic'; | ||||
| 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 DISCUSSION_FILTERS_DEFAULT_VALUE = 0; | ||||
| export const DISCUSSION_TAB_LABEL = 'show'; | ||||
| export const NOTE_UNDERSCORE = 'note_'; | ||||
| 
 | ||||
| export const NOTEABLE_TYPE_MAPPING = { | ||||
|   Issue: ISSUE_NOTEABLE_TYPE, | ||||
|  |  | |||
|  | @ -28,4 +28,14 @@ module TagsHelper | |||
|   def protected_tag?(project, tag) | ||||
|     ProtectedTag.protected?(project, tag.name) | ||||
|   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 | ||||
|  |  | |||
|  | @ -33,9 +33,12 @@ class SlashCommandsService < Service | |||
|     return unless valid_token?(params[:token]) | ||||
| 
 | ||||
|     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) | ||||
|       end | ||||
| 
 | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ class GlobalPolicy < BasePolicy | |||
|     prevent :access_git | ||||
|     prevent :access_api | ||||
|     prevent :receive_notifications | ||||
|     prevent :use_slash_commands | ||||
|   end | ||||
| 
 | ||||
|   rule { required_terms_not_accepted }.policy do | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ | |||
|     = s_('AdminUsers|The user will not be able to access the API') | ||||
|   %li | ||||
|     = s_('AdminUsers|The user will not receive any notifications') | ||||
|   %li | ||||
|     = s_('AdminUsers|The user will not be able to use slash commands') | ||||
|   %li | ||||
|     = s_('AdminUsers|When the user logs back in, their account will reactivate as a fully active account') | ||||
|   %li | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ | |||
|     .col-sm-10 | ||||
|       = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5 | ||||
|       .form-text.text-muted | ||||
|         = s_('TagsPage|Optionally, add a message to the tag.') | ||||
|         = tag_description_help_text | ||||
|   %hr | ||||
|   .form-group.row | ||||
|     = 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 | ||||
|   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. | ||||
|     Event.unscoped.where( | ||||
|       '(id IN (SELECT id FROM (?) ids_to_remove))', | ||||
|       Event.unscoped.where( | ||||
|         'created_at < ?', | ||||
|         (2.years + 1.day).ago) | ||||
|         (3.years + 1.day).ago) | ||||
|       .select(:id) | ||||
|       .limit(10_000)) | ||||
|     .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:** | ||||
| 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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ A deactivated user: | |||
| 
 | ||||
| - Cannot access Git repositories or the API. | ||||
| - 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. | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 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 | ||||
| 
 | ||||
| Include any troubleshooting steps that you can foresee. If you know beforehand what issues | ||||
|  |  | |||
|  | @ -15,6 +15,15 @@ module Gitlab | |||
|           MESSAGE | ||||
|         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 | ||||
|           ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") | ||||
|         end | ||||
|  |  | |||
|  | @ -375,6 +375,12 @@ msgstr "" | |||
| msgid "%{title} changes" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "%{total} open issue weight" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "%{total} open issues" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "%{unstaged} unstaged and %{staged} staged changes" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -1252,6 +1258,9 @@ msgstr "" | |||
| msgid "AdminUsers|The user will not be able to access the API" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "AdminUsers|The user will not be able to use slash commands" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "AdminUsers|The user will not receive any notifications" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -2649,7 +2658,7 @@ msgstr "" | |||
| msgid "Built-in" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "BurndownChartLabel|Guideline" | ||||
| msgid "Burndown chart" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "BurndownChartLabel|Open issue weight" | ||||
|  | @ -2658,12 +2667,6 @@ msgstr "" | |||
| msgid "BurndownChartLabel|Open issues" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "BurndownChartLabel|Progress" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "BurndownChartLabel|Remaining" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Business" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -8303,6 +8306,9 @@ msgstr "" | |||
| msgid "GroupsTree|Search by name" | ||||
| 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}" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -8943,6 +8949,9 @@ msgstr "" | |||
| msgid "Issue was closed by %{name} %{reason}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Issue weight" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "IssueBoards|Board" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -15814,7 +15823,7 @@ msgstr "" | |||
| msgid "TagsPage|New tag" | ||||
| 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 "" | ||||
| 
 | ||||
| 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}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Total issues" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Total test time for all commits/merges" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Total weight" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Total: %{total}" | ||||
| msgstr "" | ||||
| 
 | ||||
|  |  | |||
|  | @ -251,7 +251,7 @@ describe 'Issue Boards', :js do | |||
|         expect(page).to have_selector(selector, text: development.title, count: 1) | ||||
|       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) | ||||
| 
 | ||||
|         wait_for_board_cards(2, 7) | ||||
|  | @ -259,10 +259,10 @@ describe 'Issue Boards', :js do | |||
|         wait_for_board_cards(4, 1) | ||||
| 
 | ||||
|         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 | ||||
| 
 | ||||
|       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) | ||||
| 
 | ||||
|         wait_for_board_cards(2, 9) | ||||
|  | @ -270,7 +270,7 @@ describe 'Issue Boards', :js do | |||
|         wait_for_board_cards(4, 1) | ||||
| 
 | ||||
|         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 | ||||
| 
 | ||||
|       it 'issue moves from closed' do | ||||
|  |  | |||
|  | @ -304,7 +304,8 @@ describe 'Issue Boards', :js do | |||
|         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) | ||||
|     end | ||||
| 
 | ||||
|  | @ -330,7 +331,8 @@ describe 'Issue Boards', :js do | |||
|         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(regression.title) | ||||
|     end | ||||
|  | @ -357,7 +359,8 @@ describe 'Issue Boards', :js do | |||
|         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) | ||||
|     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', () => { | ||||
|     it('should set fragment when url has no fragment', () => { | ||||
|       const url = urlUtils.setUrlFragment('/home/feature', 'usage'); | ||||
|  |  | |||
|  | @ -32,7 +32,10 @@ describe('Issue card component', () => { | |||
|   beforeEach(() => { | ||||
|     setFixtures('<div class="test-container"></div>'); | ||||
| 
 | ||||
|     list = listObj; | ||||
|     list = { | ||||
|       ...listObj, | ||||
|       type: 'label', | ||||
|     }; | ||||
|     issue = new ListIssue({ | ||||
|       title: 'Testing', | ||||
|       id: 1, | ||||
|  | @ -241,8 +244,8 @@ describe('Issue card component', () => { | |||
|       Vue.nextTick(() => done()); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders list label', () => { | ||||
|       expect(component.$el.querySelectorAll('.badge').length).toBe(2); | ||||
|     it('does not render list label but renders all other labels', () => { | ||||
|       expect(component.$el.querySelectorAll('.badge').length).toBe(1); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders label', () => { | ||||
|  | @ -278,7 +281,7 @@ describe('Issue card component', () => { | |||
| 
 | ||||
|       Vue.nextTick() | ||||
|         .then(() => { | ||||
|           expect(component.$el.querySelectorAll('.badge').length).toBe(2); | ||||
|           expect(component.$el.querySelectorAll('.badge').length).toBe(1); | ||||
|           expect(component.$el.textContent).not.toContain('closed'); | ||||
| 
 | ||||
|           done(); | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ export const listObj = { | |||
|   weight: 3, | ||||
|   label: { | ||||
|     id: 5000, | ||||
|     title: 'Testing', | ||||
|     title: 'Test', | ||||
|     color: 'red', | ||||
|     description: 'testing;', | ||||
|     textColor: 'white', | ||||
|  | @ -30,7 +30,7 @@ export const listObjDuplicate = { | |||
|   weight: 3, | ||||
|   label: { | ||||
|     id: listObj.label.id, | ||||
|     title: 'Testing', | ||||
|     title: 'Test', | ||||
|     color: 'red', | ||||
|     description: 'testing;', | ||||
|   }, | ||||
|  |  | |||
|  | @ -160,5 +160,28 @@ describe('DiscussionFilter component', () => { | |||
|         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' | ||||
| 
 | ||||
| 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 | ||||
|     let(:project) { build(:project) } | ||||
| 
 | ||||
|  | @ -10,9 +17,18 @@ describe Gitlab::SlashCommands::Presenters::Access do | |||
| 
 | ||||
|     it { is_expected.to be_a(Hash) } | ||||
| 
 | ||||
|     it 'displays an error message' do | ||||
|       expect(subject[:text]).to match('are not allowed') | ||||
|       expect(subject[:response_type]).to be(:ephemeral) | ||||
|     it_behaves_like 'displays an error message' do | ||||
|       let(:error_message) { 'you do not have access to the GitLab project' } | ||||
|     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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -288,6 +288,14 @@ describe GlobalPolicy do | |||
|       it { is_expected.not_to be_allowed(:use_slash_commands) } | ||||
|     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 | ||||
|       before do | ||||
|         current_user.lock_access! | ||||
|  |  | |||
|  | @ -94,16 +94,32 @@ RSpec.shared_examples 'chat slash commands service' do | |||
|           subject.trigger(params) | ||||
|         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 | ||||
|           before do | ||||
|             chat_name.user.block | ||||
|           end | ||||
| 
 | ||||
|           it 'blocks command execution' do | ||||
|             expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute) | ||||
|           it_behaves_like 'blocks command execution' do | ||||
|             let(:error_message) { 'you do not have access to the GitLab project' } | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|             result = subject.trigger(params) | ||||
|             expect(result).to include(text: /^You are not allowed/) | ||||
|         context 'when user is deactivated' do | ||||
|           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 | ||||
|  |  | |||
|  | @ -6,12 +6,12 @@ describe PruneOldEventsWorker do | |||
|   describe '#perform' do | ||||
|     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_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(Event.find_by(id: expired_event.id)).to be_nil | ||||
|     end | ||||
|  | @ -26,9 +26,9 @@ describe PruneOldEventsWorker do | |||
|       expect(not_expired_13_month_event.reload).to be_present | ||||
|     end | ||||
| 
 | ||||
|     it 'leaves events from 2 years ago' do | ||||
|     it 'leaves events from 3 years ago' do | ||||
|       subject.perform | ||||
|       expect(not_expired_2_years_event).to be_present | ||||
|       expect(not_expired_3_years_event).to be_present | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue