Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-24 03:08:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-24 03:08:43 +0300
commit082b24b03bbb9dca7edf1341aa7b0a51c9aeb18b (patch)
tree5a0b89d06053474c7565de01d9a2500713bda235
parent3a06969fe871598ead8b99ce42a1152f03d4ef91 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--CHANGELOG-EE.md4
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_header.vue5
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue (renamed from app/assets/javascripts/pipelines/components/blank_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue (renamed from app/assets/javascripts/pipelines/components/empty_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue (renamed from app/assets/javascripts/pipelines/components/nav_controls.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_triggerer.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_url.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue (renamed from app/assets/javascripts/pipelines/components/pipelines.vue)20
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_actions.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_artifacts.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_table.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_table_row.vue)12
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue (renamed from app/assets/javascripts/pipelines/components/stage.vue)14
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue (renamed from app/assets/javascripts/pipelines/components/time_ago.vue)6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue)2
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js (renamed from app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js)2
-rw-r--r--app/assets/stylesheets/pages/alert_management/details.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss1
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml5
-rw-r--r--changelogs/unreleased/system-notes-broken-css.yml5
-rw-r--r--db/migrate/20200522205606_create_group_deploy_keys_group.rb2
-rw-r--r--db/migrate/20200610130002_create_vulnerability_statistics.rb2
-rw-r--r--db/migrate/20200617150041_create_namespace_limits.rb2
-rw-r--r--db/migrate/20200622103836_create_snippet_statistics.rb2
-rw-r--r--db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb18
-rw-r--r--db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb18
-rw-r--r--db/migrate/20200623000320_remove_index_uploads_store_is_null.rb18
-rw-r--r--db/structure.sql9
-rw-r--r--doc/administration/gitaly/index.md9
-rw-r--r--doc/administration/high_availability/gitlab.md2
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/administration/operations/unicorn.md2
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/introduction/index.md2
-rw-r--r--doc/ci/jenkins/index.md19
-rw-r--r--doc/user/group/saml_sso/index.md7
-rw-r--r--doc/user/project/clusters/serverless/index.md6
-rw-r--r--doc/user/project/pages/getting_started/fork_sample_project.md55
-rw-r--r--doc/user/project/pages/getting_started/pages_ci_cd_template.md2
-rw-r--r--doc/user/project/pages/getting_started/pages_forked_sample_project.md56
-rw-r--r--doc/user/project/pages/getting_started/pages_from_scratch.md402
-rw-r--r--doc/user/project/pages/getting_started_part_four.md401
-rw-r--r--doc/user/project/pages/index.md6
-rw-r--r--doc/user/project/pages/introduction.md2
-rw-r--r--lib/gitlab/database/dynamic_model_helpers.rb16
-rw-r--r--lib/gitlab/database/migration_helpers.rb136
-rw-r--r--lib/gitlab/database/migrations/background_migration_helpers.rb144
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb99
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb2
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb62
-rw-r--r--lib/gitlab/database/schema_helpers.rb19
-rw-r--r--qa/qa/page/project/pipeline/index.rb4
-rw-r--r--spec/frontend/fixtures/metrics_dashboard.rb12
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap28
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js11
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js12
-rw-r--r--spec/frontend/monitoring/components/dashboard_template_spec.js4
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js5
-rw-r--r--spec/frontend/monitoring/fixture_data.js16
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js895
-rw-r--r--spec/frontend/pipelines/blank_state_spec.js2
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js2
-rw-r--r--spec/frontend/pipelines/nav_controls_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_table_row_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js2
-rw-r--r--spec/frontend/pipelines/stage_spec.js2
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js6
-rw-r--r--spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js6
-rw-r--r--spec/lib/gitlab/database/dynamic_model_helpers_spec.rb28
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb236
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb245
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb164
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb52
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb6
98 files changed, 1941 insertions, 1461 deletions
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md
index 1fe9715d332..d7bdd181997 100644
--- a/CHANGELOG-EE.md
+++ b/CHANGELOG-EE.md
@@ -1,5 +1,9 @@
Please view this file on the master branch, on stable branches it's out of date.
+## 13.1.1 (2020-06-23)
+
+- No changes.
+
## 13.0.6 (2020-06-10)
### Security (1 change)
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 6958a5d2526..66eb11f0009 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -7,7 +7,7 @@ import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Tabs from '../../../vue_shared/components/tabs/tabs';
import Tab from '../../../vue_shared/components/tabs/tab.vue';
-import EmptyState from '../../../pipelines/components/empty_state.vue';
+import EmptyState from '../../../pipelines/components/pipelines_list/empty_state.vue';
import JobsList from '../jobs/list.vue';
import IDEServices from '~/ide/services';
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index 4e9f00c6d6e..16a21ae0d3c 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -353,10 +353,7 @@ export default {
</gl-deprecated-button>
</div>
- <div
- v-if="externalDashboardUrl && externalDashboardUrl.length"
- class="mb-2 mr-2 d-flex d-sm-block"
- >
+ <div v-if="externalDashboardUrl.length" class="mb-2 mr-2 d-flex d-sm-block">
<gl-deprecated-button
class="flex-grow-1 js-external-dashboard-link"
variant="primary"
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index 2c37d7da4a7..0bae2984c45 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -8,7 +8,7 @@ import {
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
-import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
+import pipelinesComponent from '../../../../pipelines/components/pipelines_list/pipelines.vue';
import Translate from '../../../../vue_shared/translate';
Vue.use(Translate);
diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
index 6c3a4a27606..6c3a4a27606 100644
--- a/app/assets/javascripts/pipelines/components/blank_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 74ada6a4d15..74ada6a4d15 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
index 4f6c9d2bd90..a66bbb7e5ba 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
@@ -1,6 +1,6 @@
<script>
import { GlDeprecatedButton } from '@gitlab/ui';
-import LoadingButton from '../../vue_shared/components/loading_button.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
export default {
name: 'PipelineNavControls',
diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue
index f604edd8859..f604edd8859 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue
diff --git a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
index 740b54cd8e0..740b54cd8e0 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index 6c977b841af..6c977b841af 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index dbf29b0c29c..54f7240140e 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -1,17 +1,17 @@
<script>
import { isEqual } from 'lodash';
-import { __, sprintf, s__ } from '../../locale';
-import createFlash from '../../flash';
-import PipelinesService from '../services/pipelines_service';
-import pipelinesMixin from '../mixins/pipelines';
-import TablePagination from '../../vue_shared/components/pagination/table_pagination.vue';
-import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
+import { __, sprintf, s__ } from '~/locale';
+import createFlash from '~/flash';
+import PipelinesService from '../../services/pipelines_service';
+import pipelinesMixin from '../../mixins/pipelines';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue';
-import { getParameterByName } from '../../lib/utils/common_utils';
-import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin';
import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
-import { validateParams } from '../utils';
-import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../constants';
+import { validateParams } from '../../utils';
+import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
index 7d4276e8d2e..3009ca7a775 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
@@ -5,7 +5,7 @@ import flash from '~/flash';
import { s__, __, sprintf } from '~/locale';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import Icon from '~/vue_shared/components/icon.vue';
-import eventHub from '../event_hub';
+import eventHub from '../../event_hub';
export default {
directives: {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
index 59c066b2683..59c066b2683 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
index 0505a8668d1..0505a8668d1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index d3ba0c97f6b..e29219f8f88 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -2,7 +2,7 @@
import { GlTooltipDirective } from '@gitlab/ui';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
import PipelineStopModal from './pipeline_stop_modal.vue';
-import eventHub from '../event_hub';
+import eventHub from '../../event_hub';
/**
* Pipelines Table Component.
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
index 981914dd046..733ebbf81ec 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
@@ -1,16 +1,16 @@
<script>
-import eventHub from '../event_hub';
+import eventHub from '../../event_hub';
import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
-import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelineTriggerer from './pipeline_triggerer.vue';
import PipelinesTimeago from './time_ago.vue';
-import CommitComponent from '../../vue_shared/components/commit.vue';
-import LoadingButton from '../../vue_shared/components/loading_button.vue';
-import Icon from '../../vue_shared/components/icon.vue';
-import { PIPELINES_TABLE } from '../constants';
+import CommitComponent from '~/vue_shared/components/commit.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import { PIPELINES_TABLE } from '../../constants';
/**
* Pipeline table row.
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
index 569920a4f31..99492bd8357 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
@@ -14,13 +14,13 @@
import $ from 'jquery';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
-import { __ } from '../../locale';
-import Flash from '../../flash';
-import axios from '../../lib/utils/axios_utils';
-import eventHub from '../event_hub';
-import Icon from '../../vue_shared/components/icon.vue';
-import JobItem from './graph/job_item.vue';
-import { PIPELINES_TABLE } from '../constants';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import eventHub from '../../event_hub';
+import Icon from '~/vue_shared/components/icon.vue';
+import JobItem from '../graph/job_item.vue';
+import { PIPELINES_TABLE } from '../../constants';
export default {
components: {
diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index 2a23a0f6744..8a01e1fe3f5 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -1,8 +1,8 @@
<script>
import iconTimerSvg from 'icons/_icon_timer.svg';
-import '../../lib/utils/datetime_utility';
-import tooltip from '../../vue_shared/directives/tooltip';
-import timeagoMixin from '../../vue_shared/mixins/timeago';
+import '~/lib/utils/datetime_utility';
+import tooltip from '~/vue_shared/directives/tooltip';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
directives: {
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue
index da14bb2d308..b6eff2931d3 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue
@@ -1,7 +1,7 @@
<script>
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
-import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
+import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants';
import createFlash from '~/flash';
import { debounce } from 'lodash';
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue
index dc43d94f4fd..dc43d94f4fd 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue
index 7b209c5fa12..64de6d2a053 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue
@@ -1,7 +1,7 @@
<script>
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
-import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
+import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants';
import createFlash from '~/flash';
import { debounce } from 'lodash';
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
index 4062a3b11bb..b5aeb3fe9e0 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
@@ -13,7 +13,7 @@ import {
ANY_TRIGGER_AUTHOR,
FETCH_AUTHOR_ERROR_MESSAGE,
FILTER_PIPELINES_SEARCH_DELAY,
-} from '../../constants';
+} from '../../../constants';
export default {
anyTriggerAuthor: ANY_TRIGGER_AUTHOR,
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 876b30299fb..7710a96e5fb 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -1,11 +1,11 @@
import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
-import { __ } from '../../locale';
-import createFlash from '../../flash';
-import Poll from '../../lib/utils/poll';
-import EmptyState from '../components/empty_state.vue';
-import SvgBlankState from '../components/blank_state.vue';
-import PipelinesTableComponent from '../components/pipelines_table.vue';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import Poll from '~/lib/utils/poll';
+import EmptyState from '../components/pipelines_list/empty_state.vue';
+import SvgBlankState from '../components/pipelines_list/blank_state.vue';
+import PipelinesTableComponent from '../components/pipelines_list/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 6df53311ef0..9c821be7490 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -3,7 +3,7 @@
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
import { sprintf, s__ } from '~/locale';
-import PipelineStage from '~/pipelines/components/stage.vue';
+import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
index 02ed77937b7..1eb24c1d98f 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/constants.js
@@ -1,5 +1,5 @@
import { __ } from '~/locale';
-import { generateToolbarItem } from './editor_service';
+import { generateToolbarItem } from './services/editor_service';
import buildCustomHTMLRenderer from './services/build_custom_renderer';
export const CUSTOM_EVENTS = {
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
index 5c310fc059b..76d7683e889 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
@@ -16,7 +16,7 @@ import {
removeCustomEventListener,
addImage,
getMarkdown,
-} from './editor_service';
+} from './services/editor_service';
export default {
components: {
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
index 278cd50a947..40054ae1b77 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import ToolbarItem from './toolbar_item.vue';
+import ToolbarItem from '../toolbar_item.vue';
const buildWrapper = propsData => {
const instance = new Vue({
diff --git a/app/assets/stylesheets/pages/alert_management/details.scss b/app/assets/stylesheets/pages/alert_management/details.scss
index 591a26e5941..6c1370dbb5c 100644
--- a/app/assets/stylesheets/pages/alert_management/details.scss
+++ b/app/assets/stylesheets/pages/alert_management/details.scss
@@ -68,6 +68,6 @@
}
.note-header-info {
- margin-top: 1px;
+ @include gl-mt-1;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index e8cdfd717c0..c711c69ac66 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -10,6 +10,7 @@ $note-form-margin-left: 72px;
top: 0;
bottom: 0;
left: $left;
+ height: calc(100% - 20px);
}
}
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 8aba9356949..477500248fd 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -117,7 +117,7 @@ module Ci
after_save :update_file_store, if: :saved_change_to_file?
scope :not_expired, -> { where('expire_at IS NULL OR expire_at > ?', Time.current) }
- scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
+ scope :with_files_stored_locally, -> { where(file_store: ::JobArtifactUploader::Store::LOCAL) }
scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) }
scope :for_sha, ->(sha, project_id) { joins(job: :pipeline).where(ci_pipelines: { sha: sha, project_id: project_id }) }
scope :for_ref, ->(ref, project_id) { joins(job: :pipeline).where(ci_pipelines: { ref: ref, project_id: project_id }) }
diff --git a/changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml b/changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml
new file mode 100644
index 00000000000..0386b89b974
--- /dev/null
+++ b/changelogs/unreleased/223788-improve-query-to-retrieve-job-artifacts-with-files-stored-locally.yml
@@ -0,0 +1,5 @@
+---
+title: Improve query to retrieve job artifacts with files stored locally
+merge_request: 35084
+author:
+type: performance
diff --git a/changelogs/unreleased/system-notes-broken-css.yml b/changelogs/unreleased/system-notes-broken-css.yml
new file mode 100644
index 00000000000..082fc324030
--- /dev/null
+++ b/changelogs/unreleased/system-notes-broken-css.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken CSS for system notes
+merge_request: 34870
+author:
+type: other
diff --git a/db/migrate/20200522205606_create_group_deploy_keys_group.rb b/db/migrate/20200522205606_create_group_deploy_keys_group.rb
index acc72634c4c..85dbc14b0f5 100644
--- a/db/migrate/20200522205606_create_group_deploy_keys_group.rb
+++ b/db/migrate/20200522205606_create_group_deploy_keys_group.rb
@@ -19,9 +19,7 @@ class CreateGroupDeployKeysGroup < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- # rubocop:disable Migration/DropTable
drop_table :group_deploy_keys_groups
- # rubocop:enable Migration/DropTable
end
end
end
diff --git a/db/migrate/20200610130002_create_vulnerability_statistics.rb b/db/migrate/20200610130002_create_vulnerability_statistics.rb
index add9051898b..77fd116230e 100644
--- a/db/migrate/20200610130002_create_vulnerability_statistics.rb
+++ b/db/migrate/20200610130002_create_vulnerability_statistics.rb
@@ -24,7 +24,7 @@ class CreateVulnerabilityStatistics < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- drop_table :vulnerability_statistics # rubocop:disable Migration/DropTable
+ drop_table :vulnerability_statistics
end
end
end
diff --git a/db/migrate/20200617150041_create_namespace_limits.rb b/db/migrate/20200617150041_create_namespace_limits.rb
index a3bc1cce037..59a014ff7ca 100644
--- a/db/migrate/20200617150041_create_namespace_limits.rb
+++ b/db/migrate/20200617150041_create_namespace_limits.rb
@@ -17,6 +17,6 @@ class CreateNamespaceLimits < ActiveRecord::Migration[6.0]
end
def down
- drop_table :namespace_limits # rubocop:disable Migration/DropTable
+ drop_table :namespace_limits
end
end
diff --git a/db/migrate/20200622103836_create_snippet_statistics.rb b/db/migrate/20200622103836_create_snippet_statistics.rb
index cf3480321f0..691a9acdc04 100644
--- a/db/migrate/20200622103836_create_snippet_statistics.rb
+++ b/db/migrate/20200622103836_create_snippet_statistics.rb
@@ -18,7 +18,7 @@ class CreateSnippetStatistics < ActiveRecord::Migration[6.0]
def down
with_lock_retries do
- drop_table :snippet_statistics # rubocop:disable Migration/DropTable
+ drop_table :snippet_statistics
end
end
end
diff --git a/db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb b/db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb
new file mode 100644
index 00000000000..e293bcfa1ce
--- /dev/null
+++ b/db/migrate/20200622235737_remove_index_ci_job_artifacts_file_store_is_null.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexCiJobArtifactsFileStoreIsNull < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_ci_job_artifacts_file_store_is_null'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:ci_job_artifacts, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:ci_job_artifacts, :id, where: "file_store IS NULL", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb b/db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb
new file mode 100644
index 00000000000..76faa5c4cd2
--- /dev/null
+++ b/db/migrate/20200623000148_remove_index_lfs_objects_file_store_is_null.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexLfsObjectsFileStoreIsNull < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_lfs_objects_file_store_is_null'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:lfs_objects, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:lfs_objects, :id, where: "file_store IS NULL", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200623000320_remove_index_uploads_store_is_null.rb b/db/migrate/20200623000320_remove_index_uploads_store_is_null.rb
new file mode 100644
index 00000000000..ad84cd5a649
--- /dev/null
+++ b/db/migrate/20200623000320_remove_index_uploads_store_is_null.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexUploadsStoreIsNull < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_uploads_store_is_null'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:uploads, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:uploads, :id, where: "store IS NULL", name: INDEX_NAME)
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 5eb6be6e0ec..e3c57c15fe6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9559,8 +9559,6 @@ CREATE UNIQUE INDEX index_ci_group_variables_on_group_id_and_key ON public.ci_gr
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON public.ci_instance_variables USING btree (key);
-CREATE INDEX index_ci_job_artifacts_file_store_is_null ON public.ci_job_artifacts USING btree (id) WHERE (file_store IS NULL);
-
CREATE INDEX index_ci_job_artifacts_for_terraform_reports ON public.ci_job_artifacts USING btree (project_id, id) WHERE (file_type = 18);
CREATE INDEX index_ci_job_artifacts_on_expire_at_and_job_id ON public.ci_job_artifacts USING btree (expire_at, job_id);
@@ -10225,8 +10223,6 @@ CREATE UNIQUE INDEX index_lfs_file_locks_on_project_id_and_path ON public.lfs_fi
CREATE INDEX index_lfs_file_locks_on_user_id ON public.lfs_file_locks USING btree (user_id);
-CREATE INDEX index_lfs_objects_file_store_is_null ON public.lfs_objects USING btree (id) WHERE (file_store IS NULL);
-
CREATE INDEX index_lfs_objects_on_file_store ON public.lfs_objects USING btree (file_store);
CREATE UNIQUE INDEX index_lfs_objects_on_oid ON public.lfs_objects USING btree (oid);
@@ -11093,8 +11089,6 @@ CREATE INDEX index_uploads_on_store ON public.uploads USING btree (store);
CREATE INDEX index_uploads_on_uploader_and_path ON public.uploads USING btree (uploader, path);
-CREATE INDEX index_uploads_store_is_null ON public.uploads USING btree (id) WHERE (store IS NULL);
-
CREATE INDEX index_user_agent_details_on_subject_id_and_subject_type ON public.user_agent_details USING btree (subject_id, subject_type);
CREATE INDEX index_user_callouts_on_user_id ON public.user_callouts USING btree (user_id);
@@ -14120,5 +14114,8 @@ COPY "schema_migrations" (version) FROM STDIN;
20200618134223
20200618134723
20200622103836
+20200622235737
+20200623000148
+20200623000320
\.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 9113f417104..4603dfea15b 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -19,12 +19,13 @@ In the Gitaly documentation:
- [GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell).
- [GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).
-GitLab end users do not have direct access to Gitaly.
+GitLab end users do not have direct access to Gitaly. Gitaly only manages Git
+repository access for GitLab. Other types of GitLab data aren't accessed using Gitaly.
CAUTION: **Caution:**
-From GitLab 13.0, using NFS for Git repositories is deprecated. In GitLab 14.0,
-support for NFS for Git repositories is scheduled to be removed. Upgrade to
-[Gitaly Cluster](praefect.md) as soon as possible.
+From GitLab 13.0, Gitaly support for NFS is deprecated. In GitLab 14.0, Gitaly support
+for NFS is scheduled to be removed. Upgrade to [Gitaly Cluster](praefect.md) as soon as
+possible.
## Architecture
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 67a84f99bea..2c0b8d4125d 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -126,7 +126,7 @@ need some extra configuration.
from running on upgrade. Only the primary GitLab application server should
handle migrations.
-1. **Recommended** Configure host keys. Copy the contents (primary and public keys) of `/etc/ssh/` on
+1. **Recommended** Configure host keys. Copy the contents (private and public keys) of `/etc/ssh/` on
the primary application server to `/etc/ssh` on all secondary servers. This
prevents false man-in-the-middle-attack alerts when accessing servers in your
High Availability cluster behind a load balancer.
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 7d7053a26db..0ec6ea7986e 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -794,7 +794,7 @@ Omnibus GitLab packages or in `/home/git/gitlab/log/service_measurement.log` for
installations from source.
It contain only a single structured log with measurements for each service execution.
-It will contain measurement such as: number of SQL calls, `execution_time`, `gc_stats`, memory usage, etc...
+It will contain measurement such as: number of SQL calls, `execution_time`, `gc_stats`, `memory_usage`, etc...
For example:
diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md
index eabf99eb08c..a1593c3a6c3 100644
--- a/doc/administration/operations/unicorn.md
+++ b/doc/administration/operations/unicorn.md
@@ -45,7 +45,7 @@ master process has PID 56227 below.
The main tunable options for Unicorn are the number of worker processes and the
request timeout after which the Unicorn master terminates a worker process.
See the [Omnibus GitLab Unicorn settings
-documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md)
+documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.html)
if you want to adjust these settings.
## unicorn-worker-killer
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 150f160b762..dad9978ba06 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -58,7 +58,7 @@ the following documents:
- [How GitLab CI/CD works](introduction/index.md#how-gitlab-cicd-works).
- [Fundamental pipeline architectures](pipelines/pipeline_architectures.md).
- [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow).
-- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md).
+- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started/pages_from_scratch.md).
If you're migrating from another CI/CD tool, check out our handy references:
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index 7ba24f9c861..6d17778bf0d 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -226,7 +226,7 @@ To get started with GitLab CI/CD, you need to familiarize yourself
with the [`.gitlab-ci.yml`](../yaml/README.md) configuration file
syntax and with its attributes.
-This document [introduces the concepts of GitLab CI/CD in the scope of GitLab Pages](../../user/project/pages/getting_started_part_four.md), for deploying static websites.
+This document [introduces the concepts of GitLab CI/CD in the scope of GitLab Pages](../../user/project/pages/getting_started/pages_from_scratch.md), for deploying static websites.
Although it's meant for users who want to write their own Pages
script from scratch, it also serves as an introduction to the setup process for GitLab CI/CD.
It covers the very first general steps of writing a CI/CD configuration
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
index 37e0522e74d..aa16742e634 100644
--- a/doc/ci/jenkins/index.md
+++ b/doc/ci/jenkins/index.md
@@ -12,16 +12,25 @@ A lot of GitLab users have successfully migrated to GitLab CI/CD from Jenkins. T
easier if you're just getting started, we've collected several resources here that you might find useful
before diving in. Think of this page as a "GitLab CI/CD for Jenkins Users" guide.
-First of all, our [Quick Start Guide](../quick_start/README.md) contains a good overview of how GitLab CI/CD works.
-You may also be interested in [Auto DevOps](../../topics/autodevops/index.md) which can potentially be used to build, test,
-and deploy your applications with little to no configuration needed at all.
+The following list of recommended steps was created after observing organizations
+that were able to quickly complete this migration:
+
+1. Start by reading the GitLab CI/CD [Quick Start Guide](../quick_start/README.md) and [important product differences](#important-product-differences).
+1. Learn the importance of [managing the organizational transition](#managing-the-organizational-transition).
+1. [Add Runners](../runners/README.md) to your GitLab instance.
+1. Educate and enable your developers to independently perform the following steps in their projects:
+ 1. Review the [Quick Start Guide](../quick_start/README.md) and [Pipeline Configuration Reference](../yaml/README.md).
+ 1. Use the [Jenkins Wrapper](#jenkinsfile-wrapper) to temporarily maintain fragile Jenkins jobs.
+ 1. Migrate the build and CI jobs and configure them to show results directly in your merge requests. They can use [Auto DevOps](../../topics/autodevops/index.md) as a starting point, and [customize](../../topics/autodevops/customize.md) or [decompose](../../topics/autodevops/customize.md#using-components-of-auto-devops) the configuration as needed.
+ 1. Add [Review Apps](../review_apps/index.md).
+ 1. Migrate the deployment jobs using [cloud deployment templates](../cloud_deployment/index.md), adding [environments](../environments/index.md), and [deploy boards](../..//user/project/deploy_boards.md).
+ 1. Work to unwrap any jobs still running with the use of the Jenkins wrapper.
+1. Take stock of any common CI/CD job definitions then create and share [templates](#templates) for them.
For an example of how to convert a Jenkins pipeline into a GitLab CI/CD pipeline,
or how to use Auto DevOps to test your code automatically, watch the
[Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y) video.
-For advanced CI/CD teams, [templates](#templates) can enable the reuse of pipeline configurations.
-
Otherwise, read on for important information that will help you get the ball rolling. Welcome
to GitLab!
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 81684038dc2..f2c5ce76d92 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -70,7 +70,12 @@ When SSO enforcement is enabled for a group, users cannot share a project in the
#### Group-managed accounts
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/709) in GitLab 12.1.
+CAUTION: **Warning:**
+This is a [Closed Beta](https://about.gitlab.com/handbook/product/#closed-beta) feature.
+
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/709) in GitLab 12.1.
+> - It's deployed behind a feature flag, disabled by default.
+> - It's disabled on GitLab.com. If you want to enable it for your subscription, contact Product Management through [GitLab Support](https://support.gitlab.com).
When SSO is being enforced, groups can enable an additional level of protection by enforcing the creation of dedicated user accounts to access the group.
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 8f7a0022ff4..d00f05fca66 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -570,7 +570,7 @@ The simplest way to accomplish this is to
use [Certbot to manually obtain Let's Encrypt certificates](https://knative.dev/docs/serving/using-a-tls-cert/#using-certbot-to-manually-obtain-let-s-encrypt-certificates). Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated websites to enable HTTPS.
NOTE: **Note:**
-The instructions below relate to installing and running Certbot on a Linux server and may not work on other operating systems.
+The instructions below relate to installing and running Certbot on a Linux server that has Python 3 installed and may not work on other operating systems or with other versions of Python.
1. Install Certbot by running the
[`certbot-auto` wrapper script](https://certbot.eff.org/docs/install.html#certbot-auto).
@@ -580,7 +580,7 @@ The instructions below relate to installing and running Certbot on a Linux serve
wget https://dl.eff.org/certbot-auto
sudo mv certbot-auto /usr/local/bin/certbot-auto
sudo chown root /usr/local/bin/certbot-auto
- chmod 0755 /usr/local/bin/certbot-auto
+ sudo chmod 0755 /usr/local/bin/certbot-auto
/usr/local/bin/certbot-auto --help
```
@@ -609,7 +609,7 @@ The instructions below relate to installing and running Certbot on a Linux serve
using DNS challenge during authorization:
```shell
- ./certbot-auto certonly --manual --preferred-challenges dns -d '*.<namespace>.example.com'
+ /usr/local/bin/certbot-auto certonly --manual --preferred-challenges dns -d '*.<namespace>.example.com'
```
Where `<namespace>` is the namespace created by GitLab for your serverless project (composed of `<project_name>-<project_id>-<environment>`) and
diff --git a/doc/user/project/pages/getting_started/fork_sample_project.md b/doc/user/project/pages/getting_started/fork_sample_project.md
index de9bd97b262..250e90f0302 100644
--- a/doc/user/project/pages/getting_started/fork_sample_project.md
+++ b/doc/user/project/pages/getting_started/fork_sample_project.md
@@ -1,56 +1,5 @@
---
-type: reference, howto
-stage: Release
-group: Release Management
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: 'pages_forked_sample_project.md'
---
-# Create a Pages website from a forked sample
-
-GitLab provides [sample projects for the most popular Static Site Generators](https://gitlab.com/pages).
-You can fork one of the sample projects and run the CI/CD pipeline to generate a Pages website.
-
-Fork a sample project when you want to test GitLab Pages or start a new project that's already
-configured to generate a Pages site.
-
-<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) of how this works.
-
-To fork a sample project and create a Pages website:
-
-1. View the sample projects by going to the [GitLab Pages examples](https://gitlab.com/pages) group.
-1. Click the name of the project you want to [fork](../../../../gitlab-basics/fork-project.md).
-1. In the top right, click the **Fork** button, and then choose a namespace to fork to.
-1. Go to your project's **CI/CD > Pipelines** and click **Run pipeline**.
- GitLab CI/CD builds and deploys your site.
-
-The site can take approximately 30 minutes to deploy.
-When the pipeline is finished, go to **Settings > Pages** to find the link to your website from your project.
-
-For every change pushed to your repository, GitLab CI/CD runs a new pipeline
-that immediately publishes your changes to the Pages site.
-
-You can take some **optional** further steps:
-
-- _Remove the fork relationship._ If you want to contribute to the project you forked from,
- you can keep this relationship. Otherwise, go to your project's **Settings > General**,
- expand **Advanced settings**, and scroll down to **Remove fork relationship**:
-
- ![Remove fork relationship](../img/remove_fork_relationship_v13_1.png)
-
-- _Change the URL to match your namespace._ If your Pages site is hosted on GitLab.com,
- you can rename it to `<namespace>.gitlab.io`, where `<namespace>` is your GitLab namespace
- (the one you chose when you forked the project).
-
- - Go to your project's **Settings > General** and expand **Advanced**. Scroll down to
- **Change path** and change the path to `<namespace>.gitlab.io`.
-
- For example, if your project's URL is `gitlab.com/gitlab-tests/jekyll`, your namespace is
- `gitlab-tests`.
-
- If you set the repository path to `gitlab-tests.gitlab.io`,
- the resulting URL for your Pages website is `https://gitlab-tests.gitlab.io`.
-
- ![Change repo's path](../img/change_path_v12_10.png)
-
- - Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-baseurls)
- from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the config file.
+This document was moved to [pages_forked_sample_project.md](pages_forked_sample_project.md).
diff --git a/doc/user/project/pages/getting_started/pages_ci_cd_template.md b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
index 5d7126ab22e..906ffe43285 100644
--- a/doc/user/project/pages/getting_started/pages_ci_cd_template.md
+++ b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
@@ -35,7 +35,7 @@ configuration for the Pages site to generate properly.
[GitLab Pages group of sample projects](https://gitlab.com/pages).
These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
You can also [learn how to write your own `.gitlab-ci.yml`
- file for GitLab Pages](../getting_started_part_four.md).
+ file for GitLab Pages](pages_from_scratch.md).
1. Save and commit the `.gitlab-ci.yml` file.
diff --git a/doc/user/project/pages/getting_started/pages_forked_sample_project.md b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
new file mode 100644
index 00000000000..de9bd97b262
--- /dev/null
+++ b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
@@ -0,0 +1,56 @@
+---
+type: reference, howto
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Create a Pages website from a forked sample
+
+GitLab provides [sample projects for the most popular Static Site Generators](https://gitlab.com/pages).
+You can fork one of the sample projects and run the CI/CD pipeline to generate a Pages website.
+
+Fork a sample project when you want to test GitLab Pages or start a new project that's already
+configured to generate a Pages site.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) of how this works.
+
+To fork a sample project and create a Pages website:
+
+1. View the sample projects by going to the [GitLab Pages examples](https://gitlab.com/pages) group.
+1. Click the name of the project you want to [fork](../../../../gitlab-basics/fork-project.md).
+1. In the top right, click the **Fork** button, and then choose a namespace to fork to.
+1. Go to your project's **CI/CD > Pipelines** and click **Run pipeline**.
+ GitLab CI/CD builds and deploys your site.
+
+The site can take approximately 30 minutes to deploy.
+When the pipeline is finished, go to **Settings > Pages** to find the link to your website from your project.
+
+For every change pushed to your repository, GitLab CI/CD runs a new pipeline
+that immediately publishes your changes to the Pages site.
+
+You can take some **optional** further steps:
+
+- _Remove the fork relationship._ If you want to contribute to the project you forked from,
+ you can keep this relationship. Otherwise, go to your project's **Settings > General**,
+ expand **Advanced settings**, and scroll down to **Remove fork relationship**:
+
+ ![Remove fork relationship](../img/remove_fork_relationship_v13_1.png)
+
+- _Change the URL to match your namespace._ If your Pages site is hosted on GitLab.com,
+ you can rename it to `<namespace>.gitlab.io`, where `<namespace>` is your GitLab namespace
+ (the one you chose when you forked the project).
+
+ - Go to your project's **Settings > General** and expand **Advanced**. Scroll down to
+ **Change path** and change the path to `<namespace>.gitlab.io`.
+
+ For example, if your project's URL is `gitlab.com/gitlab-tests/jekyll`, your namespace is
+ `gitlab-tests`.
+
+ If you set the repository path to `gitlab-tests.gitlab.io`,
+ the resulting URL for your Pages website is `https://gitlab-tests.gitlab.io`.
+
+ ![Change repo's path](../img/change_path_v12_10.png)
+
+ - Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-baseurls)
+ from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the config file.
diff --git a/doc/user/project/pages/getting_started/pages_from_scratch.md b/doc/user/project/pages/getting_started/pages_from_scratch.md
new file mode 100644
index 00000000000..23d22e9fa66
--- /dev/null
+++ b/doc/user/project/pages/getting_started/pages_from_scratch.md
@@ -0,0 +1,402 @@
+---
+last_updated: 2020-01-06
+type: reference, howto
+stage: Release
+group: Release Management
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Create a GitLab Pages website from scratch
+
+This tutorial shows you how to create a Pages site from scratch. You will start with
+a blank project and create your own CI file, which gives instruction to the
+[GitLab Runner](https://docs.gitlab.com/runner/). When your CI/CD
+[pipeline](../../../../ci/pipelines/index.md) runs, the Pages site is created.
+
+This example uses the [Jekyll](https://jekyllrb.com/) Static Site Generator (SSG).
+Other SSGs follow similar steps. You do not need to be familiar with Jekyll or SSGs
+to complete this tutorial.
+
+## Prerequisites
+
+To follow along with this example, start with a blank project in GitLab.
+Create three files in the root (top-level) directory.
+
+- `.gitlab-ci.yml` A YAML file that contains the commands you want to run.
+ For now, leave the file's contents blank.
+
+- `index.html` An HTML file you can populate with whatever HTML content you'd like,
+ for example:
+
+ ```html
+ <html>
+ <head>
+ <title>Home</title>
+ </head>
+ <body>
+ <h1>Hello World!</h1>
+ </body>
+ </html>
+ ```
+
+- [`Gemfile`](https://bundler.io/gemfile.html) A file that describes dependencies for Ruby programs.
+ Populate it with this content:
+
+ ```ruby
+ source "https://rubygems.org"
+
+ gem "jekyll"
+ ```
+
+## Choose a Docker image
+
+In this example, the Runner uses a [Docker image](../../../../ci/docker/using_docker_images.md)
+to run scripts and deploy the site.
+
+This specific Ruby image is maintained on [DockerHub](https://hub.docker.com/_/ruby).
+
+Edit your `.gitlab-ci.yml` and add this text as the first line.
+
+```yaml
+image: ruby:2.7
+```
+
+If your SSG needs [NodeJS](https://nodejs.org/) to build, you must specify an
+image that contains NodeJS as part of its file system. For example, for a
+[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:12.17.0`.
+
+## Install Jekyll
+
+To run [Jekyll](https://jekyllrb.com/) locally, you would open your terminal and:
+
+- Install [Bundler](https://bundler.io/) by running `gem install bundler`.
+- Create `Gemfile.lock` by running `bundle install`.
+- Install Jekyll by running `bundle exec jekyll build`.
+
+In the `.gitlab-ci.yml` file, this is written as:
+
+```yaml
+script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
+```
+
+In addition, in the `.gitlab-ci.yml` file, each `script` is organized by a `job`.
+A `job` includes the scripts and settings you want to apply to that specific
+task.
+
+```yaml
+job:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
+```
+
+For GitLab Pages, this `job` has a specific name, called `pages`.
+This setting tells the Runner you want the job to deploy your website
+with GitLab Pages:
+
+```yaml
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
+```
+
+## Specify the `public` directory for output
+
+Jekyll needs to know where to generate its output.
+GitLab Pages only considers files in a directory called `public`.
+
+Jekyll uses destination (`-d`) to specify an output directory for the built website:
+
+```yaml
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+```
+
+## Specify the `public` directory for artifacts
+
+Now that Jekyll has output the files to the `public` directory,
+the Runner needs to know where to get them. The artifacts are stored
+in the `public` directory:
+
+```yaml
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+Paste this into `.gitlab-ci.yml` file, so it now looks like this:
+
+```yaml
+image: ruby:2.7
+
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+Now save and commit the `.gitlab-ci.yml` file. You can watch the pipeline run
+by going to **CI / CD > Pipelines**.
+
+When it succeeds, go to **Settings > Pages** to view the URL where your site
+is now available.
+
+If you want to do more advanced tasks, you can update your `.gitlab-ci.yml` file
+with [any of the available settings](../../../../ci/yaml/README.md). You can check
+your CI syntax with the [GitLab CI/CD Lint Tool](https://gitlab.com/ci/lint).
+
+The following topics show other examples of other options you can add to your CI/CD file.
+
+## Deploy specific branches to a Pages site
+
+You may want to deploy to a Pages site only from specific branches.
+
+First, add a `workflow` section to force the pipeline to run only when changes are
+pushed to branches:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+Then configure the pipeline to run the job for the master branch only.
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+```
+
+## Specify a stage to deploy
+
+There are three default stages for GitLab CI/CD: build, test,
+and deploy.
+
+If you want to test your script and check the built site before deploying
+to production, you can run the test exactly as it will run when you
+push to `master`.
+
+To specify a stage for your job to run in,
+add a `stage` line to your CI file:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ stage: deploy
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+```
+
+Now add another job to the CI file, telling it to
+test every push to every branch **except** the `master` branch:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+pages:
+ stage: deploy
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+
+test:
+ stage: test
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ rules:
+ - if: '$CI_COMMIT_BRANCH != "master"'
+```
+
+When the `test` job runs in the `test` stage, Jekyll
+builds the site in a directory called `test`. The job affects
+all branches except `master`.
+
+When you apply stages to different jobs, every job in the same
+stage builds in parallel. If your web application needs more than
+one test before being deployed, you can run all your tests at the
+same time.
+
+## Remove duplicate commands
+
+To avoid duplicating the same scripts in every job, you can add them
+to a `before_script` section.
+
+In the example, `gem install bundler` and `bundle install` were running
+for both jobs, `pages` and `test`.
+
+Move these commands to a `before_script` section:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+before_script:
+ - gem install bundler
+ - bundle install
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ rules:
+ - if: '$CI_COMMIT_BRANCH != "master"'
+```
+
+## Build faster with cached dependencies
+
+To build faster, you can cache the installation files for your
+project's dependencies by using the `cache` parameter.
+
+This example caches Jekyll dependencies in a `vendor` directory
+when you run `bundle install`:
+
+```yaml
+image: ruby:2.7
+
+workflow:
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+
+cache:
+ paths:
+ - vendor/
+
+before_script:
+ - gem install bundler
+ - bundle install --path vendor
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "master"'
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ rules:
+ - if: '$CI_COMMIT_BRANCH != "master"'
+```
+
+In this case, you need to exclude the `/vendor`
+directory from the list of folders Jekyll builds. Otherwise, Jekyll
+will try to build the directory contents along with the site.
+
+In the root directory, create a file called `_config.yml`
+and add this content:
+
+```yaml
+exclude:
+ - vendor
+```
+
+Now GitLab CI/CD not only builds the website,
+but also pushes with **continuous tests** to feature-branches,
+**caches** dependencies installed with Bundler, and
+**continuously deploys** every push to the `master` branch.
+
+## Related topics
+
+For more information, see the following blog posts.
+
+- [Use GitLab CI/CD `environments` to deploy your
+ web app to staging and production](https://about.gitlab.com/blog/2016/08/26/ci-deployment-and-environments/).
+- Learn [how to run jobs sequentially,
+ in parallel, or build a custom pipeline](https://about.gitlab.com/blog/2016/07/29/the-basics-of-gitlab-ci/).
+- Learn [how to pull specific directories from different projects](https://about.gitlab.com/blog/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+ to deploy this website, <https://docs.gitlab.com>.
+- Learn [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 8cf58280b88..e45befe004e 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -1,402 +1,5 @@
---
-last_updated: 2020-01-06
-type: reference, howto
-stage: Release
-group: Release Management
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: 'getting_started/pages_from_scratch.md'
---
-# Create a GitLab Pages website from scratch
-
-This tutorial shows you how to create a Pages site from scratch. You will start with
-a blank project and create your own CI file, which gives instruction to the
-[GitLab Runner](https://docs.gitlab.com/runner/). When your CI/CD
-[pipeline](../../../ci/pipelines/index.md) runs, the Pages site is created.
-
-This example uses the [Jekyll](https://jekyllrb.com/) Static Site Generator (SSG).
-Other SSGs follow similar steps. You do not need to be familiar with Jekyll or SSGs
-to complete this tutorial.
-
-## Prerequisites
-
-To follow along with this example, start with a blank project in GitLab.
-Create three files in the root (top-level) directory.
-
-- `.gitlab-ci.yml` A YAML file that contains the commands you want to run.
- For now, leave the file's contents blank.
-
-- `index.html` An HTML file you can populate with whatever HTML content you'd like,
- for example:
-
- ```html
- <html>
- <head>
- <title>Home</title>
- </head>
- <body>
- <h1>Hello World!</h1>
- </body>
- </html>
- ```
-
-- [`Gemfile`](https://bundler.io/gemfile.html) A file that describes dependencies for Ruby programs.
- Populate it with this content:
-
- ```ruby
- source "https://rubygems.org"
-
- gem "jekyll"
- ```
-
-## Choose a Docker image
-
-In this example, the Runner uses a [Docker image](../../../ci/docker/using_docker_images.md)
-to run scripts and deploy the site.
-
-This specific Ruby image is maintained on [DockerHub](https://hub.docker.com/_/ruby).
-
-Edit your `.gitlab-ci.yml` and add this text as the first line.
-
-```yaml
-image: ruby:2.7
-```
-
-If your SSG needs [NodeJS](https://nodejs.org/) to build, you must specify an
-image that contains NodeJS as part of its file system. For example, for a
-[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:12.17.0`.
-
-## Install Jekyll
-
-To run [Jekyll](https://jekyllrb.com/) locally, you would open your terminal and:
-
-- Install [Bundler](https://bundler.io/) by running `gem install bundler`.
-- Create `Gemfile.lock` by running `bundle install`.
-- Install Jekyll by running `bundle exec jekyll build`.
-
-In the `.gitlab-ci.yml` file, this is written as:
-
-```yaml
-script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
-```
-
-In addition, in the `.gitlab-ci.yml` file, each `script` is organized by a `job`.
-A `job` includes the scripts and settings you want to apply to that specific
-task.
-
-```yaml
-job:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
-```
-
-For GitLab Pages, this `job` has a specific name, called `pages`.
-This setting tells the Runner you want the job to deploy your website
-with GitLab Pages:
-
-```yaml
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
-```
-
-## Specify the `public` directory for output
-
-Jekyll needs to know where to generate its output.
-GitLab Pages only considers files in a directory called `public`.
-
-Jekyll uses destination (`-d`) to specify an output directory for the built website:
-
-```yaml
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
-```
-
-## Specify the `public` directory for artifacts
-
-Now that Jekyll has output the files to the `public` directory,
-the Runner needs to know where to get them. The artifacts are stored
-in the `public` directory:
-
-```yaml
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-Paste this into `.gitlab-ci.yml` file, so it now looks like this:
-
-```yaml
-image: ruby:2.7
-
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-Now save and commit the `.gitlab-ci.yml` file. You can watch the pipeline run
-by going to **CI / CD > Pipelines**.
-
-When it succeeds, go to **Settings > Pages** to view the URL where your site
-is now available.
-
-If you want to do more advanced tasks, you can update your `.gitlab-ci.yml` file
-with [any of the available settings](../../../ci/yaml/README.md). You can check
-your CI syntax with the [GitLab CI/CD Lint Tool](https://gitlab.com/ci/lint).
-
-The following topics show other examples of other options you can add to your CI/CD file.
-
-## Deploy specific branches to a Pages site
-
-You may want to deploy to a Pages site only from specific branches.
-
-First, add a `workflow` section to force the pipeline to run only when changes are
-pushed to branches:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-Then configure the pipeline to run the job for the master branch only.
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-```
-
-## Specify a stage to deploy
-
-There are three default stages for GitLab CI/CD: build, test,
-and deploy.
-
-If you want to test your script and check the built site before deploying
-to production, you can run the test exactly as it will run when you
-push to `master`.
-
-To specify a stage for your job to run in,
-add a `stage` line to your CI file:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- stage: deploy
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-```
-
-Now add another job to the CI file, telling it to
-test every push to every branch **except** the `master` branch:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-pages:
- stage: deploy
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-
-test:
- stage: test
- script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- rules:
- - if: '$CI_COMMIT_BRANCH != "master"'
-```
-
-When the `test` job runs in the `test` stage, Jekyll
-builds the site in a directory called `test`. The job affects
-all branches except `master`.
-
-When you apply stages to different jobs, every job in the same
-stage builds in parallel. If your web application needs more than
-one test before being deployed, you can run all your tests at the
-same time.
-
-## Remove duplicate commands
-
-To avoid duplicating the same scripts in every job, you can add them
-to a `before_script` section.
-
-In the example, `gem install bundler` and `bundle install` were running
-for both jobs, `pages` and `test`.
-
-Move these commands to a `before_script` section:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-before_script:
- - gem install bundler
- - bundle install
-
-pages:
- stage: deploy
- script:
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-
-test:
- stage: test
- script:
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- rules:
- - if: '$CI_COMMIT_BRANCH != "master"'
-```
-
-## Build faster with cached dependencies
-
-To build faster, you can cache the installation files for your
-project's dependencies by using the `cache` parameter.
-
-This example caches Jekyll dependencies in a `vendor` directory
-when you run `bundle install`:
-
-```yaml
-image: ruby:2.7
-
-workflow:
- rules:
- - if: '$CI_COMMIT_BRANCH'
-
-cache:
- paths:
- - vendor/
-
-before_script:
- - gem install bundler
- - bundle install --path vendor
-
-pages:
- stage: deploy
- script:
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- rules:
- - if: '$CI_COMMIT_BRANCH == "master"'
-
-test:
- stage: test
- script:
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- rules:
- - if: '$CI_COMMIT_BRANCH != "master"'
-```
-
-In this case, you need to exclude the `/vendor`
-directory from the list of folders Jekyll builds. Otherwise, Jekyll
-will try to build the directory contents along with the site.
-
-In the root directory, create a file called `_config.yml`
-and add this content:
-
-```yaml
-exclude:
- - vendor
-```
-
-Now GitLab CI/CD not only builds the website,
-but also pushes with **continuous tests** to feature-branches,
-**caches** dependencies installed with Bundler, and
-**continuously deploys** every push to the `master` branch.
-
-## Related topics
-
-For more information, see the following blog posts.
-
-- [Use GitLab CI/CD `environments` to deploy your
- web app to staging and production](https://about.gitlab.com/blog/2016/08/26/ci-deployment-and-environments/).
-- Learn [how to run jobs sequentially,
- in parallel, or build a custom pipeline](https://about.gitlab.com/blog/2016/07/29/the-basics-of-gitlab-ci/).
-- Learn [how to pull specific directories from different projects](https://about.gitlab.com/blog/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- to deploy this website, <https://docs.gitlab.com>.
-- Learn [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
+This document was moved to [getting_started/pages_from_scratch.md](getting_started/pages_from_scratch.md).
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 488288af5ad..b116c28d94b 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -46,10 +46,10 @@ To create a GitLab Pages website:
| Document | Description |
| -------- | ----------- |
-| [Fork a sample project](getting_started/fork_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
+| [Fork a sample project](getting_started/pages_forked_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
| [Use a new project template](getting_started/pages_new_project_template.md) | Create a new project with Pages already configured by using a new project template. |
| [Use a `.gitlab-ci.yml` template](getting_started/pages_ci_cd_template.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. |
-| [Create a `gitlab-ci.yml` file from scratch](getting_started_part_four.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. |
+| [Create a `gitlab-ci.yml` file from scratch](getting_started/pages_from_scratch.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. |
To update a GitLab Pages website:
@@ -81,7 +81,7 @@ becomes available automatically.
To deploy your site, GitLab uses its built-in tool called [GitLab CI/CD](../../../ci/README.md)
to build your site and publish it to the GitLab Pages server. The sequence of
scripts that GitLab CI/CD runs to accomplish this task is created from a file named
-`.gitlab-ci.yml`, which you can [create and modify](getting_started_part_four.md) at will. A specific `job` called `pages` in the configuration file will make GitLab aware that you are deploying a GitLab Pages website.
+`.gitlab-ci.yml`, which you can [create and modify](getting_started/pages_from_scratch.md) at will. A specific `job` called `pages` in the configuration file will make GitLab aware that you are deploying a GitLab Pages website.
You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-default-domain-names),
`*.gitlab.io`, or your own domain (`example.com`). In that case, you'll
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 614a0d0dd19..6177a81dbea 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -130,7 +130,7 @@ pages:
### `.gitlab-ci.yml` for a static site generator
-See this document for a [step-by-step guide](getting_started_part_four.md).
+See this document for a [step-by-step guide](getting_started/pages_from_scratch.md).
### `.gitlab-ci.yml` for a repository where there's also actual code
diff --git a/lib/gitlab/database/dynamic_model_helpers.rb b/lib/gitlab/database/dynamic_model_helpers.rb
new file mode 100644
index 00000000000..892f8291780
--- /dev/null
+++ b/lib/gitlab/database/dynamic_model_helpers.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module DynamicModelHelpers
+ def define_batchable_model(table_name)
+ Class.new(ActiveRecord::Base) do
+ include EachBatch
+
+ self.table_name = table_name
+ self.inheritance_column = :_type_disabled
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index fd09c31e994..62a053e9c5a 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -3,10 +3,10 @@
module Gitlab
module Database
module MigrationHelpers
+ include Migrations::BackgroundMigrationHelpers
+
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
MAX_IDENTIFIER_NAME_LENGTH = 63
- BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job
- BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time
PERMITTED_TIMESTAMP_COLUMNS = %i[created_at updated_at deleted_at].to_set.freeze
DEFAULT_TIMESTAMP_COLUMNS = %i[created_at updated_at].freeze
@@ -786,10 +786,6 @@ module Gitlab
end
end
- def perform_background_migration_inline?
- Rails.env.test? || Rails.env.development?
- end
-
# Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(trigger, table, old, new)
execute <<-EOF.strip_heredoc
@@ -973,106 +969,6 @@ into similar problems in the future (e.g. when new tables are created).
end
end
- # Bulk queues background migration jobs for an entire table, batched by ID range.
- # "Bulk" meaning many jobs will be pushed at a time for efficiency.
- # If you need a delay interval per job, then use `queue_background_migration_jobs_by_range_at_intervals`.
- #
- # model_class - The table being iterated over
- # job_class_name - The background migration job class as a string
- # batch_size - The maximum number of rows per job
- #
- # Example:
- #
- # class Route < ActiveRecord::Base
- # include EachBatch
- # self.table_name = 'routes'
- # end
- #
- # bulk_queue_background_migration_jobs_by_range(Route, 'ProcessRoutes')
- #
- # Where the model_class includes EachBatch, and the background migration exists:
- #
- # class Gitlab::BackgroundMigration::ProcessRoutes
- # def perform(start_id, end_id)
- # # do something
- # end
- # end
- def bulk_queue_background_migration_jobs_by_range(model_class, job_class_name, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE)
- raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
-
- jobs = []
- table_name = model_class.quoted_table_name
-
- model_class.each_batch(of: batch_size) do |relation|
- start_id, end_id = relation.pluck("MIN(#{table_name}.id)", "MAX(#{table_name}.id)").first
-
- if jobs.length >= BACKGROUND_MIGRATION_JOB_BUFFER_SIZE
- # Note: This code path generally only helps with many millions of rows
- # We push multiple jobs at a time to reduce the time spent in
- # Sidekiq/Redis operations. We're using this buffer based approach so we
- # don't need to run additional queries for every range.
- bulk_migrate_async(jobs)
- jobs.clear
- end
-
- jobs << [job_class_name, [start_id, end_id]]
- end
-
- bulk_migrate_async(jobs) unless jobs.empty?
- end
-
- # Queues background migration jobs for an entire table, batched by ID range.
- # Each job is scheduled with a `delay_interval` in between.
- # If you use a small interval, then some jobs may run at the same time.
- #
- # model_class - The table or relation being iterated over
- # job_class_name - The background migration job class as a string
- # delay_interval - The duration between each job's scheduled time (must respond to `to_f`)
- # batch_size - The maximum number of rows per job
- # other_arguments - Other arguments to send to the job
- #
- # *Returns the final migration delay*
- #
- # Example:
- #
- # class Route < ActiveRecord::Base
- # include EachBatch
- # self.table_name = 'routes'
- # end
- #
- # queue_background_migration_jobs_by_range_at_intervals(Route, 'ProcessRoutes', 1.minute)
- #
- # Where the model_class includes EachBatch, and the background migration exists:
- #
- # class Gitlab::BackgroundMigration::ProcessRoutes
- # def perform(start_id, end_id)
- # # do something
- # end
- # end
- def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE, other_job_arguments: [], initial_delay: 0)
- raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
-
- # To not overload the worker too much we enforce a minimum interval both
- # when scheduling and performing jobs.
- if delay_interval < BackgroundMigrationWorker.minimum_interval
- delay_interval = BackgroundMigrationWorker.minimum_interval
- end
-
- final_delay = 0
-
- model_class.each_batch(of: batch_size) do |relation, index|
- start_id, end_id = relation.pluck(Arel.sql('MIN(id), MAX(id)')).first
-
- # `BackgroundMigrationWorker.bulk_perform_in` schedules all jobs for
- # the same time, which is not helpful in most cases where we wish to
- # spread the work over time.
- final_delay = initial_delay + delay_interval * index
- migrate_in(final_delay, job_class_name, [start_id, end_id] + other_job_arguments)
- end
-
- final_delay
- end
-
# Fetches indexes on a column by name for postgres.
#
# This will include indexes using an expression on the column, for example:
@@ -1131,30 +1027,6 @@ into similar problems in the future (e.g. when new tables are created).
execute(sql)
end
- def migrate_async(*args)
- with_migration_context do
- BackgroundMigrationWorker.perform_async(*args)
- end
- end
-
- def migrate_in(*args)
- with_migration_context do
- BackgroundMigrationWorker.perform_in(*args)
- end
- end
-
- def bulk_migrate_in(*args)
- with_migration_context do
- BackgroundMigrationWorker.bulk_perform_in(*args)
- end
- end
-
- def bulk_migrate_async(*args)
- with_migration_context do
- BackgroundMigrationWorker.bulk_perform_async(*args)
- end
- end
-
# Returns the name for a check constraint
#
# type:
@@ -1437,10 +1309,6 @@ into similar problems in the future (e.g. when new tables are created).
your migration class
ERROR
end
-
- def with_migration_context(&block)
- Gitlab::ApplicationContext.with_context(caller_id: self.class.to_s, &block)
- end
end
end
end
diff --git a/lib/gitlab/database/migrations/background_migration_helpers.rb b/lib/gitlab/database/migrations/background_migration_helpers.rb
new file mode 100644
index 00000000000..abde8470063
--- /dev/null
+++ b/lib/gitlab/database/migrations/background_migration_helpers.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Migrations
+ module BackgroundMigrationHelpers
+ BACKGROUND_MIGRATION_BATCH_SIZE = 1_000 # Number of rows to process per job
+ BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1_000 # Number of jobs to bulk queue at a time
+
+ # Bulk queues background migration jobs for an entire table, batched by ID range.
+ # "Bulk" meaning many jobs will be pushed at a time for efficiency.
+ # If you need a delay interval per job, then use `queue_background_migration_jobs_by_range_at_intervals`.
+ #
+ # model_class - The table being iterated over
+ # job_class_name - The background migration job class as a string
+ # batch_size - The maximum number of rows per job
+ #
+ # Example:
+ #
+ # class Route < ActiveRecord::Base
+ # include EachBatch
+ # self.table_name = 'routes'
+ # end
+ #
+ # bulk_queue_background_migration_jobs_by_range(Route, 'ProcessRoutes')
+ #
+ # Where the model_class includes EachBatch, and the background migration exists:
+ #
+ # class Gitlab::BackgroundMigration::ProcessRoutes
+ # def perform(start_id, end_id)
+ # # do something
+ # end
+ # end
+ def bulk_queue_background_migration_jobs_by_range(model_class, job_class_name, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE)
+ raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
+
+ jobs = []
+ table_name = model_class.quoted_table_name
+
+ model_class.each_batch(of: batch_size) do |relation|
+ start_id, end_id = relation.pluck("MIN(#{table_name}.id)", "MAX(#{table_name}.id)").first
+
+ if jobs.length >= BACKGROUND_MIGRATION_JOB_BUFFER_SIZE
+ # Note: This code path generally only helps with many millions of rows
+ # We push multiple jobs at a time to reduce the time spent in
+ # Sidekiq/Redis operations. We're using this buffer based approach so we
+ # don't need to run additional queries for every range.
+ bulk_migrate_async(jobs)
+ jobs.clear
+ end
+
+ jobs << [job_class_name, [start_id, end_id]]
+ end
+
+ bulk_migrate_async(jobs) unless jobs.empty?
+ end
+
+ # Queues background migration jobs for an entire table, batched by ID range.
+ # Each job is scheduled with a `delay_interval` in between.
+ # If you use a small interval, then some jobs may run at the same time.
+ #
+ # model_class - The table or relation being iterated over
+ # job_class_name - The background migration job class as a string
+ # delay_interval - The duration between each job's scheduled time (must respond to `to_f`)
+ # batch_size - The maximum number of rows per job
+ # other_arguments - Other arguments to send to the job
+ #
+ # *Returns the final migration delay*
+ #
+ # Example:
+ #
+ # class Route < ActiveRecord::Base
+ # include EachBatch
+ # self.table_name = 'routes'
+ # end
+ #
+ # queue_background_migration_jobs_by_range_at_intervals(Route, 'ProcessRoutes', 1.minute)
+ #
+ # Where the model_class includes EachBatch, and the background migration exists:
+ #
+ # class Gitlab::BackgroundMigration::ProcessRoutes
+ # def perform(start_id, end_id)
+ # # do something
+ # end
+ # end
+ def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BACKGROUND_MIGRATION_BATCH_SIZE, other_job_arguments: [], initial_delay: 0)
+ raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
+
+ # To not overload the worker too much we enforce a minimum interval both
+ # when scheduling and performing jobs.
+ if delay_interval < BackgroundMigrationWorker.minimum_interval
+ delay_interval = BackgroundMigrationWorker.minimum_interval
+ end
+
+ final_delay = 0
+
+ model_class.each_batch(of: batch_size) do |relation, index|
+ start_id, end_id = relation.pluck(Arel.sql('MIN(id), MAX(id)')).first
+
+ # `BackgroundMigrationWorker.bulk_perform_in` schedules all jobs for
+ # the same time, which is not helpful in most cases where we wish to
+ # spread the work over time.
+ final_delay = initial_delay + delay_interval * index
+ migrate_in(final_delay, job_class_name, [start_id, end_id] + other_job_arguments)
+ end
+
+ final_delay
+ end
+
+ def perform_background_migration_inline?
+ Rails.env.test? || Rails.env.development?
+ end
+
+ def migrate_async(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.perform_async(*args)
+ end
+ end
+
+ def migrate_in(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.perform_in(*args)
+ end
+ end
+
+ def bulk_migrate_in(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.bulk_perform_in(*args)
+ end
+ end
+
+ def bulk_migrate_async(*args)
+ with_migration_context do
+ BackgroundMigrationWorker.bulk_perform_async(*args)
+ end
+ end
+
+ def with_migration_context(&block)
+ Gitlab::ApplicationContext.with_context(caller_id: self.class.to_s, &block)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb b/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb
new file mode 100644
index 00000000000..2003763bcca
--- /dev/null
+++ b/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module PartitioningMigrationHelpers
+ # Class that will generically copy data from a given table into its corresponding partitioned table
+ class BackfillPartitionedTable
+ include ::Gitlab::Database::DynamicModelHelpers
+
+ SUB_BATCH_SIZE = 2_500
+ PAUSE_SECONDS = 0.25
+
+ def perform(start_id, stop_id, source_table, partitioned_table, source_column)
+ return unless Feature.enabled?(:backfill_partitioned_audit_events, default_enabled: true)
+
+ if transaction_open?
+ raise "Aborting job to backfill partitioned #{source_table} table! Do not run this job in a transaction block!"
+ end
+
+ unless table_exists?(partitioned_table)
+ logger.warn "exiting backfill migration because partitioned table #{partitioned_table} does not exist. " \
+ "This could be due to the migration being rolled back after migration jobs were enqueued in sidekiq"
+ return
+ end
+
+ bulk_copy = BulkCopy.new(source_table, partitioned_table, source_column)
+ parent_batch_relation = relation_scoped_to_range(source_table, source_column, start_id, stop_id)
+
+ parent_batch_relation.each_batch(of: SUB_BATCH_SIZE) do |sub_batch|
+ start_id, stop_id = sub_batch.pluck(Arel.sql("MIN(#{source_column}), MAX(#{source_column})")).first
+
+ bulk_copy.copy_between(start_id, stop_id)
+ sleep(PAUSE_SECONDS)
+ end
+ end
+
+ private
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ def transaction_open?
+ connection.transaction_open?
+ end
+
+ def table_exists?(table)
+ connection.table_exists?(table)
+ end
+
+ def logger
+ @logger ||= ::Gitlab::BackgroundMigration::Logger.build
+ end
+
+ def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
+ define_batchable_model(source_table).where(source_key_column => start_id..stop_id)
+ end
+
+ # Helper class to copy data between two tables via upserts
+ class BulkCopy
+ DELIMITER = ', '
+
+ attr_reader :source_table, :destination_table, :source_column
+
+ def initialize(source_table, destination_table, source_column)
+ @source_table = source_table
+ @destination_table = destination_table
+ @source_column = source_column
+ end
+
+ def copy_between(start_id, stop_id)
+ connection.execute(<<~SQL)
+ INSERT INTO #{destination_table} (#{column_listing})
+ SELECT #{column_listing}
+ FROM #{source_table}
+ WHERE #{source_column} BETWEEN #{start_id} AND #{stop_id}
+ FOR UPDATE
+ ON CONFLICT (#{conflict_targets}) DO NOTHING
+ SQL
+ end
+
+ private
+
+ def connection
+ @connection ||= ActiveRecord::Base.connection
+ end
+
+ def column_listing
+ @column_listing ||= connection.columns(source_table).map(&:name).join(DELIMITER)
+ end
+
+ def conflict_targets
+ connection.primary_key(destination_table).join(DELIMITER)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb
index 9e687009cd7..1fb9476b7d9 100644
--- a/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb
+++ b/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers.rb
@@ -99,7 +99,7 @@ module Gitlab
drop_function(fn_name, if_exists: true)
else
create_or_replace_fk_function(fn_name, final_keys)
- create_trigger(trigger_name, fn_name, fires: "AFTER DELETE ON #{to_table}")
+ create_trigger(to_table, trigger_name, fn_name, fires: 'AFTER DELETE')
end
end
end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
index f77fbe98df1..abc4b2ba546 100644
--- a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
+++ b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
@@ -5,10 +5,16 @@ module Gitlab
module PartitioningMigrationHelpers
module TableManagementHelpers
include ::Gitlab::Database::SchemaHelpers
+ include ::Gitlab::Database::DynamicModelHelpers
+ include ::Gitlab::Database::Migrations::BackgroundMigrationHelpers
- WHITELISTED_TABLES = %w[audit_events].freeze
+ ALLOWED_TABLES = %w[audit_events].freeze
ERROR_SCOPE = 'table partitioning'
+ MIGRATION_CLASS_NAME = "::#{module_parent_name}::BackfillPartitionedTable"
+ BATCH_INTERVAL = 2.minutes.freeze
+ BATCH_SIZE = 50_000
+
# Creates a partitioned copy of an existing table, using a RANGE partitioning strategy on a timestamp column.
# One partition is created per month between the given `min_date` and `max_date`.
#
@@ -23,7 +29,7 @@ module Gitlab
# :max_date - a date specifying the upper bounds of the partitioning range
#
def partition_table_by_date(table_name, column_name, min_date:, max_date:)
- assert_table_is_whitelisted(table_name)
+ assert_table_is_allowed(table_name)
assert_not_in_transaction_block(scope: ERROR_SCOPE)
raise "max_date #{max_date} must be greater than min_date #{min_date}" if min_date >= max_date
@@ -35,9 +41,11 @@ module Gitlab
raise "partition column #{column_name} does not exist on #{table_name}" if partition_column.nil?
new_table_name = partitioned_table_name(table_name)
+
create_range_partitioned_copy(new_table_name, table_name, partition_column, primary_key)
create_daterange_partitions(new_table_name, partition_column.name, min_date, max_date)
- create_sync_trigger(table_name, new_table_name, primary_key)
+ create_trigger_to_sync_tables(table_name, new_table_name, primary_key)
+ enqueue_background_migration(table_name, new_table_name, primary_key)
end
# Clean up a partitioned copy of an existing table. This deletes the partitioned table and all partitions.
@@ -47,7 +55,7 @@ module Gitlab
# drop_partitioned_table_for :audit_events
#
def drop_partitioned_table_for(table_name)
- assert_table_is_whitelisted(table_name)
+ assert_table_is_allowed(table_name)
assert_not_in_transaction_block(scope: ERROR_SCOPE)
with_lock_retries do
@@ -64,10 +72,10 @@ module Gitlab
private
- def assert_table_is_whitelisted(table_name)
- return if WHITELISTED_TABLES.include?(table_name.to_s)
+ def assert_table_is_allowed(table_name)
+ return if ALLOWED_TABLES.include?(table_name.to_s)
- raise "partitioning helpers are in active development, and #{table_name} is not whitelisted for use, " \
+ raise "partitioning helpers are in active development, and #{table_name} is not allowed for use, " \
"for more information please contact the database team"
end
@@ -125,7 +133,8 @@ module Gitlab
min_date = min_date.beginning_of_month.to_date
max_date = max_date.next_month.beginning_of_month.to_date
- create_range_partition_safely("#{table_name}_000000", table_name, 'MINVALUE', to_sql_date_literal(min_date))
+ upper_bound = to_sql_date_literal(min_date)
+ create_range_partition_safely("#{table_name}_000000", table_name, 'MINVALUE', upper_bound)
while min_date < max_date
partition_name = "#{table_name}_#{min_date.strftime('%Y%m')}"
@@ -154,7 +163,7 @@ module Gitlab
create_range_partition(partition_name, table_name, lower_bound, upper_bound)
end
- def create_sync_trigger(source_table, target_table, unique_key)
+ def create_trigger_to_sync_tables(source_table, target_table, unique_key)
function_name = sync_function_name(source_table)
trigger_name = sync_trigger_name(source_table)
@@ -162,11 +171,19 @@ module Gitlab
create_sync_function(function_name, target_table, unique_key)
create_comment('FUNCTION', function_name, "Partitioning migration: table sync for #{source_table} table")
- create_trigger(trigger_name, function_name, fires: "AFTER INSERT OR UPDATE OR DELETE ON #{source_table}")
+ create_sync_trigger(source_table, trigger_name, function_name)
end
end
def create_sync_function(name, target_table, unique_key)
+ if function_exists?(name)
+ # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.warn "Partitioning sync function not created because it already exists" \
+ " (this may be due to an aborted migration or similar): function name: #{name}"
+ # rubocop:enable Gitlab/RailsLogger
+ return
+ end
+
delimiter = ",\n "
column_names = connection.columns(target_table).map(&:name)
set_statements = build_set_statements(column_names, unique_key)
@@ -190,7 +207,30 @@ module Gitlab
end
def build_set_statements(column_names, unique_key)
- column_names.reject { |name| name == unique_key }.map { |column_name| "#{column_name} = NEW.#{column_name}" }
+ column_names.reject { |name| name == unique_key }.map { |name| "#{name} = NEW.#{name}" }
+ end
+
+ def create_sync_trigger(table_name, trigger_name, function_name)
+ if trigger_exists?(table_name, trigger_name)
+ # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.warn "Partitioning sync trigger not created because it already exists" \
+ " (this may be due to an aborted migration or similar): trigger name: #{trigger_name}"
+ # rubocop:enable Gitlab/RailsLogger
+ return
+ end
+
+ create_trigger(table_name, trigger_name, function_name, fires: 'AFTER INSERT OR UPDATE OR DELETE')
+ end
+
+ def enqueue_background_migration(source_table, partitioned_table, source_key)
+ model_class = define_batchable_model(source_table)
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ model_class,
+ MIGRATION_CLASS_NAME,
+ BATCH_INTERVAL,
+ batch_size: BATCH_SIZE,
+ other_job_arguments: [source_table.to_s, partitioned_table, source_key])
end
end
end
diff --git a/lib/gitlab/database/schema_helpers.rb b/lib/gitlab/database/schema_helpers.rb
index 8e544307d81..ad10fd61067 100644
--- a/lib/gitlab/database/schema_helpers.rb
+++ b/lib/gitlab/database/schema_helpers.rb
@@ -16,15 +16,30 @@ module Gitlab
SQL
end
- def create_trigger(name, function_name, fires: nil)
+ def function_exists?(name)
+ connection.select_value("SELECT 1 FROM pg_proc WHERE proname = '#{name}'")
+ end
+
+ def create_trigger(table_name, name, function_name, fires:)
execute(<<~SQL)
CREATE TRIGGER #{name}
- #{fires}
+ #{fires} ON #{table_name}
FOR EACH ROW
EXECUTE PROCEDURE #{function_name}()
SQL
end
+ def trigger_exists?(table_name, name)
+ connection.select_value(<<~SQL)
+ SELECT 1
+ FROM pg_trigger
+ INNER JOIN pg_class
+ ON pg_trigger.tgrelid = pg_class.oid
+ WHERE pg_class.relname = '#{table_name}'
+ AND pg_trigger.tgname = '#{name}'
+ SQL
+ end
+
def drop_function(name, if_exists: true)
exists_clause = optional_clause(if_exists, "IF EXISTS")
execute("DROP FUNCTION #{exists_clause} #{name}()")
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 54e4d0fb2fc..aa2ef2f058f 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -5,11 +5,11 @@ module QA
module Project
module Pipeline
class Index < QA::Page::Base
- view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
+ view 'app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue' do
element :pipeline_url_link
end
- view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do
+ view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue' do
element :pipeline_commit_status
element :pipeline_retry_button
end
diff --git a/spec/frontend/fixtures/metrics_dashboard.rb b/spec/frontend/fixtures/metrics_dashboard.rb
index 6ee730f5c3d..b5dee7525f6 100644
--- a/spec/frontend/fixtures/metrics_dashboard.rb
+++ b/spec/frontend/fixtures/metrics_dashboard.rb
@@ -6,11 +6,10 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
include MetricsDashboardHelpers
- let_it_be(:user) { create(:user) }
- let_it_be(:namespace) { create(:namespace, name: 'monitoring' )}
- let_it_be(:project) { project_with_dashboard_namespace('.gitlab/dashboards/test.yml', namespace: namespace) }
- let_it_be(:environment) { create(:environment, id: 1, project: project) }
- let_it_be(:params) { { environment: environment } }
+ let(:user) { create(:user) }
+ let(:project) { project_with_dashboard('.gitlab/dashboards/test.yml') }
+ let(:environment) { create(:environment, project: project) }
+ let(:params) { { environment: environment } }
before(:all) do
clean_frontend_fixtures('metrics_dashboard/')
@@ -25,7 +24,6 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
project.add_maintainer(user)
allow(controller).to receive(:project).and_return(project)
- allow(controller).to receive(:environment).and_return(environment)
allow(controller)
.to receive(:metrics_dashboard_params)
.and_return(params)
@@ -37,9 +35,7 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
it 'metrics_dashboard/environment_metrics_dashboard.json' do
routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" }
-
response = get :metrics_dashboard, format: :json
-
expect(response).to be_successful
end
end
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 1f8e961dfd4..4b08163f30a 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -3,19 +3,7 @@
exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
- currentenvironmentname="environment1"
- customdashboardbasepath=".gitlab/dashboards"
- dashboardendpoint="/monitoring/monitor-project/-/environments/1/metrics_dashboard.json"
- dashboardsendpoint="/monitoring/monitor-project/-/performance_monitoring/dashboards.json"
- dashboardtimezone="LOCAL"
data-qa-selector="prometheus_graphs"
- deploymentsendpoint="/monitoring/monitor-project/-/environments/1/deployments.json"
- environmentstate="available"
- logspath="/monitoring/monitor-project/-/logs?environment_name=environment1"
- metricsdashboardbasepath="/monitoring/monitor-project/-/environments/1/metrics"
- metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
- projectpath="/monitoring/monitor-project"
- prometheusstatus=""
>
<div
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
@@ -147,15 +135,15 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<empty-state-stub
- clusterspath="/monitoring/monitor-project/-/clusters"
- documentationpath="/help/administration/monitoring/prometheus/index.md"
- emptygettingstartedsvgpath="/images/illustrations/monitoring/getting_started.svg"
- emptyloadingsvgpath="/images/illustrations/monitoring/loading.svg"
- emptynodatasmallsvgpath="/images/illustrations/chart-empty-state-small.svg"
- emptynodatasvgpath="/images/illustrations/monitoring/no_data.svg"
- emptyunabletoconnectsvgpath="/images/illustrations/monitoring/unable_to_connect.svg"
+ clusterspath="/path/to/clusters"
+ documentationpath="/path/to/docs"
+ emptygettingstartedsvgpath="/path/to/getting-started.svg"
+ emptyloadingsvgpath="/path/to/loading.svg"
+ emptynodatasmallsvgpath="/path/to/no-data-small.svg"
+ emptynodatasvgpath="/path/to/no-data.svg"
+ emptyunabletoconnectsvgpath="/path/to/unable-to-connect.svg"
selectedstate="gettingStarted"
- settingspath="/monitoring/monitor-project/-/services/prometheus/edit"
+ settingspath="/path/to/settings"
/>
</div>
`;
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 96e0c39cb27..2748948168f 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -18,8 +18,8 @@ import {
singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
+ propsData,
} from '../mock_data';
-import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
import { panelTypes } from '~/monitoring/constants';
@@ -32,6 +32,7 @@ import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
+import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
@@ -62,7 +63,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
- settingsPath: dashboardProps.settingsPath,
+ settingsPath: propsData.settingsPath,
...props,
},
store,
@@ -315,7 +316,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
- expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
+ expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
});
});
});
@@ -432,7 +433,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
- settingsPath: dashboardProps.settingsPath,
+ settingsPath: propsData.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@@ -482,7 +483,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
- settingsPath: dashboardProps.settingsPath,
+ settingsPath: propsData.settingsPath,
namespace: mockNamespace,
},
store,
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 61a39ca3a17..5143a06ec0c 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -27,12 +27,8 @@ import {
setupStoreWithVariable,
setupStoreWithLinks,
} from '../store_utils';
-import { environmentData, dashboardGitResponse } from '../mock_data';
-import {
- metricsDashboardViewModel,
- metricsDashboardPanelCount,
- dashboardProps,
-} from '../fixture_data';
+import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
+import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
import createFlash from '~/flash';
jest.mock('~/flash');
@@ -52,7 +48,7 @@ describe('Dashboard', () => {
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
- propsData: { ...dashboardProps, ...props },
+ propsData: { ...propsData, ...props },
store,
stubs: {
DashboardHeader,
@@ -63,7 +59,7 @@ describe('Dashboard', () => {
const createMountedWrapper = (props = {}, options = {}) => {
wrapper = mount(Dashboard, {
- propsData: { ...dashboardProps, ...props },
+ propsData: { ...propsData, ...props },
store,
stubs: {
'graph-group': true,
diff --git a/spec/frontend/monitoring/components/dashboard_template_spec.js b/spec/frontend/monitoring/components/dashboard_template_spec.js
index 8941e57c4ce..a1a450d4abe 100644
--- a/spec/frontend/monitoring/components/dashboard_template_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_template_spec.js
@@ -5,7 +5,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
import { setupAllDashboards } from '../store_utils';
-import { dashboardProps } from '../fixture_data';
+import { propsData } from '../mock_data';
jest.mock('~/lib/utils/url_utility');
@@ -29,7 +29,7 @@ describe('Dashboard template', () => {
it('matches the default snapshot', () => {
wrapper = shallowMount(Dashboard, {
- propsData: { ...dashboardProps },
+ propsData: { ...propsData },
store,
stubs: {
DashboardHeader,
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index 276e20bae6a..a74c621db9b 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -9,8 +9,7 @@ import {
updateHistory,
} from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
-import { mockProjectDir } from '../mock_data';
-import { dashboardProps } from '../fixture_data';
+import { mockProjectDir, propsData } from '../mock_data';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
@@ -27,7 +26,7 @@ describe('dashboard invalid url parameters', () => {
const createMountedWrapper = (props = { hasMetrics: true }, options = {}) => {
wrapper = mount(Dashboard, {
- propsData: { ...dashboardProps, ...props },
+ propsData: { ...propsData, ...props },
store,
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
...options,
diff --git a/spec/frontend/monitoring/fixture_data.js b/spec/frontend/monitoring/fixture_data.js
index 51b9d44b9a4..08a0deca808 100644
--- a/spec/frontend/monitoring/fixture_data.js
+++ b/spec/frontend/monitoring/fixture_data.js
@@ -1,7 +1,5 @@
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
import { metricStates } from '~/monitoring/constants';
-import { parseBoolean, convertObjectProps } from '~/lib/utils/common_utils';
-import { convertToCamelCase } from '~/lib/utils/text_utility';
import { metricsResult } from './mock_data';
@@ -10,20 +8,6 @@ export const metricsDashboardResponse = getJSONFixture(
'metrics_dashboard/environment_metrics_dashboard.json',
);
export const metricsDashboardPayload = metricsDashboardResponse.dashboard;
-
-const metricsData = convertObjectProps(
- // Some props use kebab-case, convert to snake_case first
- key => convertToCamelCase(key.replace(/-/g, '_')),
- metricsDashboardResponse.metrics_data,
-);
-
-export const dashboardProps = {
- ...metricsData,
- hasMetrics: parseBoolean(metricsData.hasMetrics),
- customMetricsAvailable: parseBoolean(metricsData.customMetricsAvailable),
- prometheusAlertsAvailable: parseBoolean(metricsData.prometheusAlertsAvailable),
-};
-
export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload);
export const metricsDashboardPanelCount = 22;
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 0440b216090..ac925222924 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -12,21 +12,21 @@ import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import {
+ setGettingStartedEmptyState,
+ setInitialState,
+ setExpandedPanel,
+ clearExpandedPanel,
+ filterEnvironments,
fetchData,
fetchDashboard,
receiveMetricsDashboardSuccess,
+ fetchDashboardData,
+ fetchPrometheusMetric,
fetchDeploymentsData,
fetchEnvironmentsData,
- fetchDashboardData,
fetchAnnotations,
fetchDashboardValidationWarnings,
toggleStarredValue,
- fetchPrometheusMetric,
- setInitialState,
- filterEnvironments,
- setExpandedPanel,
- clearExpandedPanel,
- setGettingStartedEmptyState,
duplicateSystemDashboard,
updateVariablesAndFetchData,
} from '~/monitoring/stores/actions';
@@ -81,6 +81,7 @@ describe('Monitoring store actions', () => {
return q;
});
});
+
afterEach(() => {
mock.reset();
@@ -88,470 +89,114 @@ describe('Monitoring store actions', () => {
createFlash.mockReset();
});
- describe('fetchData', () => {
- it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
- return testAction(
- fetchData,
- null,
- state,
- [],
- [
- { type: 'fetchEnvironmentsData' },
- { type: 'fetchDashboard' },
- { type: 'fetchAnnotations' },
- ],
- );
- });
-
- it('dispatches when feature metricsDashboardAnnotations is on', () => {
- const origGon = window.gon;
- window.gon = { features: { metricsDashboardAnnotations: true } };
-
- return testAction(
- fetchData,
- null,
- state,
- [],
- [
- { type: 'fetchEnvironmentsData' },
- { type: 'fetchDashboard' },
- { type: 'fetchAnnotations' },
- ],
- ).then(() => {
- window.gon = origGon;
- });
- });
- });
+ // Setup
- describe('fetchDeploymentsData', () => {
- it('dispatches receiveDeploymentsDataSuccess on success', () => {
- state.deploymentsEndpoint = '/success';
- mock.onGet(state.deploymentsEndpoint).reply(200, {
- deployments: deploymentData,
- });
-
- return testAction(
- fetchDeploymentsData,
- null,
- state,
- [],
- [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
- );
- });
- it('dispatches receiveDeploymentsDataFailure on error', () => {
- state.deploymentsEndpoint = '/error';
- mock.onGet(state.deploymentsEndpoint).reply(500);
-
- return testAction(
- fetchDeploymentsData,
+ describe('setGettingStartedEmptyState', () => {
+ it('should commit SET_GETTING_STARTED_EMPTY_STATE mutation', done => {
+ testAction(
+ setGettingStartedEmptyState,
null,
state,
- [],
- [{ type: 'receiveDeploymentsDataFailure' }],
- () => {
- expect(createFlash).toHaveBeenCalled();
- },
- );
- });
- });
-
- describe('fetchEnvironmentsData', () => {
- beforeEach(() => {
- state.projectPath = 'gitlab-org/gitlab-test';
- });
-
- it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
- jest.spyOn(gqClient, 'mutate').mockReturnValue({
- data: {
- project: {
- data: {
- environments: [],
- },
- },
- },
- });
-
- return testAction(
- filterEnvironments,
- {},
- state,
[
{
- type: 'SET_ENVIRONMENTS_FILTER',
- payload: {},
- },
- ],
- [
- {
- type: 'fetchEnvironmentsData',
+ type: types.SET_GETTING_STARTED_EMPTY_STATE,
},
],
- );
- });
-
- it('fetch environments data call takes in search param', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const searchTerm = 'Something';
- const mutationVariables = {
- mutation: getEnvironments,
- variables: {
- projectPath: state.projectPath,
- search: searchTerm,
- states: [ENVIRONMENT_AVAILABLE_STATE],
- },
- };
- state.environmentsSearchTerm = searchTerm;
- mockMutate.mockResolvedValue({});
-
- return testAction(
- fetchEnvironmentsData,
- null,
- state,
[],
- [
- { type: 'requestEnvironmentsData' },
- { type: 'receiveEnvironmentsDataSuccess', payload: [] },
- ],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
+ done,
);
});
+ });
- it('dispatches receiveEnvironmentsDataSuccess on success', () => {
- jest.spyOn(gqClient, 'mutate').mockResolvedValue({
- data: {
- project: {
- data: {
- environments: environmentData,
- },
- },
+ describe('setInitialState', () => {
+ it('should commit SET_INITIAL_STATE mutation', done => {
+ testAction(
+ setInitialState,
+ {
+ currentDashboard: '.gitlab/dashboards/dashboard.yml',
+ deploymentsEndpoint: 'deployments.json',
},
- });
-
- return testAction(
- fetchEnvironmentsData,
- null,
state,
- [],
[
- { type: 'requestEnvironmentsData' },
{
- type: 'receiveEnvironmentsDataSuccess',
- payload: parseEnvironmentsResponse(environmentData, state.projectPath),
+ type: types.SET_INITIAL_STATE,
+ payload: {
+ currentDashboard: '.gitlab/dashboards/dashboard.yml',
+ deploymentsEndpoint: 'deployments.json',
+ },
},
],
- );
- });
-
- it('dispatches receiveEnvironmentsDataFailure on error', () => {
- jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
-
- return testAction(
- fetchEnvironmentsData,
- null,
- state,
[],
- [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
+ done,
);
});
});
- describe('fetchAnnotations', () => {
- beforeEach(() => {
- state.timeRange = {
- start: '2020-04-15T12:54:32.137Z',
- end: '2020-08-15T12:54:32.137Z',
- };
- state.projectPath = 'gitlab-org/gitlab-test';
- state.currentEnvironmentName = 'production';
- state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
- // testAction doesn't have access to getters. The state is passed in as getters
- // instead of the actual getters inside the testAction method implementation.
- // All methods downstream that needs access to getters will throw and error.
- // For that reason, the result of the getter is set as a state variable.
- state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
- });
-
- it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const mutationVariables = {
- mutation: getAnnotations,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- startingFrom: state.timeRange.start,
- },
- };
- const parsedResponse = parseAnnotationsResponse(annotationsData);
-
- mockMutate.mockResolvedValue({
- data: {
- project: {
- environments: {
- nodes: [
- {
- metricsDashboard: {
- annotations: {
- nodes: parsedResponse,
- },
- },
- },
- ],
- },
- },
- },
- });
-
- return testAction(
- fetchAnnotations,
- null,
- state,
- [],
- [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
- );
- });
-
- it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const mutationVariables = {
- mutation: getAnnotations,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- startingFrom: state.timeRange.start,
- },
- };
-
- mockMutate.mockRejectedValue({});
+ describe('setExpandedPanel', () => {
+ it('Sets a panel as expanded', () => {
+ const group = 'group_1';
+ const panel = { title: 'A Panel' };
return testAction(
- fetchAnnotations,
- null,
+ setExpandedPanel,
+ { group, panel },
state,
+ [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
[],
- [{ type: 'receiveAnnotationsFailure' }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
);
});
});
- describe('fetchDashboardValidationWarnings', () => {
- let mockMutate;
- let mutationVariables;
-
- beforeEach(() => {
- state.projectPath = 'gitlab-org/gitlab-test';
- state.currentEnvironmentName = 'production';
- state.currentDashboard = '.gitlab/dashboards/dashboard_with_warnings.yml';
-
- mockMutate = jest.spyOn(gqClient, 'mutate');
- mutationVariables = {
- mutation: getDashboardValidationWarnings,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- },
- };
- });
-
- it('dispatches receiveDashboardValidationWarningsSuccess with true payload when there are warnings', () => {
- mockMutate.mockResolvedValue({
- data: {
- project: {
- id: 'gid://gitlab/Project/29',
- environments: {
- nodes: [
- {
- name: 'production',
- metricsDashboard: {
- path: '.gitlab/dashboards/dashboard_errors_test.yml',
- schemaValidationWarnings: ["unit: can't be blank"],
- },
- },
- ],
- },
- },
- },
- });
-
+ describe('clearExpandedPanel', () => {
+ it('Clears a panel as expanded', () => {
return testAction(
- fetchDashboardValidationWarnings,
- null,
+ clearExpandedPanel,
+ undefined,
state,
+ [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
[],
- [{ type: 'receiveDashboardValidationWarningsSuccess', payload: true }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
);
});
+ });
- it('dispatches receiveDashboardValidationWarningsSuccess with false payload when there are no warnings', () => {
- mockMutate.mockResolvedValue({
- data: {
- project: {
- id: 'gid://gitlab/Project/29',
- environments: {
- nodes: [
- {
- name: 'production',
- metricsDashboard: {
- path: '.gitlab/dashboards/dashboard_errors_test.yml',
- schemaValidationWarnings: [],
- },
- },
- ],
- },
- },
- },
- });
+ // All Data
+ describe('fetchData', () => {
+ it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
return testAction(
- fetchDashboardValidationWarnings,
+ fetchData,
null,
state,
[],
- [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
+ [
+ { type: 'fetchEnvironmentsData' },
+ { type: 'fetchDashboard' },
+ { type: 'fetchAnnotations' },
+ ],
);
});
- it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
- mockMutate.mockRejectedValue({});
+ it('dispatches when feature metricsDashboardAnnotations is on', () => {
+ const origGon = window.gon;
+ window.gon = { features: { metricsDashboardAnnotations: true } };
return testAction(
- fetchDashboardValidationWarnings,
+ fetchData,
null,
state,
[],
- [{ type: 'receiveDashboardValidationWarningsFailure' }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
- );
- });
- });
-
- describe('Toggles starred value of current dashboard', () => {
- let unstarredDashboard;
- let starredDashboard;
-
- beforeEach(() => {
- state.isUpdatingStarredValue = false;
- [unstarredDashboard, starredDashboard] = dashboardGitResponse;
- });
-
- describe('toggleStarredValue', () => {
- it('performs no changes if no dashboard is selected', () => {
- return testAction(toggleStarredValue, null, state, [], []);
- });
-
- it('performs no changes if already changing starred value', () => {
- state.selectedDashboard = unstarredDashboard;
- state.isUpdatingStarredValue = true;
- return testAction(toggleStarredValue, null, state, [], []);
- });
-
- it('stars dashboard if it is not starred', () => {
- state.selectedDashboard = unstarredDashboard;
- mock.onPost(unstarredDashboard.user_starred_path).reply(200);
-
- return testAction(toggleStarredValue, null, state, [
- { type: types.REQUEST_DASHBOARD_STARRING },
- {
- type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
- payload: {
- newStarredValue: true,
- selectedDashboard: unstarredDashboard,
- },
- },
- ]);
- });
-
- it('unstars dashboard if it is starred', () => {
- state.selectedDashboard = starredDashboard;
- mock.onPost(starredDashboard.user_starred_path).reply(200);
-
- return testAction(toggleStarredValue, null, state, [
- { type: types.REQUEST_DASHBOARD_STARRING },
- { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
- ]);
- });
- });
- });
-
- describe('Set initial state', () => {
- it('should commit SET_INITIAL_STATE mutation', done => {
- testAction(
- setInitialState,
- {
- currentDashboard: '.gitlab/dashboards/dashboard.yml',
- deploymentsEndpoint: 'deployments.json',
- },
- state,
- [
- {
- type: types.SET_INITIAL_STATE,
- payload: {
- currentDashboard: '.gitlab/dashboards/dashboard.yml',
- deploymentsEndpoint: 'deployments.json',
- },
- },
- ],
- [],
- done,
- );
- });
- });
- describe('Set empty states', () => {
- it('should commit SET_METRICS_ENDPOINT mutation', done => {
- testAction(
- setGettingStartedEmptyState,
- null,
- state,
[
- {
- type: types.SET_GETTING_STARTED_EMPTY_STATE,
- },
+ { type: 'fetchEnvironmentsData' },
+ { type: 'fetchDashboard' },
+ { type: 'fetchAnnotations' },
],
- [],
- done,
- );
+ ).then(() => {
+ window.gon = origGon;
+ });
});
});
- describe('updateVariablesAndFetchData', () => {
- it('should commit UPDATE_VARIABLES mutation and fetch data', done => {
- testAction(
- updateVariablesAndFetchData,
- { pod: 'POD' },
- state,
- [
- {
- type: types.UPDATE_VARIABLES,
- payload: { pod: 'POD' },
- },
- ],
- [
- {
- type: 'fetchDashboardData',
- },
- ],
- done,
- );
- });
- });
+ // Metrics dashboard
describe('fetchDashboard', () => {
let dispatch;
@@ -644,6 +289,7 @@ describe('Monitoring store actions', () => {
});
});
});
+
describe('receiveMetricsDashboardSuccess', () => {
let commit;
let dispatch;
@@ -708,6 +354,9 @@ describe('Monitoring store actions', () => {
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
});
});
+
+ // Metrics
+
describe('fetchDashboardData', () => {
let commit;
let dispatch;
@@ -810,6 +459,7 @@ describe('Monitoring store actions', () => {
done();
});
});
+
describe('fetchPrometheusMetric', () => {
const defaultQueryParams = {
start_time: '2019-08-06T12:40:02.184Z',
@@ -1013,6 +663,378 @@ describe('Monitoring store actions', () => {
});
});
+ // Deployments
+
+ describe('fetchDeploymentsData', () => {
+ it('dispatches receiveDeploymentsDataSuccess on success', () => {
+ state.deploymentsEndpoint = '/success';
+ mock.onGet(state.deploymentsEndpoint).reply(200, {
+ deployments: deploymentData,
+ });
+
+ return testAction(
+ fetchDeploymentsData,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
+ );
+ });
+ it('dispatches receiveDeploymentsDataFailure on error', () => {
+ state.deploymentsEndpoint = '/error';
+ mock.onGet(state.deploymentsEndpoint).reply(500);
+
+ return testAction(
+ fetchDeploymentsData,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDeploymentsDataFailure' }],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ },
+ );
+ });
+ });
+
+ // Environments
+
+ describe('fetchEnvironmentsData', () => {
+ beforeEach(() => {
+ state.projectPath = 'gitlab-org/gitlab-test';
+ });
+
+ it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
+ jest.spyOn(gqClient, 'mutate').mockReturnValue({
+ data: {
+ project: {
+ data: {
+ environments: [],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ filterEnvironments,
+ {},
+ state,
+ [
+ {
+ type: 'SET_ENVIRONMENTS_FILTER',
+ payload: {},
+ },
+ ],
+ [
+ {
+ type: 'fetchEnvironmentsData',
+ },
+ ],
+ );
+ });
+
+ it('fetch environments data call takes in search param', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const searchTerm = 'Something';
+ const mutationVariables = {
+ mutation: getEnvironments,
+ variables: {
+ projectPath: state.projectPath,
+ search: searchTerm,
+ states: [ENVIRONMENT_AVAILABLE_STATE],
+ },
+ };
+ state.environmentsSearchTerm = searchTerm;
+ mockMutate.mockResolvedValue({});
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestEnvironmentsData' },
+ { type: 'receiveEnvironmentsDataSuccess', payload: [] },
+ ],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveEnvironmentsDataSuccess on success', () => {
+ jest.spyOn(gqClient, 'mutate').mockResolvedValue({
+ data: {
+ project: {
+ data: {
+ environments: environmentData,
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestEnvironmentsData' },
+ {
+ type: 'receiveEnvironmentsDataSuccess',
+ payload: parseEnvironmentsResponse(environmentData, state.projectPath),
+ },
+ ],
+ );
+ });
+
+ it('dispatches receiveEnvironmentsDataFailure on error', () => {
+ jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
+ );
+ });
+ });
+
+ describe('fetchAnnotations', () => {
+ beforeEach(() => {
+ state.timeRange = {
+ start: '2020-04-15T12:54:32.137Z',
+ end: '2020-08-15T12:54:32.137Z',
+ };
+ state.projectPath = 'gitlab-org/gitlab-test';
+ state.currentEnvironmentName = 'production';
+ state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
+ // testAction doesn't have access to getters. The state is passed in as getters
+ // instead of the actual getters inside the testAction method implementation.
+ // All methods downstream that needs access to getters will throw and error.
+ // For that reason, the result of the getter is set as a state variable.
+ state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
+ });
+
+ it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const mutationVariables = {
+ mutation: getAnnotations,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ startingFrom: state.timeRange.start,
+ },
+ };
+ const parsedResponse = parseAnnotationsResponse(annotationsData);
+
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ environments: {
+ nodes: [
+ {
+ metricsDashboard: {
+ annotations: {
+ nodes: parsedResponse,
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchAnnotations,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const mutationVariables = {
+ mutation: getAnnotations,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ startingFrom: state.timeRange.start,
+ },
+ };
+
+ mockMutate.mockRejectedValue({});
+
+ return testAction(
+ fetchAnnotations,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAnnotationsFailure' }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+ });
+
+ describe('fetchDashboardValidationWarnings', () => {
+ let mockMutate;
+ let mutationVariables;
+
+ beforeEach(() => {
+ state.projectPath = 'gitlab-org/gitlab-test';
+ state.currentEnvironmentName = 'production';
+ state.currentDashboard = '.gitlab/dashboards/dashboard_with_warnings.yml';
+
+ mockMutate = jest.spyOn(gqClient, 'mutate');
+ mutationVariables = {
+ mutation: getDashboardValidationWarnings,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ },
+ };
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with true payload when there are warnings', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/29',
+ environments: {
+ nodes: [
+ {
+ name: 'production',
+ metricsDashboard: {
+ path: '.gitlab/dashboards/dashboard_errors_test.yml',
+ schemaValidationWarnings: ["unit: can't be blank"],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: true }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with false payload when there are no warnings', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/29',
+ environments: {
+ nodes: [
+ {
+ name: 'production',
+ metricsDashboard: {
+ path: '.gitlab/dashboards/dashboard_errors_test.yml',
+ schemaValidationWarnings: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
+ mockMutate.mockRejectedValue({});
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsFailure' }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+ });
+
+ // Dashboard manipulation
+
+ describe('toggleStarredValue', () => {
+ let unstarredDashboard;
+ let starredDashboard;
+
+ beforeEach(() => {
+ state.isUpdatingStarredValue = false;
+ [unstarredDashboard, starredDashboard] = dashboardGitResponse;
+ });
+
+ it('performs no changes if no dashboard is selected', () => {
+ return testAction(toggleStarredValue, null, state, [], []);
+ });
+
+ it('performs no changes if already changing starred value', () => {
+ state.selectedDashboard = unstarredDashboard;
+ state.isUpdatingStarredValue = true;
+ return testAction(toggleStarredValue, null, state, [], []);
+ });
+
+ it('stars dashboard if it is not starred', () => {
+ state.selectedDashboard = unstarredDashboard;
+ mock.onPost(unstarredDashboard.user_starred_path).reply(200);
+
+ return testAction(toggleStarredValue, null, state, [
+ { type: types.REQUEST_DASHBOARD_STARRING },
+ {
+ type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
+ payload: {
+ newStarredValue: true,
+ selectedDashboard: unstarredDashboard,
+ },
+ },
+ ]);
+ });
+
+ it('unstars dashboard if it is starred', () => {
+ state.selectedDashboard = starredDashboard;
+ mock.onPost(starredDashboard.user_starred_path).reply(200);
+
+ return testAction(toggleStarredValue, null, state, [
+ { type: types.REQUEST_DASHBOARD_STARRING },
+ { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
+ ]);
+ });
+ });
+
describe('duplicateSystemDashboard', () => {
beforeEach(() => {
state.dashboardsEndpoint = '/dashboards.json';
@@ -1091,29 +1113,26 @@ describe('Monitoring store actions', () => {
});
});
- describe('setExpandedPanel', () => {
- it('Sets a panel as expanded', () => {
- const group = 'group_1';
- const panel = { title: 'A Panel' };
-
- return testAction(
- setExpandedPanel,
- { group, panel },
- state,
- [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
- [],
- );
- });
- });
+ // Variables manipulation
- describe('clearExpandedPanel', () => {
- it('Clears a panel as expanded', () => {
- return testAction(
- clearExpandedPanel,
- undefined,
+ describe('updateVariablesAndFetchData', () => {
+ it('should commit UPDATE_VARIABLES mutation and fetch data', done => {
+ testAction(
+ updateVariablesAndFetchData,
+ { pod: 'POD' },
state,
- [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
- [],
+ [
+ {
+ type: types.UPDATE_VARIABLES,
+ payload: { pod: 'POD' },
+ },
+ ],
+ [
+ {
+ type: 'fetchDashboardData',
+ },
+ ],
+ done,
);
});
});
diff --git a/spec/frontend/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js
index 033bd5ccb73..bb069fdc2c8 100644
--- a/spec/frontend/pipelines/blank_state_spec.js
+++ b/spec/frontend/pipelines/blank_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import component from '~/pipelines/components/blank_state.vue';
+import component from '~/pipelines/components/pipelines_list/blank_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Blank State', () => {
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index bdc807fcbfe..add7b56845e 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -2,7 +2,7 @@ import Api from '~/api';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import PipelinesFilteredSearch from '~/pipelines/components/pipelines_filtered_search.vue';
+import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
import { users, mockSearch, branches, tags } from '../mock_data';
import { GlFilteredSearch } from '@gitlab/ui';
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index f12950b8fce..79356664834 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import emptyStateComp from '~/pipelines/components/empty_state.vue';
+import emptyStateComp from '~/pipelines/components/pipelines_list/empty_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Empty State', () => {
diff --git a/spec/frontend/pipelines/nav_controls_spec.js b/spec/frontend/pipelines/nav_controls_spec.js
index 6d28da0ea2a..139d53881c8 100644
--- a/spec/frontend/pipelines/nav_controls_spec.js
+++ b/spec/frontend/pipelines/nav_controls_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import navControlsComp from '~/pipelines/components/nav_controls.vue';
+import navControlsComp from '~/pipelines/components/pipelines_list/nav_controls.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Nav Controls', () => {
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index a8eec274487..6fd9a143d82 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue';
+import pipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
describe('Pipelines Triggerer', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 70b94f2c8e1..ef0e2387c0e 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import { trimText } from 'helpers/text_helper';
import { shallowMount } from '@vue/test-utils';
-import PipelineUrlComponent from '~/pipelines/components/pipeline_url.vue';
+import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
$.fn.popover = () => {};
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index 5e8d21660de..aef54d94974 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
-import PipelinesActions from '~/pipelines/components/pipelines_actions.vue';
+import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
import { GlDeprecatedButton } from '@gitlab/ui';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index a93cc8a62ab..512205c3fc3 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import PipelineArtifacts from '~/pipelines/components/pipelines_artifacts.vue';
+import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
import { GlLink } from '@gitlab/ui';
describe('Pipelines Artifacts dropdown', () => {
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 0eeaef01a2d..1bd630507aa 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
-import PipelinesComponent from '~/pipelines/components/pipelines.vue';
+import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
diff --git a/spec/frontend/pipelines/pipelines_table_row_spec.js b/spec/frontend/pipelines/pipelines_table_row_spec.js
index 3d564c8758c..9901f476f1b 100644
--- a/spec/frontend/pipelines/pipelines_table_row_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_row_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import PipelinesTableRowComponent from '~/pipelines/components/pipelines_table_row.vue';
+import PipelinesTableRowComponent from '~/pipelines/components/pipelines_list/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index b0ab250dd16..c7d104bbde8 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import PipelinesTable from '~/pipelines/components/pipelines_table.vue';
+import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
describe('Pipelines Table', () => {
let pipeline;
diff --git a/spec/frontend/pipelines/stage_spec.js b/spec/frontend/pipelines/stage_spec.js
index 6aa041bcb7f..547f8994ca5 100644
--- a/spec/frontend/pipelines/stage_spec.js
+++ b/spec/frontend/pipelines/stage_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import StageComponent from '~/pipelines/components/stage.vue';
+import StageComponent from '~/pipelines/components/pipelines_list/stage.vue';
import eventHub from '~/pipelines/event_hub';
import { stageReply } from './mock_data';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 1bd16182d47..04934fb93b0 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import TimeAgo from '~/pipelines/components/time_ago.vue';
+import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
describe('Timeago component', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
index 1a85221581e..650dd8a1def 100644
--- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineBranchNameToken from '~/pipelines/components/tokens/pipeline_branch_name_token.vue';
+import PipelineBranchNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue';
import { branches, mockBranchesAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index ee3694868a5..096e4cd97f6 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -1,6 +1,6 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineStatusToken from '~/pipelines/components/tokens/pipeline_status_token.vue';
+import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue';
describe('Pipeline Status Token', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
index 9fecc9412b7..15b283dc2ff 100644
--- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineTagNameToken from '~/pipelines/components/tokens/pipeline_tag_name_token.vue';
+import PipelineTagNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue';
import { tags, mockTagsAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
index 98de4f40c51..0b5cf2e202b 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineTriggerAuthorToken from '~/pipelines/components/tokens/pipeline_trigger_author_token.vue';
+import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
import { users } from '../mock_data';
describe('Pipeline Trigger Author Token', () => {
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
index faa32131fab..eb18d5c26e5 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
@@ -4,7 +4,7 @@ import {
removeCustomEventListener,
addImage,
getMarkdown,
-} from '~/vue_shared/components/rich_content_editor/editor_service';
+} from '~/vue_shared/components/rich_content_editor/services/editor_service';
describe('Editor Service', () => {
const mockInstance = {
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
index 0db10389df4..6091c757ed1 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
@@ -13,10 +13,10 @@ import {
addCustomEventListener,
removeCustomEventListener,
addImage,
-} from '~/vue_shared/components/rich_content_editor/editor_service';
+} from '~/vue_shared/components/rich_content_editor/services/editor_service';
-jest.mock('~/vue_shared/components/rich_content_editor/editor_service', () => ({
- ...jest.requireActual('~/vue_shared/components/rich_content_editor/editor_service'),
+jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service', () => ({
+ ...jest.requireActual('~/vue_shared/components/rich_content_editor/services/editor_service'),
addCustomEventListener: jest.fn(),
removeCustomEventListener: jest.fn(),
addImage: jest.fn(),
diff --git a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
index bbcdc0b879f..4416dbd014a 100644
--- a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
@@ -11,8 +11,8 @@ import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import { mockApiEndpoint } from '../mock_data';
-import { metricsDashboardPayload, dashboardProps } from '../fixture_data';
+import { mockApiEndpoint, propsData } from '../mock_data';
+import { metricsDashboardPayload } from '../fixture_data';
import { setupStoreWithData } from '../store_utils';
const localVue = createLocalVue();
@@ -56,7 +56,7 @@ describe('Dashboard', () => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
- ...dashboardProps,
+ ...propsData,
hasMetrics: true,
showPanels: true,
},
diff --git a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb
new file mode 100644
index 00000000000..c0dc14a3f4f
--- /dev/null
+++ b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::DynamicModelHelpers do
+ describe '#define_batchable_model' do
+ subject { including_class.new.define_batchable_model(table_name) }
+
+ let(:including_class) { Class.new.include(described_class) }
+ let(:table_name) { 'projects' }
+
+ it 'is an ActiveRecord model' do
+ expect(subject.ancestors).to include(ActiveRecord::Base)
+ end
+
+ it 'includes EachBatch' do
+ expect(subject.included_modules).to include(EachBatch)
+ end
+
+ it 'has the correct table name' do
+ expect(subject.table_name).to eq(table_name)
+ end
+
+ it 'has the inheritance type column disable' do
+ expect(subject.inheritance_column).to eq('_type_disabled')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index bed444ee7c7..1f725687bdc 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1215,166 +1215,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#bulk_queue_background_migration_jobs_by_range' do
- context 'when the model has an ID column' do
- let!(:id1) { create(:user).id }
- let!(:id2) { create(:user).id }
- let!(:id3) { create(:user).id }
-
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
- context 'with enough rows to bulk queue jobs more than once' do
- before do
- stub_const('Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
- end
-
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- end
- end
-
- it 'queues jobs in groups of buffer size 1' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]])
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]])
-
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
- end
- end
-
- context 'with not enough rows to bulk queue jobs more than once' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- end
- end
-
- it 'queues jobs in bulk all at once (big buffer size)' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]],
- ['FooJob', [id3, id3]]])
-
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
- end
- end
-
- context 'without specifying batch_size' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- end
- end
- end
- end
-
- context "when the model doesn't have an ID column" do
- it 'raises error (for now)' do
- expect do
- model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
- end.to raise_error(StandardError, /does not have an ID/)
- end
- end
- end
-
- describe '#queue_background_migration_jobs_by_range_at_intervals' do
- context 'when the model has an ID column' do
- let!(:id1) { create(:user).id }
- let!(:id2) { create(:user).id }
- let!(:id3) { create(:user).id }
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
- it 'returns the final expected delay' do
- Sidekiq::Testing.fake! do
- final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
-
- expect(final_delay.to_f).to eq(20.minutes.to_f)
- end
- end
-
- it 'returns zero when nothing gets queued' do
- Sidekiq::Testing.fake! do
- final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
-
- expect(final_delay).to eq(0)
- end
- end
-
- context 'with batch_size option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
- end
- end
- end
-
- context 'without batch_size option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- end
- end
- end
-
- context 'with other_job_arguments option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- end
- end
- end
-
- context 'with initial_delay option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f)
- end
- end
- end
- end
-
- context "when the model doesn't have an ID column" do
- it 'raises error (for now)' do
- expect do
- model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
- end.to raise_error(StandardError, /does not have an ID/)
- end
- end
- end
-
describe '#change_column_type_using_background_migration' do
let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) }
@@ -1485,26 +1325,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#perform_background_migration_inline?' do
- it 'returns true in a test environment' do
- stub_rails_env('test')
-
- expect(model.perform_background_migration_inline?).to eq(true)
- end
-
- it 'returns true in a development environment' do
- stub_rails_env('development')
-
- expect(model.perform_background_migration_inline?).to eq(true)
- end
-
- it 'returns false in a production environment' do
- stub_rails_env('production')
-
- expect(model.perform_background_migration_inline?).to eq(false)
- end
- end
-
describe '#index_exists_by_name?' do
it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute(
@@ -1973,62 +1793,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#migrate_async' do
- it 'calls BackgroundMigrationWorker.perform_async' do
- expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
-
- model.migrate_async("Class", "hello", "world")
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.migrate_async('Class', 'hello', 'world')
- end
- end
-
- describe '#migrate_in' do
- it 'calls BackgroundMigrationWorker.perform_in' do
- expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
-
- model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
- end
- end
-
- describe '#bulk_migrate_async' do
- it 'calls BackgroundMigrationWorker.bulk_perform_async' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
-
- model.bulk_migrate_async([%w(Class hello world)])
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.bulk_migrate_async([%w(Class hello world)])
- end
- end
-
- describe '#bulk_migrate_in' do
- it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
-
- model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
- end
- end
-
describe '#check_constraint_name' do
it 'returns a valid constraint name' do
name = model.check_constraint_name(:this_is_a_very_long_table_name,
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
new file mode 100644
index 00000000000..1b1ac0cb387
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -0,0 +1,245 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
+ let(:model) do
+ ActiveRecord::Migration.new.extend(described_class)
+ end
+
+ describe '#bulk_queue_background_migration_jobs_by_range' do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ context 'with enough rows to bulk queue jobs more than once' do
+ before do
+ stub_const('Gitlab::Database::Migrations::BackgroundMigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
+ end
+
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in groups of buffer size 1' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]])
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'with not enough rows to bulk queue jobs more than once' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in bulk all at once (big buffer size)' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]],
+ ['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'without specifying batch_size' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#queue_background_migration_jobs_by_range_at_intervals' do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ it 'returns the final expected delay' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
+
+ expect(final_delay.to_f).to eq(20.minutes.to_f)
+ end
+ end
+
+ it 'returns zero when nothing gets queued' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
+
+ expect(final_delay).to eq(0)
+ end
+ end
+
+ context 'with batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'without batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with other_job_arguments option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with initial_delay option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#perform_background_migration_inline?' do
+ it 'returns true in a test environment' do
+ stub_rails_env('test')
+
+ expect(model.perform_background_migration_inline?).to eq(true)
+ end
+
+ it 'returns true in a development environment' do
+ stub_rails_env('development')
+
+ expect(model.perform_background_migration_inline?).to eq(true)
+ end
+
+ it 'returns false in a production environment' do
+ stub_rails_env('production')
+
+ expect(model.perform_background_migration_inline?).to eq(false)
+ end
+ end
+
+ describe '#migrate_async' do
+ it 'calls BackgroundMigrationWorker.perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
+
+ model.migrate_async("Class", "hello", "world")
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_async('Class', 'hello', 'world')
+ end
+ end
+
+ describe '#migrate_in' do
+ it 'calls BackgroundMigrationWorker.perform_in' do
+ expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+ end
+
+ describe '#bulk_migrate_async' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+ end
+
+ describe '#bulk_migrate_in' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb
new file mode 100644
index 00000000000..2016c31b891
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable, '#perform' do
+ subject { described_class.new }
+
+ let(:source_table) { '_test_partitioning_backfills' }
+ let(:destination_table) { "#{source_table}_part" }
+ let(:unique_key) { 'id' }
+
+ before do
+ allow(subject).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'when the destination table exists' do
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{source_table} (
+ id serial NOT NULL PRIMARY KEY,
+ col1 int NOT NULL,
+ col2 text NOT NULL,
+ created_at timestamptz NOT NULL
+ )
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table} (
+ id serial NOT NULL,
+ col1 int NOT NULL,
+ col2 text NOT NULL,
+ created_at timestamptz NOT NULL,
+ PRIMARY KEY (id, created_at)
+ ) PARTITION BY RANGE (created_at)
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table}_202001 PARTITION OF #{destination_table}
+ FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table}_202002 PARTITION OF #{destination_table}
+ FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
+ SQL
+
+ source_model.table_name = source_table
+ destination_model.table_name = destination_table
+
+ stub_const("#{described_class}::SUB_BATCH_SIZE", 2)
+ stub_const("#{described_class}::PAUSE_SECONDS", pause_seconds)
+
+ allow(subject).to receive(:sleep)
+ end
+
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:source_model) { Class.new(ActiveRecord::Base) }
+ let(:destination_model) { Class.new(ActiveRecord::Base) }
+ let(:timestamp) { Time.utc(2020, 1, 2).round }
+ let(:pause_seconds) { 1 }
+
+ let!(:source1) { create_source_record(timestamp) }
+ let!(:source2) { create_source_record(timestamp + 1.day) }
+ let!(:source3) { create_source_record(timestamp + 1.month) }
+
+ it 'copies data into the destination table idempotently' do
+ expect(destination_model.count).to eq(0)
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(3)
+
+ source_model.find_each do |source_record|
+ destination_record = destination_model.find_by_id(source_record.id)
+
+ expect(destination_record.attributes).to eq(source_record.attributes)
+ end
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(3)
+ end
+
+ it 'breaks the assigned batch into smaller batches' do
+ expect_next_instance_of(described_class::BulkCopy) do |bulk_copy|
+ expect(bulk_copy).to receive(:copy_between).with(source1.id, source2.id)
+ expect(bulk_copy).to receive(:copy_between).with(source3.id, source3.id)
+ end
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end
+
+ it 'pauses after copying each sub-batch' do
+ expect(subject).to receive(:sleep).with(pause_seconds).twice
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end
+
+ context 'when the feature flag is disabled' do
+ let(:mock_connection) { double('connection') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ stub_feature_flags(backfill_partitioned_audit_events: false)
+ end
+
+ it 'exits without attempting to copy data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(0)
+ end
+ end
+
+ context 'when the job is run within an explicit transaction block' do
+ let(:mock_connection) { double('connection') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ allow(subject).to receive(:transaction_open?).and_return(true)
+ end
+
+ it 'raises an error before copying data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ expect do
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end.to raise_error(/Aborting job to backfill partitioned #{source_table}/)
+
+ expect(destination_model.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when the destination table does not exist' do
+ let(:mock_connection) { double('connection') }
+ let(:mock_logger) { double('logger') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ allow(subject).to receive(:logger).and_return(mock_logger)
+
+ expect(mock_connection).to receive(:table_exists?).with(destination_table).and_return(false)
+ allow(mock_logger).to receive(:warn)
+ end
+
+ it 'exits without attempting to copy data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end
+
+ it 'logs a warning message that the job was skipped' do
+ expect(mock_logger).to receive(:warn).with(/#{destination_table} does not exist/)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end
+ end
+
+ def create_source_record(timestamp)
+ source_model.create!(col1: 123, col2: 'original value', created_at: timestamp)
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 586b57d2002..c78c143f2f8 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
allow(migration).to receive(:partitioned_table_name).and_return(partitioned_table)
allow(migration).to receive(:sync_function_name).and_return(function_name)
allow(migration).to receive(:sync_trigger_name).and_return(trigger_name)
- allow(migration).to receive(:assert_table_is_whitelisted)
+ allow(migration).to receive(:assert_table_is_allowed)
end
describe '#partition_table_by_date' do
@@ -33,15 +33,19 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
let(:old_primary_key) { 'id' }
let(:new_primary_key) { [old_primary_key, partition_column] }
- context 'when the table is not whitelisted' do
- let(:template_table) { :this_table_is_not_whitelisted }
+ before do
+ allow(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals)
+ end
+
+ context 'when the table is not allowed' do
+ let(:template_table) { :this_table_is_not_allowed }
it 'raises an error' do
- expect(migration).to receive(:assert_table_is_whitelisted).with(template_table).and_call_original
+ expect(migration).to receive(:assert_table_is_allowed).with(template_table).and_call_original
expect do
migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
- end.to raise_error(/#{template_table} is not whitelisted for use/)
+ end.to raise_error(/#{template_table} is not allowed for use/)
end
end
@@ -237,6 +241,36 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
expect(model.find(second_todo.id).attributes).to eq(second_todo.attributes)
end
end
+
+ describe 'copying historic data to the partitioned table' do
+ let(:template_table) { 'todos' }
+ let(:migration_class) { '::Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable' }
+ let(:sub_batch_size) { described_class::SUB_BATCH_SIZE }
+ let(:pause_seconds) { described_class::PAUSE_SECONDS }
+ let!(:first_id) { create(:todo).id }
+ let!(:second_id) { create(:todo).id }
+ let!(:third_id) { create(:todo).id }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ expect(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original
+ end
+
+ it 'enqueues jobs to copy each batch of data' do
+ Sidekiq::Testing.fake! do
+ migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+
+ first_job_arguments = [first_id, second_id, template_table, partitioned_table, 'id']
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([migration_class, first_job_arguments])
+
+ second_job_arguments = [third_id, third_id, template_table, partitioned_table, 'id']
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([migration_class, second_job_arguments])
+ end
+ end
+ end
end
describe '#drop_partitioned_table_for' do
@@ -244,15 +278,15 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
%w[000000 201912 202001 202002].map { |suffix| "#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
end
- context 'when the table is not whitelisted' do
- let(:template_table) { :this_table_is_not_whitelisted }
+ context 'when the table is not allowed' do
+ let(:template_table) { :this_table_is_not_allowed }
it 'raises an error' do
- expect(migration).to receive(:assert_table_is_whitelisted).with(template_table).and_call_original
+ expect(migration).to receive(:assert_table_is_allowed).with(template_table).and_call_original
expect do
migration.drop_partitioned_table_for template_table
- end.to raise_error(/#{template_table} is not whitelisted for use/)
+ end.to raise_error(/#{template_table} is not allowed for use/)
end
end
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index 013c634aa7b..6dbb0cd651f 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -7,12 +7,6 @@ module MetricsDashboardHelpers
create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
end
- def project_with_dashboard_namespace(dashboard_path, dashboard_yml = nil)
- dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
-
- create(:project, :custom_repo, namespace: namespace, path: 'monitor-project', files: { dashboard_path => dashboard_yml })
- end
-
def delete_project_dashboard(project, user, dashboard_path)
project.repository.delete_file(
user,