From 8f5ebbe2c7488fd8285e528cc3c0b2b3fdd0d2e0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 6 Aug 2021 06:10:16 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../components/board_filtered_search.vue | 35 +++++++++++- .../issue_board_filtered_search.vue | 16 +++++- .../components/content_editor_error.vue | 31 +++++++++++ .../components/editor_state_observer.vue | 3 + .../components/issues_list_app.vue | 1 - ...ique_index_to_vulnerability_flags_table.rb | 17 ++++++ db/schema_migrations/20210803110920 | 1 + db/structure.sql | 2 + lib/tasks/gitlab/gitaly.rake | 55 +++++++++++++++++++ .../components/board_filtered_search_spec.js | 4 +- .../issue_board_filtered_search_spec.js | 31 ++++++----- spec/frontend/boards/mock_data.js | 15 ++++- .../components/content_editor_error_spec.js | 54 ++++++++++++++++++ spec/support/helpers/test_env.rb | 2 +- 14 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 app/assets/javascripts/content_editor/components/content_editor_error.vue create mode 100644 db/migrate/20210803110920_add_unique_index_to_vulnerability_flags_table.rb create mode 100644 db/schema_migrations/20210803110920 create mode 100644 spec/frontend/content_editor/components/content_editor_error_spec.js diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue index cfd6b21fa66..baefac572f1 100644 --- a/app/assets/javascripts/boards/components/board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/board_filtered_search.vue @@ -27,7 +27,13 @@ export default { }, computed: { urlParams() { - const { authorUsername, labelName, assigneeUsername, search } = this.filterParams; + const { + authorUsername, + labelName, + assigneeUsername, + search, + milestoneTitle, + } = this.filterParams; let notParams = {}; if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) { @@ -36,6 +42,7 @@ export default { 'not[label_name][]': this.filterParams.not.labelName, 'not[author_username]': this.filterParams.not.authorUsername, 'not[assignee_username]': this.filterParams.not.assigneeUsername, + 'not[milestone_title]': this.filterParams.not.milestoneTitle, }, undefined, ); @@ -46,6 +53,7 @@ export default { author_username: authorUsername, 'label_name[]': labelName, assignee_username: assigneeUsername, + milestone_title: milestoneTitle, search, }; }, @@ -64,7 +72,13 @@ export default { this.performSearch(); }, getFilteredSearchValue() { - const { authorUsername, labelName, assigneeUsername, search } = this.filterParams; + const { + authorUsername, + labelName, + assigneeUsername, + search, + milestoneTitle, + } = this.filterParams; const filteredSearchValue = []; if (authorUsername) { @@ -90,6 +104,13 @@ export default { ); } + if (milestoneTitle) { + filteredSearchValue.push({ + type: 'milestone_title', + value: { data: milestoneTitle, operator: '=' }, + }); + } + if (this.filterParams['not[authorUsername]']) { filteredSearchValue.push({ type: 'author_username', @@ -97,6 +118,13 @@ export default { }); } + if (this.filterParams['not[milestoneTitle]']) { + filteredSearchValue.push({ + type: 'milestone_title', + value: { data: this.filterParams['not[milestoneTitle]'], operator: '!=' }, + }); + } + if (this.filterParams['not[assigneeUsername]']) { filteredSearchValue.push({ type: 'assignee_username', @@ -143,6 +171,9 @@ export default { case 'label_name': labels.push(filter.value.data); break; + case 'milestone_title': + filterParams.milestoneTitle = filter.value.data; + break; case 'filtered-search-term': if (filter.value.data) plainText.push(filter.value.data); break; diff --git a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue index d8dac17d326..22099f695ee 100644 --- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue @@ -1,4 +1,5 @@ + diff --git a/app/assets/javascripts/content_editor/components/editor_state_observer.vue b/app/assets/javascripts/content_editor/components/editor_state_observer.vue index acdca67bff7..2eeb0719096 100644 --- a/app/assets/javascripts/content_editor/components/editor_state_observer.vue +++ b/app/assets/javascripts/content_editor/components/editor_state_observer.vue @@ -5,6 +5,9 @@ export const tiptapToComponentMap = { update: 'docUpdate', selectionUpdate: 'selectionUpdate', transaction: 'transaction', + focus: 'focus', + blur: 'blur', + error: 'error', }; const getComponentEventName = (tiptapEventName) => tiptapToComponentMap[tiptapEventName]; diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 6563094ef72..17d35212a0d 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -275,7 +275,6 @@ export default { avatar_url: gon.current_user_avatar_url, }); } - const tokens = [ { type: TOKEN_TYPE_AUTHOR, diff --git a/db/migrate/20210803110920_add_unique_index_to_vulnerability_flags_table.rb b/db/migrate/20210803110920_add_unique_index_to_vulnerability_flags_table.rb new file mode 100644 index 00000000000..38d72496484 --- /dev/null +++ b/db/migrate/20210803110920_add_unique_index_to_vulnerability_flags_table.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddUniqueIndexToVulnerabilityFlagsTable < ActiveRecord::Migration[6.1] + include Gitlab::Database::MigrationHelpers + + INDEX_NAME = 'index_vulnerability_flags_on_unique_columns' + + disable_ddl_transaction! + + def up + add_concurrent_index :vulnerability_flags, [:vulnerability_occurrence_id, :flag_type, :origin], name: INDEX_NAME, unique: true + end + + def down + remove_concurrent_index_by_name :vulnerability_flags, INDEX_NAME + end +end diff --git a/db/schema_migrations/20210803110920 b/db/schema_migrations/20210803110920 new file mode 100644 index 00000000000..69ba671ea7b --- /dev/null +++ b/db/schema_migrations/20210803110920 @@ -0,0 +1 @@ +529cf86e09b5aa9015b604e73827cb21e92ced401f30dfb281115a506596bd4e \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 688cd0f1452..93b4d62bfea 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -25450,6 +25450,8 @@ CREATE INDEX index_vulnerability_findings_remediations_on_remediation_id ON vuln CREATE UNIQUE INDEX index_vulnerability_findings_remediations_on_unique_keys ON vulnerability_findings_remediations USING btree (vulnerability_occurrence_id, vulnerability_remediation_id); +CREATE UNIQUE INDEX index_vulnerability_flags_on_unique_columns ON vulnerability_flags USING btree (vulnerability_occurrence_id, flag_type, origin); + CREATE INDEX index_vulnerability_flags_on_vulnerability_occurrence_id ON vulnerability_flags USING btree (vulnerability_occurrence_id); CREATE INDEX index_vulnerability_historical_statistics_on_date_and_id ON vulnerability_historical_statistics USING btree (date, id); diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake index df75b3cf716..6675439e430 100644 --- a/lib/tasks/gitlab/gitaly.rake +++ b/lib/tasks/gitlab/gitaly.rake @@ -2,6 +2,42 @@ namespace :gitlab do namespace :gitaly do + desc 'Installs gitaly for running tests within gitlab-development-kit' + task :test_install, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args| + inside_gdk = Rails.env.test? && File.exist?(Rails.root.join('../GDK_ROOT')) + + if ENV['FORCE_GITALY_INSTALL'] || !inside_gdk + Rake::Task["gitlab:gitaly:install"].invoke(*args) + + next + end + + gdk_gitaly_dir = ENV.fetch('GDK_GITALY', Rails.root.join('../gitaly')) + + # Our test setup expects a git repo, so clone rather than copy + version = Gitlab::GitalyClient.expected_server_version + checkout_or_clone_version(version: version, repo: gdk_gitaly_dir, target_dir: args.dir, clone_opts: %w[--depth 1]) + + # We assume the GDK gitaly already compiled binaries + build_dir = File.join(gdk_gitaly_dir, '_build') + FileUtils.cp_r(build_dir, args.dir) + + # We assume the GDK gitaly already ran bundle install + bundle_dir = File.join(gdk_gitaly_dir, 'ruby', '.bundle') + FileUtils.cp_r(bundle_dir, File.join(args.dir, 'ruby')) + + # For completeness we copy this for gitaly's make target + ruby_bundle_file = File.join(gdk_gitaly_dir, '.ruby-bundle') + FileUtils.cp_r(ruby_bundle_file, args.dir) + + gitaly_binary = File.join(build_dir, 'bin', 'gitaly') + warn_gitaly_out_of_date!(gitaly_binary, version) + rescue Errno::ENOENT => e + puts "Could not copy files, did you run `gdk update`? Error: #{e.message}" + + raise + end + desc 'GitLab | Gitaly | Install or upgrade gitaly' task :install, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args| warn_user_is_not_gitlab @@ -41,5 +77,24 @@ Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]") _, status = Gitlab::Popen.popen(%w[which gmake]) status == 0 ? 'gmake' : 'make' end + + def warn_gitaly_out_of_date!(gitaly_binary, expected_version) + binary_version, exit_status = Gitlab::Popen.popen(%W[#{gitaly_binary} -version]) + + raise "Failed to run `#{gitaly_binary} -version`" unless exit_status == 0 + + binary_version = binary_version.strip + + # See help for `git describe` for format + git_describe_sha = /g([a-f0-9]{5,40})\z/ + match = binary_version.match(git_describe_sha) + + # Just skip if the version does not have a sha component + return unless match + + return if expected_version.start_with?(match[1]) + + puts "WARNING: #{binary_version.strip} does not exactly match repository version #{expected_version}" + end end end diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js index 6ac5d16e5a3..01dba83dd91 100644 --- a/spec/frontend/boards/components/board_filtered_search_spec.js +++ b/spec/frontend/boards/components/board_filtered_search_spec.js @@ -115,6 +115,7 @@ describe('BoardFilteredSearch', () => { { type: 'author_username', value: { data: 'root', operator: '=' } }, { type: 'label_name', value: { data: 'label', operator: '=' } }, { type: 'label_name', value: { data: 'label2', operator: '=' } }, + { type: 'milestone_title', value: { data: 'New Milestone', operator: '=' } }, ]; jest.spyOn(urlUtility, 'updateHistory'); findFilteredSearch().vm.$emit('onFilter', mockFilters); @@ -122,7 +123,8 @@ describe('BoardFilteredSearch', () => { expect(urlUtility.updateHistory).toHaveBeenCalledWith({ title: '', replace: true, - url: 'http://test.host/?author_username=root&label_name[]=label&label_name[]=label2', + url: + 'http://test.host/?author_username=root&label_name[]=label&label_name[]=label2&milestone_title=New+Milestone', }); }); }); diff --git a/spec/frontend/boards/components/issue_board_filtered_search_spec.js b/spec/frontend/boards/components/issue_board_filtered_search_spec.js index 0e3cf59901e..b6de46f8db8 100644 --- a/spec/frontend/boards/components/issue_board_filtered_search_spec.js +++ b/spec/frontend/boards/components/issue_board_filtered_search_spec.js @@ -1,16 +1,16 @@ import { shallowMount } from '@vue/test-utils'; import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue'; import IssueBoardFilteredSpec from '~/boards/components/issue_board_filtered_search.vue'; -import { BoardType } from '~/boards/constants'; import issueBoardFilters from '~/boards/issue_board_filters'; import { mockTokens } from '../mock_data'; +jest.mock('~/boards/issue_board_filters'); + describe('IssueBoardFilter', () => { let wrapper; - const createComponent = ({ initialFilterParams = {} } = {}) => { + const createComponent = () => { wrapper = shallowMount(IssueBoardFilteredSpec, { - provide: { initialFilterParams }, props: { fullPath: '', boardType: '' }, }); }; @@ -20,7 +20,17 @@ describe('IssueBoardFilter', () => { }); describe('default', () => { + let fetchAuthorsSpy; + let fetchLabelsSpy; beforeEach(() => { + fetchAuthorsSpy = jest.fn(); + fetchLabelsSpy = jest.fn(); + + issueBoardFilters.mockReturnValue({ + fetchAuthors: fetchAuthorsSpy, + fetchLabels: fetchLabelsSpy, + }); + createComponent(); }); @@ -28,17 +38,10 @@ describe('IssueBoardFilter', () => { expect(wrapper.find(BoardFilteredSearch).exists()).toBe(true); }); - it.each([[BoardType.group], [BoardType.project]])( - 'when boardType is %s we pass the correct tokens to BoardFilteredSearch', - (boardType) => { - const { fetchAuthors, fetchLabels } = issueBoardFilters({}, '', boardType); + it('passes the correct tokens to BoardFilteredSearch', () => { + const tokens = mockTokens(fetchLabelsSpy, fetchAuthorsSpy, wrapper.vm.fetchMilestones); - const tokens = mockTokens(fetchLabels, fetchAuthors); - - expect(wrapper.find(BoardFilteredSearch).props('tokens').toString()).toBe( - tokens.toString(), - ); - }, - ); + expect(wrapper.find(BoardFilteredSearch).props('tokens')).toEqual(tokens); + }); }); }); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 420f5aa293b..d1d00786ad3 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -8,6 +8,7 @@ import boardsStore from '~/boards/stores/boards_store'; import { __ } from '~/locale'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; +import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; export const boardObj = { id: 1, @@ -542,7 +543,7 @@ export const mockMoveData = { ...mockMoveIssueParams, }; -export const mockTokens = (fetchLabels, fetchAuthors) => [ +export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ { icon: 'labels', title: __('Label'), @@ -568,6 +569,7 @@ export const mockTokens = (fetchLabels, fetchAuthors) => [ token: AuthorToken, unique: true, fetchAuthors, + preloadedAuthors: [], }, { icon: 'user', @@ -580,5 +582,16 @@ export const mockTokens = (fetchLabels, fetchAuthors) => [ token: AuthorToken, unique: true, fetchAuthors, + preloadedAuthors: [], + }, + { + icon: 'clock', + title: __('Milestone'), + symbol: '%', + type: 'milestone_title', + token: MilestoneToken, + unique: true, + defaultMilestones: [], + fetchMilestones, }, ]; diff --git a/spec/frontend/content_editor/components/content_editor_error_spec.js b/spec/frontend/content_editor/components/content_editor_error_spec.js new file mode 100644 index 00000000000..8723fb5a338 --- /dev/null +++ b/spec/frontend/content_editor/components/content_editor_error_spec.js @@ -0,0 +1,54 @@ +import { GlAlert } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ContentEditorError from '~/content_editor/components/content_editor_error.vue'; +import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue'; +import { createTestEditor, emitEditorEvent } from '../test_utils'; + +describe('content_editor/components/content_editor_error', () => { + let wrapper; + let tiptapEditor; + + const findErrorAlert = () => wrapper.findComponent(GlAlert); + + const createWrapper = async () => { + tiptapEditor = createTestEditor(); + + wrapper = shallowMountExtended(ContentEditorError, { + provide: { + tiptapEditor, + }, + stubs: { + EditorStateObserver, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders error when content editor emits an error event', async () => { + const error = 'error message'; + + createWrapper(); + + await emitEditorEvent({ tiptapEditor, event: 'error', params: { error } }); + + expect(findErrorAlert().text()).toBe(error); + }); + + it('allows dismissing the error', async () => { + const error = 'error message'; + + createWrapper(); + + await emitEditorEvent({ tiptapEditor, event: 'error', params: { error } }); + + findErrorAlert().vm.$emit('dismiss'); + + await nextTick(); + + expect(findErrorAlert().exists()).toBe(false); + }); +}); diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 5d27a47709f..eca0716f484 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -158,7 +158,7 @@ module TestEnv component_timed_setup('Gitaly', install_dir: gitaly_dir, version: Gitlab::GitalyClient.expected_server_version, - task: "gitlab:gitaly:install", + task: "gitlab:gitaly:test_install", task_args: [gitaly_dir, repos_path, gitaly_url].compact) do Gitlab::SetupHelper::Gitaly.create_configuration( gitaly_dir,