summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue35
-rw-r--r--app/assets/javascripts/boards/components/issue_board_filtered_search.vue16
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor_error.vue31
-rw-r--r--app/assets/javascripts/content_editor/components/editor_state_observer.vue3
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue1
-rw-r--r--db/migrate/20210803110920_add_unique_index_to_vulnerability_flags_table.rb17
-rw-r--r--db/schema_migrations/202108031109201
-rw-r--r--db/structure.sql2
-rw-r--r--lib/tasks/gitlab/gitaly.rake55
-rw-r--r--spec/frontend/boards/components/board_filtered_search_spec.js4
-rw-r--r--spec/frontend/boards/components/issue_board_filtered_search_spec.js31
-rw-r--r--spec/frontend/boards/mock_data.js15
-rw-r--r--spec/frontend/content_editor/components/content_editor_error_spec.js54
-rw-r--r--spec/support/helpers/test_env.rb2
14 files changed, 246 insertions, 21 deletions
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 @@
<script>
+import { mapActions } from 'vuex';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import issueBoardFilters from '~/boards/issue_board_filters';
import { TYPE_USER } from '~/graphql_shared/constants';
@@ -6,6 +7,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils';
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 default {
i18n: {
@@ -13,6 +15,7 @@ export default {
label: __('Label'),
author: __('Author'),
assignee: __('Assignee'),
+ milestone: __('Milestone'),
is: __('is'),
isNot: __('is not'),
},
@@ -29,7 +32,7 @@ export default {
},
computed: {
tokens() {
- const { label, is, isNot, author, assignee } = this.$options.i18n;
+ const { label, is, isNot, author, assignee, milestone } = this.$options.i18n;
const { fetchAuthors, fetchLabels } = issueBoardFilters(
this.$apollo,
this.fullPath,
@@ -77,10 +80,21 @@ export default {
fetchAuthors,
preloadedAuthors: this.preloadedAuthors(),
},
+ {
+ type: 'milestone_title',
+ title: milestone,
+ icon: 'clock',
+ symbol: '%',
+ token: MilestoneToken,
+ unique: true,
+ defaultMilestones: [], // todo: https://gitlab.com/gitlab-org/gitlab/-/issues/337044#note_640010094
+ fetchMilestones: this.fetchMilestones,
+ },
];
},
},
methods: {
+ ...mapActions(['fetchMilestones']),
preloadedAuthors() {
return gon?.current_user_id
? [
diff --git a/app/assets/javascripts/content_editor/components/content_editor_error.vue b/app/assets/javascripts/content_editor/components/content_editor_error.vue
new file mode 100644
index 00000000000..031ea92a7e9
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/content_editor_error.vue
@@ -0,0 +1,31 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import EditorStateObserver from './editor_state_observer.vue';
+
+export default {
+ components: {
+ GlAlert,
+ EditorStateObserver,
+ },
+ data() {
+ return {
+ error: null,
+ };
+ },
+ methods: {
+ displayError({ error }) {
+ this.error = error;
+ },
+ dismissError() {
+ this.error = null;
+ },
+ },
+};
+</script>
+<template>
+ <editor-state-observer @error="displayError">
+ <gl-alert v-if="error" class="gl-mb-6" variant="danger" @dismiss="dismissError">
+ {{ error }}
+ </gl-alert>
+ </editor-state-observer>
+</template>
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,