diff options
25 files changed, 803 insertions, 334 deletions
diff --git a/.gitlab/ci/dev-fixtures.gitlab-ci.yml b/.gitlab/ci/dev-fixtures.gitlab-ci.yml index a5dab5d8708..a0b5100992b 100644 --- a/.gitlab/ci/dev-fixtures.gitlab-ci.yml +++ b/.gitlab/ci/dev-fixtures.gitlab-ci.yml @@ -1,6 +1,54 @@ +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-not-ee: &if-not-ee + if: '$CI_PROJECT_NAME !~ /^gitlab(-ee)?$/' + +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-default-refs: &if-default-refs + if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG' + +# Make sure to update all the similar patterns in other CI config files if you modify these patterns +.code-backstage-patterns: &code-backstage-patterns + - ".gitlab/ci/**/*" + - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" + - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" + - ".csscomb.json" + - "Dockerfile.assets" + - "*_VERSION" + - "Gemfile{,.lock}" + - "Rakefile" + - "{babel.config,jest.config}.js" + - "config.ru" + - "{package.json,yarn.lock}" + - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*" + - "doc/api/graphql/reference/*" # Files in this folder are auto-generated + # Backstage changes + - "Dangerfile" + - "danger/**/*" + - "{,ee/}fixtures/**/*" + - "{,ee/}rubocop/**/*" + - "{,ee/}spec/**/*" + - "doc/README.md" # Some RSpec test rely on this file + +.dev-fixtures:rules:ee-and-foss: + rules: + - <<: *if-default-refs + changes: *code-backstage-patterns + when: on_success + +.dev-fixtures:rules:ee-only: + rules: + - <<: *if-not-ee + when: never + - <<: *if-default-refs + changes: *code-backstage-patterns + when: on_success + .run-dev-fixtures: extends: - - .only-code-rails-job-base + - .default-tags + - .default-retry + - .default-cache + - .default-before_script - .use-pg9 stage: test needs: ["setup-test-env"] @@ -13,17 +61,19 @@ SIZE: 0 # number of external projects to fork, requires network connection # SEED_NESTED_GROUPS: "false" # requires network connection -run-dev-fixtures-foss: - extends: .run-dev-fixtures +run-dev-fixtures: + extends: + - .run-dev-fixtures + - .dev-fixtures:rules:ee-and-foss script: - scripts/gitaly-test-spawn - RAILS_ENV=test bundle exec rake db:seed_fu run-dev-fixtures-ee: extends: - - .only-ee - - .use-pg9-ee - .run-dev-fixtures + - .dev-fixtures:rules:ee-only + - .use-pg9-ee script: - scripts/gitaly-test-spawn - cp ee/db/fixtures/development/* $FIXTURE_PATH diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 03685d1f6a8..3d890dfea7e 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -1,41 +1,120 @@ -.only-master: - only: - refs: - - master - -.rake-exec: +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-not-ee: &if-not-ee + if: '$CI_PROJECT_NAME !~ /^gitlab(-ee)?$/' + +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-master-refs: &if-master-refs + if: '$CI_COMMIT_REF_NAME == "master"' + +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-default-refs: &if-default-refs + if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG' + +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-merge-request: &if-merge-request + if: '$CI_MERGE_REQUEST_IID' + +# Make sure to update all the similar patterns in other CI config files if you modify these patterns +.code-backstage-patterns: &code-backstage-patterns + - ".gitlab/ci/**/*" + - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" + - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" + - ".csscomb.json" + - "Dockerfile.assets" + - "*_VERSION" + - "Gemfile{,.lock}" + - "Rakefile" + - "{babel.config,jest.config}.js" + - "config.ru" + - "{package.json,yarn.lock}" + - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*" + - "doc/api/graphql/reference/*" # Files in this folder are auto-generated + # Backstage changes + - "Dangerfile" + - "danger/**/*" + - "{,ee/}fixtures/**/*" + - "{,ee/}rubocop/**/*" + - "{,ee/}spec/**/*" + - "doc/README.md" # Some RSpec test rely on this file + +# Make sure to update all the similar patterns in other CI config files if you modify these patterns +.code-backstage-qa-patterns: &code-backstage-qa-patterns + - ".gitlab/ci/**/*" + - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" + - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" + - ".csscomb.json" + - "Dockerfile.assets" + - "*_VERSION" + - "Gemfile{,.lock}" + - "Rakefile" + - "{babel.config,jest.config}.js" + - "config.ru" + - "{package.json,yarn.lock}" + - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*" + - "doc/api/graphql/reference/*" # Files in this folder are auto-generated + # Backstage changes + - "Dangerfile" + - "danger/**/*" + - "{,ee/}fixtures/**/*" + - "{,ee/}rubocop/**/*" + - "{,ee/}spec/**/*" + - "doc/README.md" # Some RSpec test rely on this file + # QA changes + - ".dockerignore" + - "qa/**/*" + +.rails:rules:ee-and-foss: + rules: + - <<: *if-default-refs + changes: *code-backstage-patterns + when: on_success + +.rails:rules:default-refs-code-backstage-qa: + rules: + - <<: *if-default-refs + changes: *code-backstage-qa-patterns + when: on_success + +.rails:rules:master-refs: + rules: + - <<: *if-master-refs + when: on_success + +.rails:rules:master-refs-ee-only: + rules: + - <<: *if-not-ee + when: never + - <<: *if-master-refs + when: on_success + +.rails:rules:ee-only: + rules: + - <<: *if-not-ee + when: never + - <<: *if-default-refs + changes: *code-backstage-patterns + when: on_success + +.rails:needs:setup-and-assets: + needs: + - job: setup-test-env + artifacts: true + - job: compile-assets pull-cache + artifacts: true + +.rails-job-base: extends: - .default-tags - .default-retry - .default-cache - - .default-only - .default-before_script - variables: - SETUP_DB: "false" - script: - - bundle exec rake $CI_JOB_NAME - -.only-code-rails-job-base: - extends: - - .default-tags - - .default-retry - - .default-cache - - .default-only - - .default-before_script - - .only:changes-code-backstage - -.only-code-qa-rails-job-base: - extends: - - .default-tags - - .default-retry - - .default-cache - - .default-only - - .default-before_script - - .only:changes-code-backstage-qa +#################### +# ee and foss jobs # setup-test-env: extends: - - .only-code-qa-rails-job-base + - .rails-job-base + - .rails:rules:default-refs-code-backstage-qa - .use-pg9 stage: prepare script: @@ -50,11 +129,48 @@ setup-test-env: cache: policy: pull-push +static-analysis: + extends: + - .rails-job-base + - .rails:rules:default-refs-code-backstage-qa + - .rails:needs:setup-and-assets + stage: test + variables: + SETUP_DB: "false" + parallel: 2 + script: + - scripts/static-analysis + cache: + key: "debian-stretch-ruby-2.6-and-rubocop" + paths: + - vendor/ruby + - tmp/rubocop_cache + policy: pull-push + +downtime_check: + extends: + - .rails-job-base + - .rails:needs:setup-and-assets + stage: test + rules: + - <<: *if-merge-request + changes: *code-backstage-patterns + when: on_success + variables: + SETUP_DB: "false" + script: + - bundle exec rake downtime_check + .rspec-base: - extends: .only-code-rails-job-base + extends: .rails-job-base stage: test - needs: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache"] - dependencies: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache"] + needs: + - job: setup-test-env + artifacts: true + - job: retrieve-tests-metadata + artifacts: true + - job: compile-assets pull-cache + artifacts: true script: - source scripts/rspec_helpers.sh - rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration" @@ -72,26 +188,22 @@ setup-test-env: reports: junit: junit_rspec.xml -.rspec-base-foss: - extends: [".rspec-base", ".only-ee-as-if-foss"] - needs: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache foss"] - dependencies: ["setup-test-env", "retrieve-tests-metadata", "compile-assets pull-cache foss"] - -.rspec-base-pg9: +.rspec-base-quarantine: extends: - .rspec-base - .use-pg9 + variables: + RSPEC_OPTS: "--tag quarantine -- spec/" + script: + - source scripts/rspec_helpers.sh + - rspec_simple_job "${RSPEC_OPTS}" + allow_failure: true -.rspec-base-pg9-foss: - extends: - - .rspec-base-foss - - .use-pg9 - -.rspec-base-pg10: +.rspec-base-pg9: extends: - .rspec-base - - .use-pg10 - - .only-master + - .rails:rules:ee-and-foss + - .use-pg9 .rspec-base-migration: script: @@ -104,200 +216,33 @@ rspec migration pg9: - .rspec-base-migration parallel: 5 -rspec migration pg9-foss: - extends: - - .rspec-base-pg9-foss - - .rspec-base-migration - parallel: 5 - rspec unit pg9: extends: .rspec-base-pg9 parallel: 20 -rspec unit pg9-foss: - extends: .rspec-base-pg9-foss - parallel: 20 - rspec integration pg9: extends: .rspec-base-pg9 parallel: 8 -rspec integration pg9-foss: - extends: .rspec-base-pg9-foss - parallel: 8 - rspec system pg9: extends: .rspec-base-pg9 parallel: 24 -rspec system pg9-foss: - extends: .rspec-base-pg9-foss - parallel: 24 - -rspec unit pg10: - extends: .rspec-base-pg10 - parallel: 20 - -rspec integration pg10: - extends: .rspec-base-pg10 - parallel: 8 - -rspec system pg10: - extends: .rspec-base-pg10 - parallel: 24 - -.rspec-ee-base-pg9: - extends: - - .rspec-base - - .only-ee - - .use-pg9-ee - -.rspec-ee-base-pg10: - extends: - - .rspec-base - - .only-ee - - .use-pg10-ee - -rspec-ee migration pg9: - extends: - - .rspec-ee-base-pg9 - - .rspec-base-migration - parallel: 2 - -rspec-ee unit pg9: - extends: .rspec-ee-base-pg9 - parallel: 10 - -rspec-ee integration pg9: - extends: .rspec-ee-base-pg9 - parallel: 4 - -rspec-ee system pg9: - extends: .rspec-ee-base-pg9 - parallel: 6 - -rspec-ee migration pg10: - extends: - - .rspec-ee-base-pg10 - - .rspec-base-migration - - .only-master - parallel: 2 - -rspec-ee unit pg10: - extends: - - .rspec-ee-base-pg10 - - .only-master - parallel: 10 - -rspec-ee integration pg10: - extends: - - .rspec-ee-base-pg10 - - .only-master - parallel: 3 - -rspec-ee system pg10: - extends: - - .rspec-ee-base-pg10 - - .only-master - parallel: 5 - -.rspec-ee-base-geo: - extends: - - .rspec-base - - .only-ee - script: - - source scripts/rspec_helpers.sh - - scripts/prepare_postgres_fdw.sh - - rspec_paralellized_job "--tag ~quarantine --tag geo" - -.rspec-ee-base-geo-pg9: - extends: - - .rspec-ee-base-geo - - .use-pg9-ee - -.rspec-ee-base-geo-pg10: - extends: - - .rspec-ee-base-geo - - .use-pg10-ee - -rspec-ee unit pg9 geo: - extends: .rspec-ee-base-geo-pg9 - parallel: 2 - -rspec-ee integration pg9 geo: - extends: .rspec-ee-base-geo-pg9 - -rspec-ee system pg9 geo: - extends: .rspec-ee-base-geo-pg9 - -rspec-ee unit pg10 geo: - extends: .rspec-ee-base-geo-pg10 - parallel: 2 - -rspec-ee integration pg10 geo: - extends: .rspec-ee-base-geo-pg10 - -rspec-ee system pg10 geo: - extends: .rspec-ee-base-geo-pg10 - -rspec quarantine pg9: - extends: - - .rspec-base-pg9 - - .only-master - variables: - RSPEC_OPTS: "--tag quarantine -- spec/" - script: - - source scripts/rspec_helpers.sh - - rspec_simple_job "${RSPEC_OPTS}" - allow_failure: true - -rspec-ee quarantine pg9: - extends: - - rspec quarantine pg9 - - .only-ee - variables: - RSPEC_OPTS: "--tag quarantine -- ee/spec/" - rspec fast_spec_helper: extends: .rspec-base-pg9 script: - bin/rspec spec/fast_spec_helper.rb -static-analysis: - extends: .only-code-qa-rails-job-base - stage: test - needs: ["setup-test-env", "compile-assets pull-cache"] - dependencies: ["setup-test-env", "compile-assets pull-cache"] - variables: - SETUP_DB: "false" - parallel: 2 - script: - - scripts/static-analysis - cache: - key: "debian-stretch-ruby-2.6-and-rubocop" - paths: - - vendor/ruby - - tmp/rubocop_cache - policy: pull-push - -downtime_check: - extends: - - .rake-exec - - .only:changes-code-backstage - - .except:refs-master-tags-stable-deploy - stage: test - needs: ["setup-test-env"] - dependencies: ["setup-test-env"] - .db-job-base: extends: - - .only-code-rails-job-base + - .rails-job-base + - .rails:rules:ee-and-foss - .use-pg9 stage: test - needs: ["setup-test-env"] - dependencies: ["setup-test-env"] + needs: + - job: setup-test-env + artifacts: true -# DB migration, rollback, and seed jobs db:migrate:reset: extends: .db-job-base script: @@ -358,12 +303,14 @@ gitlab:setup: - log/development.log coverage: - extends: .only-code-rails-job-base - cache: - policy: pull + extends: + - .rails-job-base + - .rails:rules:ee-and-foss + stage: post-test variables: SETUP_DB: "false" - stage: post-test + cache: + policy: pull script: - bundle exec scripts/merge-simplecov - bundle exec scripts/gather-test-memory-data @@ -375,11 +322,181 @@ coverage: - coverage/index.html - coverage/assets/ - tmp/memory_test/ +# ee and foss jobs # +#################### + +#################### +# master-only jobs # +rspec quarantine pg9: + extends: + - .rspec-base-quarantine + - .rails:rules:master-refs + +.rspec-base-pg10: + extends: + - .rspec-base + - .rails:rules:master-refs + - .use-pg10 + +rspec unit pg10: + extends: .rspec-base-pg10 + parallel: 20 + +rspec integration pg10: + extends: .rspec-base-pg10 + parallel: 8 + +rspec system pg10: + extends: .rspec-base-pg10 + parallel: 24 +# master-only jobs # +#################### + +######################### +# ee + master-only jobs # +rspec-ee quarantine pg9: + extends: + - .rspec-base-quarantine + - .rails:rules:master-refs-ee-only + variables: + RSPEC_OPTS: "--tag quarantine -- ee/spec/" + +rspec-ee migration pg10: + extends: + - .rspec-ee-base-pg10 + - .rspec-base-migration + - .rails:rules:master-refs + parallel: 2 + +rspec-ee unit pg10: + extends: + - .rspec-ee-base-pg10 + - .rails:rules:master-refs + parallel: 10 + +rspec-ee integration pg10: + extends: + - .rspec-ee-base-pg10 + - .rails:rules:master-refs + parallel: 3 + +rspec-ee system pg10: + extends: + - .rspec-ee-base-pg10 + - .rails:rules:master-refs + parallel: 5 +# ee + master-only jobs # +######################### + +################# +# ee-only jobs # +.rspec-base-ee: + extends: + - .rspec-base + - .rails:rules:ee-only + +.rspec-base-pg9-as-if-foss: + extends: + - .rspec-base-ee + - .as-if-foss + - .use-pg9 + needs: + - job: setup-test-env + artifacts: true + - job: retrieve-tests-metadata + artifacts: true + - job: compile-assets pull-cache foss + artifacts: true + +.rspec-ee-base-pg9: + extends: + - .rspec-base-ee + - .use-pg9-ee + +.rspec-ee-base-pg10: + extends: + - .rspec-base-ee + - .use-pg10-ee + +rspec migration pg9-as-if-foss: + extends: + - .rspec-base-pg9-as-if-foss + - .rspec-base-migration + parallel: 5 + +rspec unit pg9-as-if-foss: + extends: .rspec-base-pg9-as-if-foss + parallel: 20 + +rspec integration pg9-as-if-foss: + extends: .rspec-base-pg9-as-if-foss + parallel: 8 + +rspec system pg9-as-if-foss: + extends: .rspec-base-pg9-as-if-foss + parallel: 24 + +rspec-ee migration pg9: + extends: + - .rspec-ee-base-pg9 + - .rspec-base-migration + parallel: 2 + +rspec-ee unit pg9: + extends: .rspec-ee-base-pg9 + parallel: 10 + +rspec-ee integration pg9: + extends: .rspec-ee-base-pg9 + parallel: 4 + +rspec-ee system pg9: + extends: .rspec-ee-base-pg9 + parallel: 6 + +.rspec-ee-base-geo: + extends: .rspec-base-ee + script: + - source scripts/rspec_helpers.sh + - scripts/prepare_postgres_fdw.sh + - rspec_paralellized_job "--tag ~quarantine --tag geo" + +.rspec-ee-base-geo-pg9: + extends: + - .rspec-ee-base-geo + - .use-pg9-ee + +.rspec-ee-base-geo-pg10: + extends: + - .rspec-ee-base-geo + - .use-pg10-ee + +rspec-ee unit pg9 geo: + extends: .rspec-ee-base-geo-pg9 + parallel: 2 + +rspec-ee integration pg9 geo: + extends: .rspec-ee-base-geo-pg9 + +rspec-ee system pg9 geo: + extends: .rspec-ee-base-geo-pg9 + +rspec-ee unit pg10 geo: + extends: .rspec-ee-base-geo-pg10 + parallel: 2 + +rspec-ee integration pg10 geo: + extends: .rspec-ee-base-geo-pg10 + +rspec-ee system pg10 geo: + extends: .rspec-ee-base-geo-pg10 db:rollback geo: extends: - db:rollback - - .only-ee + - .rails:rules:ee-only script: - bundle exec rake geo:db:migrate VERSION=20170627195211 - bundle exec rake geo:db:migrate +# ee-only jobs # +################ diff --git a/app/assets/javascripts/diffs/components/diff_file_row.vue b/app/assets/javascripts/diffs/components/diff_file_row.vue new file mode 100644 index 00000000000..f41337762fd --- /dev/null +++ b/app/assets/javascripts/diffs/components/diff_file_row.vue @@ -0,0 +1,17 @@ +<script> +/** + * This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue` + * https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720 + */ +import FileRow from '~/vue_shared/components/file_row.vue'; + +export default { + components: { + FileRow, + }, +}; +</script> + +<template> + <file-row v-bind="$attrs" v-on="$listeners" /> +</template> diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index b13619a5471..18c3002825e 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -3,7 +3,8 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import { GlTooltipDirective } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; -import FileRow from '~/vue_shared/components/file_row.vue'; +import FileTree from '~/vue_shared/components/file_tree.vue'; +import DiffFileRow from './diff_file_row.vue'; import FileRowStats from './file_row_stats.vue'; export default { @@ -12,7 +13,7 @@ export default { }, components: { Icon, - FileRow, + FileTree, }, props: { hideFileStats: { @@ -61,6 +62,7 @@ export default { searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), { modifier_key: /Mac/i.test(navigator.userAgent) ? '⌘' : 'Ctrl+', }), + DiffFileRow, }; </script> @@ -91,7 +93,7 @@ export default { </div> <div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll"> <template v-if="filteredTreeList.length"> - <file-row + <file-tree v-for="file in filteredTreeList" :key="file.key" :file="file" @@ -99,6 +101,7 @@ export default { :hide-extra-on-tree="true" :extra-component="fileRowExtraComponent" :show-changed-icon="true" + :file-row-component="$options.DiffFileRow" @toggleTreeOpen="toggleTreeOpen" @clickFile="scrollToFile" /> diff --git a/app/assets/javascripts/ide/components/ide_file_row.vue b/app/assets/javascripts/ide/components/ide_file_row.vue new file mode 100644 index 00000000000..f41337762fd --- /dev/null +++ b/app/assets/javascripts/ide/components/ide_file_row.vue @@ -0,0 +1,17 @@ +<script> +/** + * This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue` + * https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720 + */ +import FileRow from '~/vue_shared/components/file_row.vue'; + +export default { + components: { + FileRow, + }, +}; +</script> + +<template> + <file-row v-bind="$attrs" v-on="$listeners" /> +</template> diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue index bacdfc7c05e..151f7f0c421 100644 --- a/app/assets/javascripts/ide/components/ide_tree_list.vue +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -1,7 +1,8 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import { GlSkeletonLoading } from '@gitlab/ui'; -import FileRow from '~/vue_shared/components/file_row.vue'; +import FileTree from '~/vue_shared/components/file_tree.vue'; +import IdeFileRow from './ide_file_row.vue'; import NavDropdown from './nav_dropdown.vue'; import FileRowExtra from './file_row_extra.vue'; @@ -9,7 +10,7 @@ export default { components: { GlSkeletonLoading, NavDropdown, - FileRow, + FileTree, }, props: { viewerType: { @@ -36,6 +37,7 @@ export default { ...mapActions(['updateViewer', 'toggleTreeOpen']), }, FileRowExtra, + IdeFileRow, }; </script> @@ -53,12 +55,13 @@ export default { </header> <div class="ide-tree-body h-100"> <template v-if="currentTree.tree.length"> - <file-row + <file-tree v-for="file in currentTree.tree" :key="file.key" :file="file" :level="0" :extra-component="$options.FileRowExtra" + :file-row-component="$options.IdeFileRow" @toggleTreeOpen="toggleTreeOpen" /> </template> diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index 0258a62b390..22fab1b03f2 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -195,8 +195,8 @@ export default { </gl-dropdown-item> <gl-dropdown-item v-if="clipboardText" + ref="copyChartLink" v-track-event="generateLinkToChartOptions(clipboardText)" - class="js-chart-link" :data-clipboard-text="clipboardText" @click="showToast(clipboardText)" > diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index 611001df32f..d962f644ff8 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -62,9 +62,6 @@ export default { 'is-open': this.file.opened, }; }, - childFilesLevel() { - return this.file.isHeader ? 0 : this.level + 1; - }, }, watch: { 'file.active': function fileActiveWatch(active) { @@ -131,53 +128,38 @@ export default { </script> <template> - <div> - <file-header v-if="file.isHeader" :path="file.path" /> - <div - v-else - :class="fileClass" - :title="file.name" - class="file-row" - role="button" - @click="clickFile" - @mouseleave="toggleDropdown(false)" - > - <div class="file-row-name-container"> - <span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated"> - <file-icon - v-if="!showChangedIcon || file.type === 'tree'" - class="file-row-icon" - :file-name="file.name" - :loading="file.loading" - :folder="isTree" - :opened="file.opened" - :size="16" - /> - <changed-file-icon v-else :file="file" :size="16" class="append-right-5" /> - {{ file.name }} - </span> - <component - :is="extraComponent" - v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')" - :file="file" - :dropdown-open="dropdownOpen" - @toggle="toggleDropdown($event)" + <file-header v-if="file.isHeader" :path="file.path" /> + <div + v-else + :class="fileClass" + :title="file.name" + class="file-row" + role="button" + @click="clickFile" + @mouseleave="toggleDropdown(false)" + > + <div class="file-row-name-container"> + <span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated"> + <file-icon + v-if="!showChangedIcon || file.type === 'tree'" + class="file-row-icon" + :file-name="file.name" + :loading="file.loading" + :folder="isTree" + :opened="file.opened" + :size="16" /> - </div> - </div> - <template v-if="file.opened || file.isHeader"> - <file-row - v-for="childFile in file.tree" - :key="childFile.key" - :file="childFile" - :level="childFilesLevel" - :hide-extra-on-tree="hideExtraOnTree" - :extra-component="extraComponent" - :show-changed-icon="showChangedIcon" - @toggleTreeOpen="toggleTreeOpen" - @clickFile="clickedFile" + <changed-file-icon v-else :file="file" :size="16" class="append-right-5" /> + {{ file.name }} + </span> + <component + :is="extraComponent" + v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')" + :file="file" + :dropdown-open="dropdownOpen" + @toggle="toggleDropdown($event)" /> - </template> + </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/file_tree.vue b/app/assets/javascripts/vue_shared/components/file_tree.vue new file mode 100644 index 00000000000..e7817b8f910 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/file_tree.vue @@ -0,0 +1,47 @@ +<script> +export default { + name: 'FileTree', + props: { + fileRowComponent: { + type: Object, + required: true, + }, + level: { + type: Number, + required: true, + }, + file: { + type: Object, + required: true, + }, + }, + computed: { + childFilesLevel() { + return this.file.isHeader ? 0 : this.level + 1; + }, + }, +}; +</script> + +<template> + <div> + <component + :is="fileRowComponent" + :level="level" + :file="file" + v-bind="$attrs" + v-on="$listeners" + /> + <template v-if="file.opened || file.isHeader"> + <file-tree + v-for="childFile in file.tree" + :key="childFile.key" + :file-row-component="fileRowComponent" + :level="childFilesLevel" + :file="childFile" + v-bind="$attrs" + v-on="$listeners" + /> + </template> + </div> +</template> diff --git a/changelogs/unreleased/remove-issue_link_types.yml b/changelogs/unreleased/remove-issue_link_types.yml new file mode 100644 index 00000000000..f123327de13 --- /dev/null +++ b/changelogs/unreleased/remove-issue_link_types.yml @@ -0,0 +1,5 @@ +--- +title: Expose issue link type in REST API +merge_request: 24175 +author: +type: added diff --git a/db/post_migrate/20191115115522_migrate_epic_notes_mentions_to_db.rb b/db/post_migrate/20191115115522_migrate_epic_notes_mentions_to_db.rb index e0b3c36b57d..7914ff59dbd 100644 --- a/db/post_migrate/20191115115522_migrate_epic_notes_mentions_to_db.rb +++ b/db/post_migrate/20191115115522_migrate_epic_notes_mentions_to_db.rb @@ -14,7 +14,7 @@ class MigrateEpicNotesMentionsToDb < ActiveRecord::Migration[5.2] INDEX_NAME = 'epic_mentions_temp_index' INDEX_CONDITION = "note LIKE '%@%'::text AND notes.noteable_type = 'Epic'" QUERY_CONDITIONS = "#{INDEX_CONDITION} AND epic_user_mentions.epic_id IS NULL" - JOIN = 'LEFT JOIN epic_user_mentions ON notes.id = epic_user_mentions.note_id' + JOIN = 'INNER JOIN epics ON epics.id = notes.noteable_id LEFT JOIN epic_user_mentions ON notes.id = epic_user_mentions.note_id' class Note < ActiveRecord::Base include EachBatch diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 51d5c758084..b98f02ccc98 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -288,10 +288,13 @@ For source installations, edit the `gitlab.yml` and set the Sidekiq ## `gitlab-shell.log` -This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for -Omnibus GitLab packages or in `/home/git/gitlab-shell/gitlab-shell.log` for +This file lives in `/var/log/gitlab/gitaly/gitlab-shell.log` for +Omnibus GitLab packages or in `/home/git/gitaly/gitlab-shell.log` for installations from source. +NOTE: **Note** +For GitLab 12.5 and earlier the file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log`. + GitLab Shell is used by GitLab for executing Git commands and provide SSH access to Git repositories. For example: diff --git a/doc/api/issue_links.md b/doc/api/issue_links.md index 941cc0b1347..2a64c2370fc 100644 --- a/doc/api/issue_links.md +++ b/doc/api/issue_links.md @@ -67,7 +67,7 @@ POST /projects/:id/issues/:issue_iid/links | `issue_iid` | integer | yes | The internal ID of a project's issue | | `target_project_id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) of a target project | | `target_issue_iid` | integer/string | yes | The internal ID of a target project's issue | -| `link_type` | string | no | The type of the relation ("relates_to", "blocks", "is_blocked_by"), defaults to "relates_to"). Ignored unless `issue_link_types` feature flag is enabled. | +| `link_type` | string | no | The type of the relation ("relates_to", "blocks", "is_blocked_by"), defaults to "relates_to"). | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues/1/links?target_project_id=5&target_issue_iid=1" diff --git a/doc/development/integrations/secure.md.md b/doc/development/integrations/secure.md index b9b37a7e298..b9b37a7e298 100644 --- a/doc/development/integrations/secure.md.md +++ b/doc/development/integrations/secure.md diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 25672d510c7..41040258e01 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -95,7 +95,7 @@ The following table depicts the various user permission levels in a project. | Stop environments | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ | | Cancel and retry jobs | | | ✓ | ✓ | ✓ | -| Create or update commit status | | | ✓ | ✓ | ✓ | +| Create or update commit status | | | ✓ (*5*) | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | | Create/edit/delete project milestones | | | ✓ | ✓ | ✓ | @@ -144,6 +144,7 @@ The following table depicts the various user permission levels in a project. (*2*): Guest users can only view the confidential issues they created themselves. (*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**. (*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [Protected Branches](./project/protected_branches.md). +(*5*): If the [branch is protected](./project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings), this depends on the access Developers and Maintainers are given. ## Project features permissions diff --git a/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb b/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb index 7d40dfbcdc4..40f45301727 100644 --- a/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb +++ b/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb @@ -13,7 +13,7 @@ module Gitlab def perform(resource_model, join, conditions, with_notes, start_id, end_id) resource_model = "#{ISOLATION_MODULE}::#{resource_model}".constantize if resource_model.is_a?(String) - model = with_notes ? "#{ISOLATION_MODULE}::Note".constantize : resource_model + model = with_notes ? Gitlab::BackgroundMigration::UserMentions::Models::Note : resource_model resource_user_mention_model = resource_model.user_mention_model records = model.joins(join).where(conditions).where(id: start_id..end_id) @@ -21,7 +21,7 @@ module Gitlab records.in_groups_of(BULK_INSERT_SIZE, false).each do |records| mentions = [] records.each do |record| - mentions << record.build_mention_values + mentions << record.build_mention_values(resource_user_mention_model.resource_foreign_key) end Gitlab::Database.bulk_insert( diff --git a/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb b/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb index 40aab896212..b7fa92a6686 100644 --- a/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb +++ b/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb @@ -65,11 +65,11 @@ module Gitlab false end - def build_mention_values + def build_mention_values(resource_foreign_key) refs = all_references(author) { - "#{self.user_mention_model.resource_foreign_key}": user_mention_resource_id, + "#{resource_foreign_key}": user_mention_resource_id, note_id: user_mention_note_id, mentioned_users_ids: array_to_sql(refs.mentioned_users.pluck(:id)), mentioned_projects_ids: array_to_sql(refs.mentioned_projects.pluck(:id)), diff --git a/lib/gitlab/background_migration/user_mentions/models/note.rb b/lib/gitlab/background_migration/user_mentions/models/note.rb index c2828202907..dc364d7af5a 100644 --- a/lib/gitlab/background_migration/user_mentions/models/note.rb +++ b/lib/gitlab/background_migration/user_mentions/models/note.rb @@ -19,10 +19,6 @@ module Gitlab belongs_to :noteable, polymorphic: true belongs_to :project - def user_mention_model - "#{CreateResourceUserMention::ISOLATION_MODULE}::#{noteable.class}".constantize.user_mention_model - end - def for_personal_snippet? noteable.class.name == 'PersonalSnippet' end diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js new file mode 100644 index 00000000000..3e95d1d55bd --- /dev/null +++ b/spec/frontend/diffs/components/diff_file_row_spec.js @@ -0,0 +1,25 @@ +import { shallowMount } from '@vue/test-utils'; +import DiffFileRow from '~/diffs/components/diff_file_row.vue'; +import FileRow from '~/vue_shared/components/file_row.vue'; + +describe('Diff File Row component', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(DiffFileRow, { + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders file row component', () => { + createComponent({ + level: 4, + file: {}, + }); + expect(wrapper.find(FileRow).exists()).toEqual(true); + }); +}); diff --git a/spec/frontend/ide/components/ide_file_row_spec.js b/spec/frontend/ide/components/ide_file_row_spec.js new file mode 100644 index 00000000000..7531bc3e144 --- /dev/null +++ b/spec/frontend/ide/components/ide_file_row_spec.js @@ -0,0 +1,25 @@ +import { shallowMount } from '@vue/test-utils'; +import IdeFileRow from '~/ide/components/ide_file_row.vue'; +import FileRow from '~/vue_shared/components/file_row.vue'; + +describe('Ide File Row component', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(IdeFileRow, { + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders file row component', () => { + createComponent({ + level: 4, + file: {}, + }); + expect(wrapper.find(FileRow).exists()).toEqual(true); + }); +}); diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 70b2c9cf527..7a039d46d39 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -1,5 +1,5 @@ import { shallowMount, createLocalVue, mount } from '@vue/test-utils'; -import { GlDropdownItem, GlButton, GlToast } from '@gitlab/ui'; +import { GlDropdownItem, GlButton } from '@gitlab/ui'; import VueDraggable from 'vuedraggable'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; @@ -10,6 +10,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue'; import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; +import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; import { setupComponentStore, propsData } from '../init_utils'; @@ -540,37 +541,36 @@ describe('Dashboard', () => { }); }); - // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 - // eslint-disable-next-line jest/no-disabled-tests - describe.skip('link to chart', () => { + describe('Clipboard text in panels', () => { const currentDashboard = 'TEST_DASHBOARD'; - localVue.use(GlToast); - const link = () => wrapper.find('.js-chart-link'); - const clipboardText = () => link().element.dataset.clipboardText; + + const getClipboardTextAt = i => + wrapper + .findAll(PanelType) + .at(i) + .props('clipboardText'); beforeEach(done => { createShallowWrapper({ hasMetrics: true, currentDashboard }); - setTimeout(done); - }); + setupComponentStore(wrapper); - it('adds a copy button to the dropdown', () => { - expect(link().text()).toContain('Generate link to chart'); + wrapper.vm.$nextTick(done); }); it('contains a link to the dashboard', () => { - expect(clipboardText()).toContain(`dashboard=${currentDashboard}`); - expect(clipboardText()).toContain(`group=`); - expect(clipboardText()).toContain(`title=`); - expect(clipboardText()).toContain(`y_label=`); + expect(getClipboardTextAt(0)).toContain(`dashboard=${currentDashboard}`); + expect(getClipboardTextAt(0)).toContain(`group=`); + expect(getClipboardTextAt(0)).toContain(`title=`); + expect(getClipboardTextAt(0)).toContain(`y_label=`); }); - it('undefined parameter is stripped', done => { + it('strips the undefined parameter', done => { wrapper.setProps({ currentDashboard: undefined }); wrapper.vm.$nextTick(() => { - expect(clipboardText()).not.toContain(`dashboard=`); - expect(clipboardText()).toContain(`y_label=`); + expect(getClipboardTextAt(0)).not.toContain(`dashboard=`); + expect(getClipboardTextAt(0)).toContain(`y_label=`); done(); }); }); @@ -579,18 +579,10 @@ describe('Dashboard', () => { wrapper.setProps({ currentDashboard: null }); wrapper.vm.$nextTick(() => { - expect(clipboardText()).not.toContain(`dashboard=`); - expect(clipboardText()).toContain(`y_label=`); + expect(getClipboardTextAt(0)).not.toContain(`dashboard=`); + expect(getClipboardTextAt(0)).toContain(`y_label=`); done(); }); }); - - it('creates a toast when clicked', () => { - jest.spyOn(wrapper.vm.$toast, 'show').and.stub(); - - link().vm.$emit('click'); - - expect(wrapper.vm.$toast.show).toHaveBeenCalled(); - }); }); }); diff --git a/spec/frontend/monitoring/panel_type_spec.js b/spec/frontend/monitoring/components/panel_type_spec.js index 730d67f79d8..6d8bd1d3a30 100644 --- a/spec/frontend/monitoring/panel_type_spec.js +++ b/spec/frontend/monitoring/components/panel_type_spec.js @@ -3,22 +3,29 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import { setTestTimeout } from 'helpers/timeout'; import invalidUrl from '~/lib/utils/invalid_url'; import axios from '~/lib/utils/axios_utils'; + import PanelType from '~/monitoring/components/panel_type.vue'; import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue'; import AnomalyChart from '~/monitoring/components/charts/anomaly.vue'; -import { graphDataPrometheusQueryRange } from '../../javascripts/monitoring/mock_data'; -import { anomalyMockGraphData } from '../../frontend/monitoring/mock_data'; +import { anomalyMockGraphData, graphDataPrometheusQueryRange } from 'jest/monitoring/mock_data'; import { createStore } from '~/monitoring/stores'; global.IS_EE = true; global.URL.createObjectURL = jest.fn(); +const mocks = { + $toast: { + show: jest.fn(), + }, +}; + describe('Panel Type component', () => { let axiosMock; let store; let state; let wrapper; + const exampleText = 'example_text'; const createWrapper = props => { @@ -27,6 +34,7 @@ describe('Panel Type component', () => { ...props, }, store, + mocks, }); }; @@ -88,7 +96,7 @@ describe('Panel Type component', () => { }); it('sets no clipboard copy link on dropdown by default', () => { - const link = () => wrapper.find('.js-chart-link'); + const link = () => wrapper.find({ ref: 'copyChartLink' }); expect(link().exists()).toBe(false); }); @@ -196,6 +204,7 @@ describe('Panel Type component', () => { }); describe('when cliboard data is available', () => { + const link = () => wrapper.find({ ref: 'copyChartLink' }); const clipboardText = 'A value to copy.'; beforeEach(() => { @@ -210,11 +219,19 @@ describe('Panel Type component', () => { }); it('sets clipboard text on the dropdown', () => { - const link = () => wrapper.find('.js-chart-link'); - expect(link().exists()).toBe(true); expect(link().element.dataset.clipboardText).toBe(clipboardText); }); + + it('adds a copy button to the dropdown', () => { + expect(link().text()).toContain('Generate link to chart'); + }); + + it('opens a toast on click', () => { + link().vm.$emit('click'); + + expect(wrapper.vm.$toast.show).toHaveBeenCalled(); + }); }); describe('when downloading metrics data as CSV', () => { diff --git a/spec/frontend/vue_shared/components/file_tree_spec.js b/spec/frontend/vue_shared/components/file_tree_spec.js new file mode 100644 index 00000000000..38979d9d844 --- /dev/null +++ b/spec/frontend/vue_shared/components/file_tree_spec.js @@ -0,0 +1,88 @@ +import { pick } from 'lodash'; +import { shallowMount } from '@vue/test-utils'; +import FileTree from '~/vue_shared/components/file_tree.vue'; + +const MockFileRow = { + name: 'MockFileRow', + render() { + return this.$slots.default; + }, +}; + +const TEST_LEVEL = 4; +const TEST_EXTA_ARGS = { + foo: 'lorem-ipsum', + bar: 'zoo', +}; + +describe('File Tree component', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(FileTree, { + propsData: { level: TEST_LEVEL, fileRowComponent: MockFileRow, ...props }, + attrs: { ...TEST_EXTA_ARGS }, + }); + }; + + const findFileRow = () => wrapper.find(MockFileRow); + const findChildrenTrees = () => wrapper.findAll(FileTree).wrappers.slice(1); + const findChildrenTreeProps = () => + findChildrenTrees().map(x => ({ + ...x.props(), + ...pick(x.attributes(), Object.keys(TEST_EXTA_ARGS)), + })); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('file row component', () => { + beforeEach(() => { + createComponent({ file: {} }); + }); + + it('renders file row component', () => { + expect(findFileRow().exists()).toEqual(true); + }); + + it('contains the required attribute keys', () => { + const fileRow = findFileRow(); + + // Checking strings b/c value in attributes are always strings + expect(fileRow.attributes()).toEqual({ + file: {}.toString(), + level: TEST_LEVEL.toString(), + ...TEST_EXTA_ARGS, + }); + }); + }); + + describe('file tree', () => { + const createChildren = () => [{ id: 1 }, { id: 2 }]; + const createChildrenExpectation = (props = {}) => + createChildren().map(file => ({ + fileRowComponent: MockFileRow, + file, + ...TEST_EXTA_ARGS, + ...props, + })); + + it.each` + key | value | desc | expectedChildren + ${'isHeader'} | ${true} | ${'is shown if file is header'} | ${createChildrenExpectation({ level: 0 })} + ${'opened'} | ${true} | ${'is shown if file is open'} | ${createChildrenExpectation({ level: TEST_LEVEL + 1 })} + ${'isHeader'} | ${false} | ${'is hidden if file is header'} | ${[]} + ${'opened'} | ${false} | ${'is hidden if file is open'} | ${[]} + `('$desc', ({ key, value, expectedChildren }) => { + createComponent({ + file: { + [key]: value, + tree: createChildren(), + }, + }); + + expect(findChildrenTreeProps()).toEqual(expectedChildren); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js index 2d80099fafe..071a09f8b93 100644 --- a/spec/javascripts/vue_shared/components/file_row_spec.js +++ b/spec/javascripts/vue_shared/components/file_row_spec.js @@ -19,7 +19,6 @@ describe('File row component', () => { const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown'); const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button'); - const findFileRow = () => vm.$el.querySelector('.file-row'); it('renders name', () => { createComponent({ @@ -42,7 +41,7 @@ describe('File row component', () => { }); spyOn(vm, '$emit').and.stub(); - vm.$el.querySelector('.file-row').click(); + vm.$el.click(); expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path); }); @@ -87,7 +86,7 @@ describe('File row component', () => { level: 0, }); - expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null); + expect(vm.$el.classList).toContain('js-file-row-header'); }); describe('new dropdown', () => { @@ -138,7 +137,7 @@ describe('File row component', () => { }); it('closes when row triggers mouseleave', () => { - findFileRow().dispatchEvent(new Event('mouseleave')); + vm.$el.dispatchEvent(new Event('mouseleave')); expect(vm.dropdownOpen).toBe(false); }); diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb new file mode 100644 index 00000000000..f90e1a1ebab --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +shared_examples 'resource mentions migration' do |migration_class, resource_class| + it 'migrates resource mentions' do + join = migration_class::JOIN + conditions = migration_class::QUERY_CONDITIONS + + expect do + subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id)) + end.to change { user_mentions.count }.by(1) + + user_mention = user_mentions.last + expect(user_mention.mentioned_users_ids.sort).to eq(mentioned_users.pluck(:id).sort) + expect(user_mention.mentioned_groups_ids.sort).to eq([group.id]) + expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id) + + # check that performing the same job twice does not fail and does not change counts + expect do + subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id)) + end.to change { user_mentions.count }.by(0) + end +end + +shared_examples 'resource notes mentions migration' do |migration_class, resource_class| + before do + note1.becomes(Note).save! + note2.becomes(Note).save! + note3.becomes(Note).save! + # note4.becomes(Note).save(validate: false) + end + + it 'migrates mentions from note' do + join = migration_class::JOIN + conditions = migration_class::QUERY_CONDITIONS + + # there are 4 notes for each noteable_type, but one does not have mentions and + # another one's noteable_id points to an inexistent resource + expect(notes.where(noteable_type: resource_class.to_s).count).to eq 4 + + expect do + subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id)) + end.to change { user_mentions.count }.by(2) + + # check that the user_mention for regular note is created + user_mention = user_mentions.first + expect(Note.find(user_mention.note_id).system).to be false + expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort) + expect(user_mention.mentioned_groups_ids.sort).to eq([group.id]) + expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id) + + # check that the user_mention for system note is created + user_mention = user_mentions.second + expect(Note.find(user_mention.note_id).system).to be true + expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort) + expect(user_mention.mentioned_groups_ids.sort).to eq([group.id]) + expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id) + + # check that performing the same job twice does not fail and does not change counts + expect do + subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id)) + end.to change { user_mentions.count }.by(0) + end +end + +shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes| + it 'schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + migration = described_class::MIGRATION + join = described_class::JOIN + conditions = described_class::QUERY_CONDITIONS + + expect(migration).to be_scheduled_delayed_migration(2.minutes, resource_class.name, join, conditions, is_for_notes, resource1.id, resource1.id) + expect(migration).to be_scheduled_delayed_migration(4.minutes, resource_class.name, join, conditions, is_for_notes, resource2.id, resource2.id) + expect(migration).to be_scheduled_delayed_migration(6.minutes, resource_class.name, join, conditions, is_for_notes, resource3.id, resource3.id) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end +end |