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-10-30 21:08:56 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-30 21:08:56 +0300
commit98d7cc758fb73239fb957c297446c811ab4150d9 (patch)
tree227a5e8efe35d2ac158e762397609a3f1754b224
parent038366a0932c5f88019cc3db85382f26af3933e7 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue129
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_job_details_container.vue102
-rw-r--r--app/assets/javascripts/jobs/store/getters.js2
-rw-r--r--app/assets/javascripts/pages/search/show/index.js2
-rw-r--r--app/assets/javascripts/pages/search/show/search.js34
-rw-r--r--app/assets/javascripts/search/group_filter/components/group_filter.vue124
-rw-r--r--app/assets/javascripts/search/group_filter/constants.js10
-rw-r--r--app/assets/javascripts/search/group_filter/index.js28
-rw-r--r--app/assets/javascripts/search/index.js2
-rw-r--r--app/assets/javascripts/search/store/actions.js16
-rw-r--r--app/assets/javascripts/search/store/index.js4
-rw-r--r--app/assets/javascripts/search/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/search/store/mutations.js15
-rw-r--r--app/assets/javascripts/search/store/state.js2
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_area.vue12
-rw-r--r--app/assets/javascripts/static_site_editor/pages/home.vue1
-rw-r--r--app/assets/javascripts/static_site_editor/services/renderers/render_image.js30
-rw-r--r--app/assets/stylesheets/pages/search.scss3
-rw-r--r--app/finders/user_groups_counter.rb30
-rw-r--r--app/graphql/mutations/todos/create.rb39
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb9
-rw-r--r--app/graphql/resolvers/users/group_count_resolver.rb25
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/graphql/types/projects/namespace_project_sort_enum.rb1
-rw-r--r--app/graphql/types/user_type.rb4
-rw-r--r--app/models/ci/build_trace_chunk.rb2
-rw-r--r--app/models/concerns/todoable.rb10
-rw-r--r--app/models/design_management/design.rb1
-rw-r--r--app/models/group.rb11
-rw-r--r--app/models/issue.rb1
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/policies/issue_policy.rb4
-rw-r--r--app/policies/merge_request_policy.rb4
-rw-r--r--app/policies/user_policy.rb1
-rw-r--r--app/services/bulk_create_integration_service.rb6
-rw-r--r--app/views/admin/groups/show.html.haml10
-rw-r--r--app/views/admin/projects/show.html.haml17
-rw-r--r--app/views/groups/group_members/index.html.haml19
-rw-r--r--app/views/projects/project_members/_team.html.haml8
-rw-r--r--app/views/projects/project_members/index.html.haml5
-rw-r--r--app/views/search/_filter.html.haml15
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml2
-rw-r--r--app/views/shared/members/_member.html.haml16
-rw-r--r--app/views/shared/members/_requests.html.haml9
-rw-r--r--changelogs/unreleased/267963-related-merge-requests-icon-in-issues-list-is-rendered-incorrectly.yml5
-rw-r--r--changelogs/unreleased/diff-file-header-clipboard-btn-overflow.yml5
-rw-r--r--changelogs/unreleased/issue_233479-add-graphql-create-todo-mutation.yml5
-rw-r--r--config/feature_flags/development/ci_trace_new_fog_store.yml2
-rw-r--r--config/feature_flags/development/user_group_counts.yml7
-rw-r--r--doc/administration/pages/index.md28
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql51
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json159
-rw-r--r--doc/api/graphql/reference/index.md12
-rw-r--r--doc/api/scim.md22
-rw-r--r--doc/ci/cloud_deployment/index.md4
-rw-r--r--doc/ci/environments/img/protected_access_group_v13_6.pngbin0 -> 47519 bytes
-rw-r--r--doc/ci/environments/protected_environments.md55
-rw-r--r--doc/ci/merge_request_pipelines/index.md6
-rw-r--r--doc/development/adding_service_component.md6
-rw-r--r--doc/development/api_styleguide.md6
-rw-r--r--doc/development/application_limits.md6
-rw-r--r--doc/development/application_secrets.md6
-rw-r--r--doc/development/approval_rules.md6
-rw-r--r--doc/development/architecture.md6
-rw-r--r--doc/development/chaos_endpoints.md6
-rw-r--r--doc/development/code_comments.md6
-rw-r--r--doc/development/code_intelligence/index.md6
-rw-r--r--doc/development/creating_enums.md6
-rw-r--r--doc/development/dangerbot.md6
-rw-r--r--doc/development/database/setting_multiple_values.md6
-rw-r--r--doc/development/deleting_migrations.md6
-rw-r--r--doc/development/deprecation_guidelines/index.md6
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md3
-rw-r--r--doc/development/documentation/site_architecture/release_process.md6
-rw-r--r--doc/development/ee_features.md6
-rw-r--r--doc/development/emails.md6
-rw-r--r--doc/development/experiment_guide/index.md6
-rw-r--r--doc/development/fe_guide/accessibility.md6
-rw-r--r--doc/development/fe_guide/architecture.md6
-rw-r--r--doc/development/fe_guide/axios.md6
-rw-r--r--doc/development/fe_guide/dependencies.md6
-rw-r--r--doc/development/fe_guide/design_patterns.md6
-rw-r--r--doc/development/fe_guide/development_process.md6
-rw-r--r--doc/development/fe_guide/droplab/droplab.md6
-rw-r--r--doc/development/fe_guide/droplab/plugins/ajax.md6
-rw-r--r--doc/development/fe_guide/droplab/plugins/filter.md6
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md6
-rw-r--r--doc/development/fe_guide/editor_lite.md218
-rw-r--r--doc/development/fe_guide/emojis.md6
-rw-r--r--doc/development/fe_guide/frontend_faq.md6
-rw-r--r--doc/development/fe_guide/icons.md6
-rw-r--r--doc/development/fe_guide/img/editor_lite_create_ext.pngbin0 -> 806747 bytes
-rw-r--r--doc/development/fe_guide/img/editor_lite_loading.pngbin0 -> 95843 bytes
-rw-r--r--doc/development/fe_guide/index.md6
-rw-r--r--doc/development/fe_guide/keyboard_shortcuts.md6
-rw-r--r--doc/development/fe_guide/performance.md6
-rw-r--r--doc/development/fe_guide/principles.md6
-rw-r--r--doc/development/fe_guide/security.md6
-rw-r--r--doc/development/fe_guide/style/html.md6
-rw-r--r--doc/development/fe_guide/style/index.md6
-rw-r--r--doc/development/fe_guide/style/javascript.md3
-rw-r--r--doc/development/fe_guide/style/scss.md3
-rw-r--r--doc/development/fe_guide/style/vue.md199
-rw-r--r--doc/development/fe_guide/vue.md11
-rw-r--r--doc/development/fe_guide/vue3_migration.md6
-rw-r--r--doc/development/fe_guide/vuex.md6
-rw-r--r--doc/development/features_inside_dot_gitlab.md6
-rw-r--r--doc/development/file_storage.md6
-rw-r--r--doc/development/foreign_keys.md6
-rw-r--r--doc/development/gemfile.md6
-rw-r--r--doc/development/go_guide/dependencies.md6
-rw-r--r--doc/development/gotchas.md6
-rw-r--r--doc/development/graphql_guide/index.md6
-rw-r--r--doc/development/graphql_guide/pagination.md6
-rw-r--r--doc/development/hash_indexes.md6
-rw-r--r--doc/development/i18n/externalization.md6
-rw-r--r--doc/development/i18n/index.md6
-rw-r--r--doc/development/i18n/merging_translations.md6
-rw-r--r--doc/development/i18n/proofreader.md6
-rw-r--r--doc/development/i18n/translation.md6
-rw-r--r--doc/development/import_export.md6
-rw-r--r--doc/development/import_project.md6
-rw-r--r--doc/development/insert_into_tables_in_batches.md3
-rw-r--r--doc/development/integrations/jira_connect.md6
-rw-r--r--doc/development/interacting_components.md6
-rw-r--r--doc/development/iterating_tables_in_batches.md6
-rw-r--r--doc/development/licensed_feature_availability.md6
-rw-r--r--doc/development/licensing.md6
-rw-r--r--doc/development/mass_insert.md6
-rw-r--r--doc/development/merge_request_performance_guidelines.md6
-rw-r--r--doc/development/migration_style_guide.md6
-rw-r--r--doc/development/module_with_instance_variables.md6
-rw-r--r--doc/development/namespaces_storage_statistics.md6
-rw-r--r--doc/development/new_fe_guide/dependencies.md6
-rw-r--r--doc/development/new_fe_guide/development/accessibility.md6
-rw-r--r--doc/development/new_fe_guide/development/components.md6
-rw-r--r--doc/development/new_fe_guide/development/index.md6
-rw-r--r--doc/development/new_fe_guide/development/performance.md6
-rw-r--r--doc/development/new_fe_guide/index.md6
-rw-r--r--doc/development/new_fe_guide/modules/dirty_submit.md6
-rw-r--r--doc/development/new_fe_guide/modules/index.md6
-rw-r--r--doc/development/new_fe_guide/modules/widget_extensions.md6
-rw-r--r--doc/development/new_fe_guide/tips.md6
-rw-r--r--doc/development/newlines_styleguide.md6
-rw-r--r--doc/development/omnibus.md6
-rw-r--r--doc/development/ordering_table_columns.md6
-rw-r--r--doc/development/performance.md6
-rw-r--r--doc/development/permissions.md6
-rw-r--r--doc/development/policies.md6
-rw-r--r--doc/development/polling.md6
-rw-r--r--doc/development/polymorphic_associations.md6
-rw-r--r--doc/development/post_deployment_migrations.md6
-rw-r--r--doc/development/profiling.md6
-rw-r--r--doc/development/projections.md6
-rw-r--r--doc/development/pry_debugging.md6
-rw-r--r--doc/development/python_guide/index.md6
-rw-r--r--doc/development/query_count_limits.md6
-rw-r--r--doc/development/query_recorder.md6
-rw-r--r--doc/development/rails_initializers.md6
-rw-r--r--doc/development/rake_tasks.md6
-rw-r--r--doc/development/reactive_caching.md6
-rw-r--r--doc/development/redis.md6
-rw-r--r--doc/development/refactoring_guide/index.md6
-rw-r--r--doc/development/reference_processing.md3
-rw-r--r--doc/development/renaming_features.md6
-rw-r--r--doc/development/reusing_abstractions.md6
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md4
-rw-r--r--doc/development/routing.md6
-rw-r--r--doc/development/scalability.md6
-rw-r--r--doc/development/serializing_data.md6
-rw-r--r--doc/development/service_measurement.md6
-rw-r--r--doc/development/session.md6
-rw-r--r--doc/development/sha1_as_binary.md6
-rw-r--r--doc/development/shared_files.md6
-rw-r--r--doc/development/shell_commands.md6
-rw-r--r--doc/development/sidekiq_debugging.md9
-rw-r--r--doc/development/sidekiq_style_guide.md6
-rw-r--r--doc/development/single_table_inheritance.md6
-rw-r--r--doc/development/sql.md6
-rw-r--r--doc/development/testing_guide/end_to_end/dynamic_element_validation.md6
-rw-r--r--doc/development/testing_guide/end_to_end/environment_selection.md10
-rw-r--r--doc/development/testing_guide/end_to_end/feature_flags.md6
-rw-r--r--doc/development/testing_guide/end_to_end/flows.md6
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md6
-rw-r--r--doc/development/testing_guide/end_to_end/resources.md6
-rw-r--r--doc/development/testing_guide/end_to_end/rspec_metadata_tests.md6
-rw-r--r--doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md6
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md6
-rw-r--r--doc/development/testing_guide/flaky_tests.md6
-rw-r--r--doc/development/testing_guide/frontend_testing.md6
-rw-r--r--doc/development/testing_guide/index.md6
-rw-r--r--doc/development/testing_guide/smoke.md6
-rw-r--r--doc/development/testing_guide/testing_levels.md6
-rw-r--r--doc/development/testing_guide/testing_rake_tasks.md6
-rw-r--r--doc/development/understanding_explain_plans.md6
-rw-r--r--doc/development/uploads.md6
-rw-r--r--doc/development/utilities.md6
-rw-r--r--doc/development/verifying_database_capabilities.md6
-rw-r--r--doc/development/what_requires_downtime.md6
-rw-r--r--doc/development/windows.md3
-rw-r--r--doc/security/README.md3
-rw-r--r--doc/security/asset_proxy.md6
-rw-r--r--doc/security/crime_vulnerability.md3
-rw-r--r--doc/security/information_exclusivity.md3
-rw-r--r--doc/security/password_length_limits.md3
-rw-r--r--doc/security/password_storage.md3
-rw-r--r--doc/security/passwords_for_integrated_authentication_methods.md3
-rw-r--r--doc/security/project_import_decompressed_archive_size_limits.md3
-rw-r--r--doc/security/rack_attack.md3
-rw-r--r--doc/security/rate_limits.md3
-rw-r--r--doc/security/reset_user_password.md3
-rw-r--r--doc/security/unlock_user.md3
-rw-r--r--doc/security/webhooks.md3
-rw-r--r--doc/subscriptions/gitlab_com/index.md3
-rw-r--r--doc/subscriptions/index.md3
-rw-r--r--doc/subscriptions/self_managed/index.md3
-rw-r--r--doc/system_hooks/system_hooks.md3
-rw-r--r--doc/tools/email.md3
-rw-r--r--doc/update/README.md6
-rw-r--r--doc/update/mysql_to_postgresql.md6
-rw-r--r--doc/update/patch_versions.md3
-rw-r--r--doc/update/restore_after_failure.md6
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md3
-rw-r--r--doc/update/upgrading_from_source.md3
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md6
-rw-r--r--doc/user/group/repositories_analytics/index.md23
-rw-r--r--doc/user/instance/clusters/index.md6
-rw-r--r--doc/user/project/integrations/ewm.md6
-rw-r--r--doc/user/project/issues/csv_export.md6
-rw-r--r--doc/user/project/issues/csv_import.md6
-rw-r--r--doc/user/project/members/index.md6
-rw-r--r--doc/user/project/members/share_project_with_groups.md6
-rw-r--r--doc/user/project/merge_requests/versions.md3
-rw-r--r--doc/user/project/pages/lets_encrypt_for_gitlab_pages.md3
-rw-r--r--doc/user/project/web_ide/index.md4
-rw-r--r--doc/user/reserved_names.md6
-rw-r--r--doc/user/shortcuts.md3
-rw-r--r--doc/user/upgrade_email_bypass.md6
-rw-r--r--lib/api/project_snapshots.rb2
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/project_statistics.rb2
-rw-r--r--lib/api/project_templates.rb2
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/protected_tags.rb2
-rw-r--r--lib/api/release/links.rb2
-rw-r--r--lib/api/releases.rb2
-rw-r--r--lib/api/remote_mirrors.rb2
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/api/search.rb2
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--lib/api/snippets.rb2
-rw-r--r--lib/api/statistics.rb2
-rw-r--r--lib/api/submodules.rb2
-rw-r--r--lib/api/subscriptions.rb16
-rw-r--r--lib/api/suggestions.rb2
-rw-r--r--lib/api/system_hooks.rb2
-rw-r--r--lib/api/tags.rb12
-rw-r--r--lib/api/templates.rb2
-rw-r--r--lib/api/terraform/state.rb2
-rw-r--r--lib/api/terraform/state_version.rb2
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/unleash.rb2
-rw-r--r--lib/api/usage_data.rb2
-rw-r--r--lib/api/user_counts.rb2
-rw-r--r--lib/api/variables.rb2
-rw-r--r--lib/api/version.rb2
-rw-r--r--lib/api/wikis.rb2
-rw-r--r--lib/gitlab/middleware/handle_malformed_strings.rb9
-rw-r--r--locale/gitlab.pot10
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb15
-rw-r--r--spec/finders/user_groups_counter_spec.rb45
-rw-r--r--spec/frontend/jobs/components/sidebar_job_details_container_spec.js132
-rw-r--r--spec/frontend/jobs/components/sidebar_spec.js157
-rw-r--r--spec/frontend/search/dropdown_filter/components/dropdown_filter_spec.js2
-rw-r--r--spec/frontend/search/dropdown_filter/mock_data.js5
-rw-r--r--spec/frontend/search/group_filter/components/group_filter_spec.js172
-rw-r--r--spec/frontend/search/mock_data.js24
-rw-r--r--spec/frontend/search/store/actions_spec.js45
-rw-r--r--spec/frontend/search/store/mutations_spec.js38
-rw-r--r--spec/frontend/search_spec.js10
-rw-r--r--spec/frontend/static_site_editor/components/edit_area_spec.js2
-rw-r--r--spec/frontend/static_site_editor/mock_data.js1
-rw-r--r--spec/frontend/static_site_editor/services/renderers/render_image_spec.js63
-rw-r--r--spec/graphql/mutations/todos/create_spec.rb44
-rw-r--r--spec/graphql/resolvers/users/group_count_resolver_spec.rb62
-rw-r--r--spec/graphql/types/user_type_spec.rb1
-rw-r--r--spec/lib/api/every_api_endpoint_spec.rb11
-rw-r--r--spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb18
-rw-r--r--spec/models/group_spec.rb27
-rw-r--r--spec/policies/issue_policy_spec.rb7
-rw-r--r--spec/policies/merge_request_policy_spec.rb13
-rw-r--r--spec/policies/user_policy_spec.rb18
-rw-r--r--spec/requests/api/graphql/mutations/todos/create_spec.rb38
-rw-r--r--spec/services/bulk_create_integration_service_spec.rb66
-rw-r--r--spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb26
-rw-r--r--spec/views/search/_filter.html.haml_spec.rb2
300 files changed, 3314 insertions, 381 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index ea7112689c1..8f377f0000a 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -226,7 +226,7 @@ export default {
<a
ref="titleWrapper"
:v-once="!viewDiffsFileByFile"
- class="gl-mr-2 gl-text-decoration-none!"
+ class="gl-mr-2 gl-text-decoration-none! gl-text-truncate"
:href="titleLink"
@click="handleFileNameClick"
>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 8701e05a01f..e1372a5c05e 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -1,33 +1,29 @@
<script>
import { isEmpty } from 'lodash';
import { mapActions, mapState } from 'vuex';
-import { GlLink, GlButton, GlIcon } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { GlButton, GlIcon, GlLink } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
-import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
-import DetailRow from './sidebar_detail_row.vue';
import ArtifactsBlock from './artifacts_block.vue';
import TriggerBlock from './trigger_block.vue';
import CommitBlock from './commit_block.vue';
import StagesDropdown from './stages_dropdown.vue';
import JobsContainer from './jobs_container.vue';
+import SidebarJobDetailsContainer from './sidebar_job_details_container.vue';
export default {
name: 'JobSidebar',
components: {
ArtifactsBlock,
CommitBlock,
- DetailRow,
GlIcon,
TriggerBlock,
StagesDropdown,
JobsContainer,
GlLink,
GlButton,
+ SidebarJobDetailsContainer,
TooltipOnTruncate,
},
- mixins: [timeagoMixin],
props: {
artifactHelpUrl: {
type: String,
@@ -42,53 +38,12 @@ export default {
},
computed: {
...mapState(['job', 'stages', 'jobs', 'selectedStage']),
- coverage() {
- return `${this.job.coverage}%`;
- },
- duration() {
- return timeIntervalInWords(this.job.duration);
- },
- queued() {
- return timeIntervalInWords(this.job.queued);
- },
- runnerId() {
- return `${this.job.runner.description} (#${this.job.runner.id})`;
- },
retryButtonClass() {
let className = 'js-retry-button btn btn-retry';
className +=
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
return className;
},
- hasTimeout() {
- return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
- },
- timeout() {
- if (this.job.metadata == null) {
- return '';
- }
-
- let t = this.job.metadata.timeout_human_readable;
- if (this.job.metadata.timeout_source !== '') {
- t += sprintf(__(` (from %{timeoutSource})`), {
- timeoutSource: this.job.metadata.timeout_source,
- });
- }
-
- return t;
- },
- renderBlock() {
- return (
- this.job.duration ||
- this.job.finished_at ||
- this.job.erased_at ||
- this.job.queued ||
- this.hasTimeout ||
- this.job.runner ||
- this.job.coverage ||
- this.job.tags.length
- );
- },
hasArtifact() {
return !isEmpty(this.job.artifact);
},
@@ -96,16 +51,10 @@ export default {
return !isEmpty(this.job.trigger);
},
hasStages() {
- return (
- (this.job &&
- this.job.pipeline &&
- this.job.pipeline.stages &&
- this.job.pipeline.stages.length > 0) ||
- false
- );
+ return this.job?.pipeline?.stages?.length > 0;
},
commit() {
- return this.job.pipeline && this.job.pipeline.commit ? this.job.pipeline.commit : {};
+ return this.job?.pipeline?.commit || {};
},
},
methods: {
@@ -131,22 +80,22 @@ export default {
data-method="post"
data-qa-selector="retry_button"
rel="nofollow"
- >{{ __('Retry') }}</gl-link
- >
+ >{{ __('Retry') }}
+ </gl-link>
<gl-link
v-if="job.cancel_path"
:href="job.cancel_path"
class="js-cancel-job btn btn-default"
data-method="post"
rel="nofollow"
- >{{ __('Cancel') }}</gl-link
- >
+ >{{ __('Cancel') }}
+ </gl-link>
</div>
<gl-button
:aria-label="__('Toggle Sidebar')"
- class="d-md-none gl-ml-2 js-sidebar-build-toggle"
category="tertiary"
+ class="gl-display-md-none gl-ml-2 js-sidebar-build-toggle"
icon="chevron-double-lg-right"
@click="toggleSidebar"
/>
@@ -158,77 +107,37 @@ export default {
:href="job.new_issue_path"
class="btn btn-success btn-inverted float-left mr-2"
data-testid="job-new-issue"
- >{{ __('New issue') }}</gl-link
- >
+ >{{ __('New issue') }}
+ </gl-link>
<gl-link
v-if="job.terminal_path"
:href="job.terminal_path"
class="js-terminal-link btn btn-primary btn-inverted visible-md-block visible-lg-block float-left"
target="_blank"
>
- {{ __('Debug') }} <gl-icon name="external-link" :size="14" />
+ {{ __('Debug') }}
+ <gl-icon :size="14" name="external-link" />
</gl-link>
</div>
-
- <div v-if="renderBlock" class="block">
- <detail-row
- v-if="job.duration"
- :value="duration"
- class="js-job-duration"
- title="Duration"
- />
- <detail-row
- v-if="job.finished_at"
- :value="timeFormatted(job.finished_at)"
- class="js-job-finished"
- title="Finished"
- />
- <detail-row
- v-if="job.erased_at"
- :value="timeFormatted(job.erased_at)"
- class="js-job-erased"
- title="Erased"
- />
- <detail-row v-if="job.queued" :value="queued" class="js-job-queued" title="Queued" />
- <detail-row
- v-if="hasTimeout"
- :help-url="runnerHelpUrl"
- :value="timeout"
- class="js-job-timeout"
- title="Timeout"
- />
- <detail-row v-if="job.runner" :value="runnerId" class="js-job-runner" title="Runner" />
- <detail-row
- v-if="job.coverage"
- :value="coverage"
- class="js-job-coverage"
- title="Coverage"
- />
- <p v-if="job.tags.length" class="build-detail-row js-job-tags">
- <span class="font-weight-bold">{{ __('Tags:') }}</span>
- <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{
- tag
- }}</span>
- </p>
- </div>
-
+ <sidebar-job-details-container :runner-help-url="runnerHelpUrl" />
<artifacts-block v-if="hasArtifact" :artifact="job.artifact" :help-url="artifactHelpUrl" />
<trigger-block v-if="hasTriggers" :trigger="job.trigger" />
<commit-block
- :is-last-block="hasStages"
:commit="commit"
+ :is-last-block="hasStages"
:merge-request="job.merge_request"
/>
<stages-dropdown
- :stages="stages"
+ v-if="job.pipeline"
:pipeline="job.pipeline"
:selected-stage="selectedStage"
+ :stages="stages"
@requestSidebarStageDropdown="fetchJobsForStage"
/>
</div>
- <jobs-container v-if="jobs.length" :jobs="jobs" :job-id="job.id" />
+ <jobs-container v-if="jobs.length" :job-id="job.id" :jobs="jobs" />
</div>
</aside>
</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
new file mode 100644
index 00000000000..887fea982ad
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
@@ -0,0 +1,102 @@
+<script>
+import { mapState } from 'vuex';
+import DetailRow from './sidebar_detail_row.vue';
+import { __, sprintf } from '~/locale';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+
+export default {
+ name: 'SidebarJobDetailsContainer',
+ components: {
+ DetailRow,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ runnerHelpUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ ...mapState(['job']),
+ coverage() {
+ return `${this.job.coverage}%`;
+ },
+ duration() {
+ return timeIntervalInWords(this.job.duration);
+ },
+ erasedAt() {
+ return this.timeFormatted(this.job.erased_at);
+ },
+ finishedAt() {
+ return this.timeFormatted(this.job.finished_at);
+ },
+ hasTags() {
+ return this.job?.tags?.length;
+ },
+ hasTimeout() {
+ return this.job?.metadata?.timeout_human_readable ?? false;
+ },
+ hasAnyDetail() {
+ return Boolean(
+ this.job.duration ||
+ this.job.finished_at ||
+ this.job.erased_at ||
+ this.job.queued ||
+ this.job.runner ||
+ this.job.coverage,
+ );
+ },
+ queued() {
+ return timeIntervalInWords(this.job.queued);
+ },
+ runnerId() {
+ return `${this.job.runner.description} (#${this.job.runner.id})`;
+ },
+ shouldRenderBlock() {
+ return Boolean(this.hasAnyDetail || this.hasTimeout || this.hasTags);
+ },
+ timeout() {
+ return `${this.job?.metadata?.timeout_human_readable}${this.timeoutSource}`;
+ },
+ timeoutSource() {
+ if (!this.job?.metadata?.timeout_source) {
+ return '';
+ }
+
+ return sprintf(__(` (from %{timeoutSource})`), {
+ timeoutSource: this.job.metadata.timeout_source,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="shouldRenderBlock" class="block">
+ <detail-row v-if="job.duration" :value="duration" title="Duration" />
+ <detail-row
+ v-if="job.finished_at"
+ :value="finishedAt"
+ data-testid="job-finished"
+ title="Finished"
+ />
+ <detail-row v-if="job.erased_at" :value="erasedAt" title="Erased" />
+ <detail-row v-if="job.queued" :value="queued" title="Queued" />
+ <detail-row
+ v-if="hasTimeout"
+ :help-url="runnerHelpUrl"
+ :value="timeout"
+ data-testid="job-timeout"
+ title="Timeout"
+ />
+ <detail-row v-if="job.runner" :value="runnerId" title="Runner" />
+ <detail-row v-if="job.coverage" :value="coverage" title="Coverage" />
+
+ <p v-if="hasTags" class="build-detail-row" data-testid="job-tags">
+ <span class="font-weight-bold">{{ __('Tags:') }}</span>
+ <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{ tag }}</span>
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index dc4a3578a86..bf924bc1917 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -4,7 +4,7 @@ import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
export const hasUnmetPrerequisitesFailure = state =>
- state.job && state.job.failure_reason && state.job.failure_reason === 'unmet_prerequisites';
+ state?.job?.failure_reason === 'unmet_prerequisites';
export const shouldRenderCalloutMessage = state =>
!isEmpty(state.job.status) && !isEmpty(state.job.callout_message);
diff --git a/app/assets/javascripts/pages/search/show/index.js b/app/assets/javascripts/pages/search/show/index.js
index 721219874cf..04b34993bfd 100644
--- a/app/assets/javascripts/pages/search/show/index.js
+++ b/app/assets/javascripts/pages/search/show/index.js
@@ -3,5 +3,5 @@ import initSearchApp from '~/search';
document.addEventListener('DOMContentLoaded', () => {
initSearchApp();
- return new Search();
+ return new Search(); // Deprecated Dropdown (Projects)
});
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index fc66497cb41..03675f1ce66 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -5,48 +5,22 @@ import { deprecatedCreateFlash as Flash } from '~/flash';
import Api from '~/api';
import { __ } from '~/locale';
import Project from '~/pages/projects/project';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { visitUrl, queryToObject } from '~/lib/utils/url_utility';
import refreshCounts from './refresh_counts';
export default class Search {
constructor() {
- setHighlightClass();
- const $groupDropdown = $('.js-search-group-dropdown');
+ setHighlightClass(); // Code Highlighting
const $projectDropdown = $('.js-search-project-dropdown');
this.searchInput = '.js-search-input';
this.searchClear = '.js-search-clear';
- this.groupId = $groupDropdown.data('groupId');
+ const query = queryToObject(window.location.search);
+ this.groupId = query?.group_id;
this.eventListeners();
refreshCounts();
- initDeprecatedJQueryDropdown($groupDropdown, {
- selectable: true,
- filterable: true,
- filterRemote: true,
- fieldName: 'group_id',
- search: {
- fields: ['full_name'],
- },
- data(term, callback) {
- return Api.groups(term, {}, data => {
- data.unshift({
- full_name: __('Any'),
- });
- data.splice(1, 0, { type: 'divider' });
- return callback(data);
- });
- },
- id(obj) {
- return obj.id;
- },
- text(obj) {
- return obj.full_name;
- },
- clicked: () => Search.submitSearch(),
- });
-
initDeprecatedJQueryDropdown($projectDropdown, {
selectable: true,
filterable: true,
diff --git a/app/assets/javascripts/search/group_filter/components/group_filter.vue b/app/assets/javascripts/search/group_filter/components/group_filter.vue
new file mode 100644
index 00000000000..4b7963c5187
--- /dev/null
+++ b/app/assets/javascripts/search/group_filter/components/group_filter.vue
@@ -0,0 +1,124 @@
+<script>
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlSearchBoxByType,
+ GlLoadingIcon,
+ GlIcon,
+ GlSkeletonLoader,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { mapState, mapActions } from 'vuex';
+import { isEmpty } from 'lodash';
+import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
+import { ANY_GROUP, GROUP_QUERY_PARAM, PROJECT_QUERY_PARAM } from '../constants';
+
+export default {
+ name: 'GroupFilter',
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlSearchBoxByType,
+ GlLoadingIcon,
+ GlIcon,
+ GlSkeletonLoader,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ initialGroup: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ data() {
+ return {
+ groupSearch: '',
+ };
+ },
+ computed: {
+ ...mapState(['groups', 'fetchingGroups']),
+ selectedGroup: {
+ get() {
+ return isEmpty(this.initialGroup) ? ANY_GROUP : this.initialGroup;
+ },
+ set(group) {
+ visitUrl(setUrlParams({ [GROUP_QUERY_PARAM]: group.id, [PROJECT_QUERY_PARAM]: null }));
+ },
+ },
+ },
+ methods: {
+ ...mapActions(['fetchGroups']),
+ isGroupSelected(group) {
+ return group.id === this.selectedGroup.id;
+ },
+ handleGroupChange(group) {
+ this.selectedGroup = group;
+ },
+ },
+ ANY_GROUP,
+};
+</script>
+
+<template>
+ <gl-dropdown
+ ref="groupFilter"
+ class="gl-w-full"
+ menu-class="gl-w-full!"
+ toggle-class="gl-text-truncate gl-reset-line-height!"
+ :header-text="__('Filter results by group')"
+ @show="fetchGroups(groupSearch)"
+ >
+ <template #button-content>
+ <span class="dropdown-toggle-text gl-flex-grow-1 gl-text-truncate">
+ {{ selectedGroup.name }}
+ </span>
+ <gl-loading-icon v-if="fetchingGroups" inline class="mr-2" />
+ <gl-icon
+ v-if="!isGroupSelected($options.ANY_GROUP)"
+ v-gl-tooltip
+ name="clear"
+ :title="__('Clear')"
+ class="gl-text-gray-200! gl-hover-text-blue-800!"
+ @click.stop="handleGroupChange($options.ANY_GROUP)"
+ />
+ <gl-icon name="chevron-down" />
+ </template>
+ <div class="gl-sticky gl-top-0 gl-z-index-1 gl-bg-white">
+ <gl-search-box-by-type
+ v-model="groupSearch"
+ class="m-2"
+ :debounce="500"
+ @input="fetchGroups"
+ />
+ <gl-dropdown-item
+ class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2"
+ :is-check-item="true"
+ :is-checked="isGroupSelected($options.ANY_GROUP)"
+ @click="handleGroupChange($options.ANY_GROUP)"
+ >
+ {{ $options.ANY_GROUP.name }}
+ </gl-dropdown-item>
+ </div>
+ <div v-if="!fetchingGroups">
+ <gl-dropdown-item
+ v-for="group in groups"
+ :key="group.id"
+ :is-check-item="true"
+ :is-checked="isGroupSelected(group)"
+ @click="handleGroupChange(group)"
+ >
+ {{ group.full_name }}
+ </gl-dropdown-item>
+ </div>
+ <div v-if="fetchingGroups" class="mx-3 mt-2">
+ <gl-skeleton-loader :height="100">
+ <rect y="0" width="90%" height="20" rx="4" />
+ <rect y="40" width="70%" height="20" rx="4" />
+ <rect y="80" width="80%" height="20" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/search/group_filter/constants.js b/app/assets/javascripts/search/group_filter/constants.js
new file mode 100644
index 00000000000..9bd92eaa130
--- /dev/null
+++ b/app/assets/javascripts/search/group_filter/constants.js
@@ -0,0 +1,10 @@
+import { __ } from '~/locale';
+
+export const ANY_GROUP = Object.freeze({
+ id: null,
+ name: __('Any'),
+});
+
+export const GROUP_QUERY_PARAM = 'group_id';
+
+export const PROJECT_QUERY_PARAM = 'project_id';
diff --git a/app/assets/javascripts/search/group_filter/index.js b/app/assets/javascripts/search/group_filter/index.js
new file mode 100644
index 00000000000..9b009bc0305
--- /dev/null
+++ b/app/assets/javascripts/search/group_filter/index.js
@@ -0,0 +1,28 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import GroupFilter from './components/group_filter.vue';
+
+Vue.use(Translate);
+
+export default store => {
+ let initialGroup;
+ const el = document.getElementById('js-search-group-dropdown');
+
+ const { initialGroupData } = el.dataset;
+
+ initialGroup = JSON.parse(initialGroupData);
+ initialGroup = convertObjectPropsToCamelCase(initialGroup, { deep: true });
+
+ return new Vue({
+ el,
+ store,
+ render(createElement) {
+ return createElement(GroupFilter, {
+ props: {
+ initialGroup,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/search/index.js b/app/assets/javascripts/search/index.js
index 780d3ff0d25..7d175bace4f 100644
--- a/app/assets/javascripts/search/index.js
+++ b/app/assets/javascripts/search/index.js
@@ -1,9 +1,11 @@
import { queryToObject } from '~/lib/utils/url_utility';
import createStore from './store';
import initDropdownFilters from './dropdown_filter';
+import initGroupFilter from './group_filter';
export default () => {
const store = createStore({ query: queryToObject(window.location.search) });
initDropdownFilters(store);
+ initGroupFilter(store);
};
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js
new file mode 100644
index 00000000000..4bc8b2a9a5d
--- /dev/null
+++ b/app/assets/javascripts/search/store/actions.js
@@ -0,0 +1,16 @@
+import Api from '~/api';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+import * as types from './mutation_types';
+
+export const fetchGroups = ({ commit }, search) => {
+ commit(types.REQUEST_GROUPS);
+ Api.groups(search)
+ .then(data => {
+ commit(types.RECEIVE_GROUPS_SUCCESS, data);
+ })
+ .catch(() => {
+ createFlash({ message: __('There was a problem fetching groups.') });
+ commit(types.RECEIVE_GROUPS_ERROR);
+ });
+};
diff --git a/app/assets/javascripts/search/store/index.js b/app/assets/javascripts/search/store/index.js
index 10cfb647a92..e0a7e488f9f 100644
--- a/app/assets/javascripts/search/store/index.js
+++ b/app/assets/javascripts/search/store/index.js
@@ -1,10 +1,14 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
import createState from './state';
Vue.use(Vuex);
export const getStoreConfig = ({ query }) => ({
+ actions,
+ mutations,
state: createState({ query }),
});
diff --git a/app/assets/javascripts/search/store/mutation_types.js b/app/assets/javascripts/search/store/mutation_types.js
new file mode 100644
index 00000000000..8469f3517c0
--- /dev/null
+++ b/app/assets/javascripts/search/store/mutation_types.js
@@ -0,0 +1,3 @@
+export const REQUEST_GROUPS = 'REQUEST_GROUPS';
+export const RECEIVE_GROUPS_SUCCESS = 'RECEIVE_GROUPS_SUCCESS';
+export const RECEIVE_GROUPS_ERROR = 'RECEIVE_GROUPS_ERROR';
diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js
new file mode 100644
index 00000000000..34fe14716f9
--- /dev/null
+++ b/app/assets/javascripts/search/store/mutations.js
@@ -0,0 +1,15 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.REQUEST_GROUPS](state) {
+ state.fetchingGroups = true;
+ },
+ [types.RECEIVE_GROUPS_SUCCESS](state, data) {
+ state.fetchingGroups = false;
+ state.groups = data;
+ },
+ [types.RECEIVE_GROUPS_ERROR](state) {
+ state.fetchingGroups = false;
+ state.groups = [];
+ },
+};
diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js
index 9115a613767..70a8aab9998 100644
--- a/app/assets/javascripts/search/store/state.js
+++ b/app/assets/javascripts/search/store/state.js
@@ -1,4 +1,6 @@
const createState = ({ query }) => ({
query,
+ groups: [],
+ fetchingGroups: false,
});
export default createState;
diff --git a/app/assets/javascripts/static_site_editor/components/edit_area.vue b/app/assets/javascripts/static_site_editor/components/edit_area.vue
index 5b2d0207e98..e559babf39c 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_area.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_area.vue
@@ -10,6 +10,7 @@ import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants';
import imageRepository from '../image_repository';
import formatter from '../services/formatter';
import templater from '../services/templater';
+import renderImage from '../services/renderers/render_image';
export default {
components: {
@@ -41,6 +42,10 @@ export default {
type: Array,
required: true,
},
+ project: {
+ type: String,
+ required: true,
+ },
imageRoot: {
type: String,
required: false,
@@ -72,6 +77,12 @@ export default {
isWysiwygMode() {
return this.editorMode === EDITOR_TYPES.wysiwyg;
},
+ customRenderers() {
+ const imageRenderer = renderImage.build(this.mounts, this.project);
+ return {
+ image: [imageRenderer],
+ };
+ },
},
created() {
this.refreshEditHelpers();
@@ -140,6 +151,7 @@ export default {
:content="editableContent"
:initial-edit-type="editorMode"
:image-root="imageRoot"
+ :options="{ customRenderers }"
class="mb-9 pb-6 h-100"
@modeChange="onModeChange"
@input="onInputChange"
diff --git a/app/assets/javascripts/static_site_editor/pages/home.vue b/app/assets/javascripts/static_site_editor/pages/home.vue
index d7e52ef9c45..a315e3d8f49 100644
--- a/app/assets/javascripts/static_site_editor/pages/home.vue
+++ b/app/assets/javascripts/static_site_editor/pages/home.vue
@@ -139,6 +139,7 @@ export default {
:saving-changes="isSavingChanges"
:return-url="appData.returnUrl"
:mounts="appData.mounts"
+ :project="appData.project"
@submit="onPrepareSubmit"
/>
<edit-meta-modal
diff --git a/app/assets/javascripts/static_site_editor/services/renderers/render_image.js b/app/assets/javascripts/static_site_editor/services/renderers/render_image.js
new file mode 100644
index 00000000000..38304a1c57f
--- /dev/null
+++ b/app/assets/javascripts/static_site_editor/services/renderers/render_image.js
@@ -0,0 +1,30 @@
+const canRender = ({ type }) => type === 'image';
+
+// NOTE: the `metadata` is not used yet, but will be used in a follow-up iteration
+// To be removed with the next iteration of https://gitlab.com/gitlab-org/gitlab/-/issues/218531
+// eslint-disable-next-line no-unused-vars
+let metadata;
+
+const render = (node, { skipChildren }) => {
+ skipChildren();
+
+ // To be removed with the next iteration of https://gitlab.com/gitlab-org/gitlab/-/issues/218531
+ // TODO resolve relative paths
+
+ return {
+ type: 'openTag',
+ tagName: 'img',
+ selfClose: true,
+ attributes: {
+ src: node.destination,
+ alt: node.firstChild.literal,
+ },
+ };
+};
+
+const build = (mounts, project) => {
+ metadata = { mounts, project };
+ return { canRender, render };
+};
+
+export default { build };
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index a62e28a9b8a..502a1881fd2 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -270,7 +270,8 @@ input[type='checkbox']:hover {
width: 100%;
}
- .dropdown-menu-toggle {
+ .dropdown-menu-toggle,
+ .gl-new-dropdown {
@include media-breakpoint-up(lg) {
width: 240px;
}
diff --git a/app/finders/user_groups_counter.rb b/app/finders/user_groups_counter.rb
new file mode 100644
index 00000000000..7dbc8502be2
--- /dev/null
+++ b/app/finders/user_groups_counter.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class UserGroupsCounter
+ def initialize(user_ids)
+ @user_ids = user_ids
+ end
+
+ def execute
+ Namespace.unscoped do
+ Namespace.from_union([
+ groups,
+ project_groups
+ ]).group(:user_id).count # rubocop: disable CodeReuse/ActiveRecord
+ end
+ end
+
+ private
+
+ attr_reader :user_ids
+
+ def groups
+ Group.for_authorized_group_members(user_ids)
+ .select('namespaces.*, members.user_id as user_id')
+ end
+
+ def project_groups
+ Group.for_authorized_project_members(user_ids)
+ .select('namespaces.*, project_authorizations.user_id as user_id')
+ end
+end
diff --git a/app/graphql/mutations/todos/create.rb b/app/graphql/mutations/todos/create.rb
new file mode 100644
index 00000000000..53c88696fdd
--- /dev/null
+++ b/app/graphql/mutations/todos/create.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Todos
+ class Create < ::Mutations::Todos::Base
+ graphql_name 'TodoCreate'
+
+ authorize :create_todo
+
+ argument :target_id,
+ Types::GlobalIDType[Todoable],
+ required: true,
+ description: "The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported"
+
+ field :todo, Types::TodoType,
+ null: true,
+ description: 'The to-do created'
+
+ def resolve(target_id:)
+ id = ::Types::GlobalIDType[Todoable].coerce_isolated_input(target_id)
+ target = authorized_find!(id)
+
+ todo = TodoService.new.mark_todo(target, current_user)&.first
+ errors = errors_on_object(todo) if todo
+
+ {
+ todo: todo,
+ errors: errors
+ }
+ end
+
+ private
+
+ def find_object(id)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
index c221cb9aed6..9f57c8f3405 100644
--- a/app/graphql/resolvers/namespace_projects_resolver.rb
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -23,7 +23,6 @@ module Resolvers
# The namespace could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` or the `full_path` of the namespace
# to query for projects, so make sure it's loaded and not `nil` before continuing.
- namespace = object.respond_to?(:sync) ? object.sync : object
return Project.none if namespace.nil?
query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route
@@ -41,6 +40,14 @@ module Resolvers
complexity = super
complexity + 10
end
+
+ private
+
+ def namespace
+ strong_memoize(:namespace) do
+ object.respond_to?(:sync) ? object.sync : object
+ end
+ end
end
end
diff --git a/app/graphql/resolvers/users/group_count_resolver.rb b/app/graphql/resolvers/users/group_count_resolver.rb
new file mode 100644
index 00000000000..5033c26554a
--- /dev/null
+++ b/app/graphql/resolvers/users/group_count_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class GroupCountResolver < BaseResolver
+ alias_method :user, :object
+
+ def resolve(**args)
+ return unless can_read_group_count?
+
+ BatchLoader::GraphQL.for(user.id).batch do |user_ids, loader|
+ results = UserGroupsCounter.new(user_ids).execute
+
+ results.each do |user_id, count|
+ loader.call(user_id, count)
+ end
+ end
+ end
+
+ def can_read_group_count?
+ current_user&.can?(:read_group_count, user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 2cee6024329..f63f8129b20 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -63,6 +63,7 @@ module Types
mount_mutation Mutations::Terraform::State::Delete
mount_mutation Mutations::Terraform::State::Lock
mount_mutation Mutations::Terraform::State::Unlock
+ mount_mutation Mutations::Todos::Create
mount_mutation Mutations::Todos::MarkDone
mount_mutation Mutations::Todos::Restore
mount_mutation Mutations::Todos::MarkAllDone
diff --git a/app/graphql/types/projects/namespace_project_sort_enum.rb b/app/graphql/types/projects/namespace_project_sort_enum.rb
index 1e13deb6508..ede29748beb 100644
--- a/app/graphql/types/projects/namespace_project_sort_enum.rb
+++ b/app/graphql/types/projects/namespace_project_sort_enum.rb
@@ -7,6 +7,7 @@ module Types
description 'Values for sorting projects'
value 'SIMILARITY', 'Most similar to the search query', value: :similarity
+ value 'STORAGE', 'Sort by storage size', value: :storage
end
end
end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 8047708776d..735bf06337b 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -32,6 +32,10 @@ module Types
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
description: 'Group memberships of the user',
method: :group_members
+ field :group_count, GraphQL::INT_TYPE, null: true,
+ resolver: Resolvers::Users::GroupCountResolver,
+ description: 'Group count for the user',
+ feature_flag: :user_group_counts
field :status, Types::UserStatusType, null: true,
description: 'User status'
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index 1ee04f88caf..48c022bd763 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -80,7 +80,7 @@ module Ci
end
def fog_store_class
- if Feature.enabled?(:ci_trace_new_fog_store)
+ if Feature.enabled?(:ci_trace_new_fog_store, default_enabled: true)
Ci::BuildTraceChunks::Fog
else
Ci::BuildTraceChunks::LegacyFog
diff --git a/app/models/concerns/todoable.rb b/app/models/concerns/todoable.rb
new file mode 100644
index 00000000000..d93ab463251
--- /dev/null
+++ b/app/models/concerns/todoable.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# == Todoable concern
+#
+# Specify object types that supports todos.
+#
+# Used by Issue, MergeRequest, Design and Epic.
+#
+module Todoable
+end
diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb
index 62e4bd6cebc..4b4b29558bc 100644
--- a/app/models/design_management/design.rb
+++ b/app/models/design_management/design.rb
@@ -10,6 +10,7 @@ module DesignManagement
include Mentionable
include WhereComposite
include RelativePositioning
+ include Todoable
belongs_to :project, inverse_of: :designs
belongs_to :issue
diff --git a/app/models/group.rb b/app/models/group.rb
index 74f7efd253d..32ccc4ed3a7 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -98,6 +98,17 @@ class Group < Namespace
scope :by_id, ->(groups) { where(id: groups) }
+ scope :for_authorized_group_members, -> (user_ids) do
+ joins(:group_members)
+ .where("members.user_id IN (?)", user_ids)
+ .where("access_level >= ?", Gitlab::Access::GUEST)
+ end
+
+ scope :for_authorized_project_members, -> (user_ids) do
+ joins(projects: :project_authorizations)
+ .where("project_authorizations.user_id IN (?)", user_ids)
+ end
+
class << self
def sort_by_attribute(method)
if method == 'storage_size_desc'
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 5291b7890b6..ffdde91b2a2 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -21,6 +21,7 @@ class Issue < ApplicationRecord
include IdInOrdered
include Presentable
include IssueAvailableFeatures
+ include Todoable
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 24541ba3218..bf7a73f24e4 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -22,6 +22,7 @@ class MergeRequest < ApplicationRecord
include StateEventable
include ApprovableBase
include IdInOrdered
+ include Todoable
extend ::Gitlab::Utils::Override
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 44c448eb601..183f4d8f919 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -35,6 +35,10 @@ class IssuePolicy < IssuablePolicy
rule { ~can?(:read_design) }.policy do
prevent :move_design
end
+
+ rule { ~anonymous & can?(:read_issue) }.policy do
+ enable :create_todo
+ end
end
IssuePolicy.prepend_if_ee('EE::IssuePolicy')
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index e5ac228b0ee..d5ba42d750c 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -14,6 +14,10 @@ class MergeRequestPolicy < IssuablePolicy
rule { can?(:update_merge_request) }.policy do
enable :approve_merge_request
end
+
+ rule { ~anonymous & can?(:read_merge_request) }.policy do
+ enable :create_todo
+ end
end
MergeRequestPolicy.prepend_if_ee('EE::MergeRequestPolicy')
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index c9dfa98b285..61030b6d5e6 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -21,6 +21,7 @@ class UserPolicy < BasePolicy
enable :update_user
enable :update_user_status
enable :read_user_personal_access_tokens
+ enable :read_group_count
end
rule { default }.enable :read_user_profile
diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb
index 06a2a107966..61c5565db60 100644
--- a/app/services/bulk_create_integration_service.rb
+++ b/app/services/bulk_create_integration_service.rb
@@ -33,15 +33,17 @@ class BulkCreateIntegrationService
klass.insert_all(items_to_insert, returning: [:id])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def run_callbacks(batch)
if integration.external_issue_tracker?
- batch.update_all(has_external_issue_tracker: true)
+ Project.where(id: batch.select(:id)).update_all(has_external_issue_tracker: true)
end
if integration.external_wiki?
- batch.update_all(has_external_wiki: true)
+ Project.where(id: batch.select(:id)).update_all(has_external_wiki: true)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def service_hash
if integration.template?
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 424251f543e..386df99717b 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -1,6 +1,7 @@
- add_to_breadcrumbs _("Groups"), admin_groups_path
- breadcrumb_title @group.name
- page_title @group.name, _("Groups")
+- current_user_is_group_owner = @group && @group.has_owner?(current_user)
.js-remove-member-modal
%h3.page-title
@@ -116,7 +117,7 @@
= select_tag :access_level, options_for_select(@group.access_level_roles), class: "project-access-select select2"
%hr
= button_tag _('Add users to group'), class: "gl-button btn btn-success"
- = render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
+ = render 'shared/members/requests', membership_source: @group, group: @group, requesters: @requesters, force_mobile_view: true
.card
.card-header
@@ -127,6 +128,11 @@
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Manage access')
%ul.content-list.group-users-list.content-list.members-list
- = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
+ = render partial: 'shared/members/member',
+ collection: @members, as: :member,
+ locals: { membership_source: @group,
+ group: @group,
+ show_controls: false,
+ current_user_is_group_owner: current_user_is_group_owner }
.card-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 417fd1d60eb..1d08f59838d 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -2,6 +2,7 @@
- breadcrumb_title @project.full_name
- page_title @project.full_name, _("Projects")
- @content_class = "admin-projects"
+- current_user_is_group_owner = @group && @group.has_owner?(current_user)
.js-remove-member-modal
%h3.page-title
@@ -183,11 +184,16 @@
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Manage access')
%ul.content-list.members-list
- = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
+ = render partial: 'shared/members/member',
+ collection: @group_members, as: :member,
+ locals: { membership_source: @project,
+ group: @group,
+ show_controls: false,
+ current_user_is_group_owner: current_user_is_group_owner }
.card-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
- = render 'shared/members/requests', membership_source: @project, requesters: @requesters, force_mobile_view: true
+ = render 'shared/members/requests', membership_source: @project, group: @group, requesters: @requesters, force_mobile_view: true
.card
.card-header
@@ -199,6 +205,11 @@
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Manage access')
%ul.content-list.project_members.members-list
- = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
+ = render partial: 'shared/members/member',
+ collection: @project_members, as: :member,
+ locals: { membership_source: @project,
+ group: @group,
+ show_controls: false,
+ current_user_is_group_owner: current_user_is_group_owner }
.card-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index d999f20ef91..1272a216ee7 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -4,6 +4,7 @@
- show_access_requests = can_manage_members && @requesters.exists?
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group)
+- current_user_is_group_owner = @group && @group.has_owner?(current_user)
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
@@ -71,7 +72,11 @@
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
- else
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
- = render partial: 'shared/members/member', collection: @members, as: :member
+ = render partial: 'shared/members/member',
+ collection: @members, as: :member,
+ locals: { membership_source: @group,
+ group: @group,
+ current_user_is_group_owner: current_user_is_group_owner }
= paginate @members, theme: 'gitlab', params: { invited_members_page: nil, search_invited: nil }
- if @group.shared_with_group_links.any?
#tab-groups.tab-pane
@@ -97,7 +102,11 @@
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
- else
%ul.content-list.members-list
- = render partial: 'shared/members/member', collection: @invited_members, as: :member
+ = render partial: 'shared/members/member',
+ collection: @invited_members, as: :member,
+ locals: { membership_source: @group,
+ group: @group,
+ current_user_is_group_owner: current_user_is_group_owner }
= paginate @invited_members, param_name: 'invited_members_page', theme: 'gitlab', params: { page: nil }
- if show_access_requests
#tab-access-requests.tab-pane
@@ -109,4 +118,8 @@
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
- else
%ul.content-list.members-list
- = render partial: 'shared/members/member', collection: @requesters, as: :member
+ = render partial: 'shared/members/member',
+ collection: @requesters, as: :member,
+ locals: { membership_source: @group,
+ group: @group,
+ current_user_is_group_owner: current_user_is_group_owner }
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 4d4705c4ed5..138859c7c7c 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,5 +1,7 @@
- project = local_assigns.fetch(:project)
- members = local_assigns.fetch(:members)
+- group = local_assigns.fetch(:group)
+- current_user_is_group_owner = group && group.has_owner?(current_user)
.card
.card-header.flex-project-members-panel
@@ -15,4 +17,8 @@
= label_tag :sort_by, _('Sort by'), class: 'col-form-label label-bold px-2'
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
- = render partial: 'shared/members/member', collection: members, as: :member
+ = render partial: 'shared/members/member',
+ collection: members, as: :member,
+ locals: { membership_source: project,
+ group: group,
+ current_user_is_group_owner: current_user_is_group_owner }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9a1e997fce7..cad76d7aeac 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,5 +1,6 @@
- page_title _("Members")
- can_admin_project_members = can?(current_user, :admin_project_member, @project)
+- group = @project.group
.js-remove-member-modal
.row.gl-mt-3
@@ -32,12 +33,12 @@
- elsif @project.allowed_to_share_with_group?
.invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access'
- = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+ = render 'shared/members/requests', membership_source: @project, group: group, requesters: @requesters
.clearfix
%h5.member.existing-title
= _("Existing members and groups")
- if @group_links.any?
= render 'projects/project_members/groups', group_links: @group_links
- = render 'projects/project_members/team', project: @project, members: @project_members
+ = render 'projects/project_members/team', project: @project, group: group, members: @project_members
= paginate @project_members, theme: "gitlab"
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 6e8e7d8fd1f..964a2a2772a 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -2,21 +2,10 @@
= hidden_field_tag :group_id, params[:group_id]
- if params[:project_id].present?
= hidden_field_tag :project_id, params[:project_id]
-.dropdown.form-group.mb-lg-0.mx-lg-1{ data: { testid: "group-filter" } }
+.dropdown.form-group.mb-lg-0.mx-lg-1.gl-p-0{ data: { testid: "group-filter" } }
%label.d-block{ for: "dashboard_search_group" }
= _("Group")
- %button.dropdown-menu-toggle.gl-display-inline-flex.js-search-group-dropdown.gl-mt-0{ type: "button", id: "dashboard_search_group", data: { toggle: "dropdown", group_id: params[:group_id] } }
- %span.dropdown-toggle-text.gl-flex-grow-1.str-truncated-100
- = @group&.name || _("Any")
- - if @group.present?
- = link_to sprite_icon("clear"), url_for(safe_params.except(:project_id, :group_id)), class: 'search-clear js-search-clear has-tooltip', title: _('Clear')
- = icon("chevron-down")
- .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title(_("Filter results by group"))
- = dropdown_filter(_("Search groups"))
- = dropdown_content
- = dropdown_loading
-
+ %input#js-search-group-dropdown.dropdown-menu-toggle{ value: "Loading...", data: { "initial-group-data": @group.to_json } }
.dropdown.form-group.mb-lg-0.mx-lg-1{ data: { testid: "project-filter" } }
%label.d-block{ for: "dashboard_search_project" }
= _("Project")
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index f21ec45eefb..352d51dbb8e 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -6,7 +6,7 @@
- if issuable_mr > 0
%li.issuable-mr.gl-display-none.gl-display-sm-block.has-tooltip{ title: _('Related merge requests') }
- = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
+ = sprite_icon('merge-request', css_class: "gl-vertical-align-middle")
= issuable_mr
- if upvotes > 0
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 6315fcf9c01..e294936f82c 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -2,6 +2,9 @@
- show_controls = local_assigns.fetch(:show_controls, true)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- member = local_assigns.fetch(:member)
+- current_user_is_group_owner = local_assigns.fetch(:current_user_is_group_owner, false)
+- membership_source = local_assigns.fetch(:membership_source)
+- group = local_assigns.fetch(:group)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
- override = member.try(:override)
@@ -25,13 +28,13 @@
= render 'shared/members/its_you_badge', user: user, current_user: current_user
- = render_if_exists 'shared/members/ee/license_badge', user: user, group: @group
+ = render_if_exists 'shared/members/ee/license_badge', user: user, group: group, current_user_is_group_owner: current_user_is_group_owner
= render 'shared/members/blocked_badge', user: user
= render 'shared/members/two_factor_auth_badge', user: user
- - if source.instance_of?(Group) && source != @group
+ - if source.instance_of?(Group) && source != membership_source
&middot;
= link_to source.full_name, source, class: "gl-display-inline-block inline-link"
@@ -57,10 +60,9 @@
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- if show_roles
- - current_resource = @project || @group
.controls.member-controls.align-items-center
= render_if_exists 'shared/members/ee/ldap_tag', can_override: member.can_override?
- - if show_controls && member.source == current_resource
+ - if show_controls && member.source == membership_source
- if member.can_resend_invite?
= link_to sprite_icon('paper-airplane'), polymorphic_path([:resend_invite, member]),
@@ -88,7 +90,7 @@
class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member), qa_selector: "#{role.downcase}_access_level_link" }
= render_if_exists 'shared/members/ee/revert_ldap_group_sync_option',
- group: @group,
+ group: group,
member: member,
can_override: member.can_override?
.clearable-input.member-form-control{ class: [("d-sm-inline-block" unless force_mobile_view)] }
@@ -125,8 +127,8 @@
= _("Delete")
- unless force_mobile_view
= sprite_icon('remove', css_class: 'd-none d-sm-block gl-icon')
- = render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: member.can_override?
+ = render_if_exists 'shared/members/ee/override_member_buttons', group: group, member: member, user: user, action: :edit, can_override: member.can_override?
- else
%span.member-access-text.user-access-role= member.human_access
-= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :confirm, can_override: member.can_override?
+= render_if_exists 'shared/members/ee/override_member_buttons', group: group, member: member, user: user, action: :confirm, can_override: member.can_override?
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index e1e7aa36a78..3aa43ed1922 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,6 +1,8 @@
- membership_source = local_assigns.fetch(:membership_source)
- requesters = local_assigns.fetch(:requesters)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
+- group = local_assigns.fetch(:group)
+- current_user_is_group_owner = group && group.has_owner?(current_user)
- return if requesters.empty?
@@ -10,4 +12,9 @@
%strong= membership_source.name
%span.badge.badge-pill= requesters.size
%ul.content-list.members-list
- = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
+ = render partial: 'shared/members/member',
+ collection: requesters, as: :member,
+ locals: { membership_source: membership_source,
+ group: group,
+ force_mobile_view: force_mobile_view,
+ current_user_is_group_owner: current_user_is_group_owner }
diff --git a/changelogs/unreleased/267963-related-merge-requests-icon-in-issues-list-is-rendered-incorrectly.yml b/changelogs/unreleased/267963-related-merge-requests-icon-in-issues-list-is-rendered-incorrectly.yml
new file mode 100644
index 00000000000..4cc67bfbcb7
--- /dev/null
+++ b/changelogs/unreleased/267963-related-merge-requests-icon-in-issues-list-is-rendered-incorrectly.yml
@@ -0,0 +1,5 @@
+---
+title: Updated list view MR icon
+merge_request: 46059
+author:
+type: fixed
diff --git a/changelogs/unreleased/diff-file-header-clipboard-btn-overflow.yml b/changelogs/unreleased/diff-file-header-clipboard-btn-overflow.yml
new file mode 100644
index 00000000000..9dfcd6201c8
--- /dev/null
+++ b/changelogs/unreleased/diff-file-header-clipboard-btn-overflow.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure that copy to clipboard button is visible
+merge_request: 46466
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_233479-add-graphql-create-todo-mutation.yml b/changelogs/unreleased/issue_233479-add-graphql-create-todo-mutation.yml
new file mode 100644
index 00000000000..a623b13d17c
--- /dev/null
+++ b/changelogs/unreleased/issue_233479-add-graphql-create-todo-mutation.yml
@@ -0,0 +1,5 @@
+---
+title: Allow to create todo on GraphQL
+merge_request: 46029
+author:
+type: added
diff --git a/config/feature_flags/development/ci_trace_new_fog_store.yml b/config/feature_flags/development/ci_trace_new_fog_store.yml
index 61ef02376f6..04d2a43100a 100644
--- a/config/feature_flags/development/ci_trace_new_fog_store.yml
+++ b/config/feature_flags/development/ci_trace_new_fog_store.yml
@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46209
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273405
type: development
group: group::testing
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/user_group_counts.yml b/config/feature_flags/development/user_group_counts.yml
new file mode 100644
index 00000000000..a4edb5f5c92
--- /dev/null
+++ b/config/feature_flags/development/user_group_counts.yml
@@ -0,0 +1,7 @@
+---
+name: user_group_counts
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44069/
+rollout_issue_url:
+type: development
+group: group::compliance
+default_enabled: false
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 56892bed996..e206e383856 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -462,6 +462,34 @@ are stored.
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+Alternatively, if you have existing Pages deployed you can follow
+the below steps to do a no downtime transfer to a new storage location.
+
+1. Pause Pages deployments by setting the following in `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ sidekiq['experimental_queue_selector'] = true
+ sidekiq['queue_groups'] = [
+ "feature_category!=pages"
+ ]
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. `rsync` contents from the current storage location to the new storage location: `sudo rsync -avzh --progress /var/opt/gitlab/gitlab-rails/shared/pages/ /mnt/storage/pages`
+1. Set the new storage location in `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['pages_path'] = "/mnt/storage/pages"
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Verify Pages are still being served up as expected.
+1. Unpause Pages deployments by removing from `/etc/gitlab/gitlab.rb` the `sidekiq` setting set above.
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Trigger a new Pages deployment and verify it's working as expected.
+1. Remove the old Pages storage location: `sudo rm -rf /var/opt/gitlab/gitlab-rails/shared/pages`
+1. Verify Pages are still being served up as expected.
+
## Configure listener for reverse proxy requests
Follow the steps below to configure the proxy listener of GitLab Pages. [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2533) in
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index fd36324f363..0b676a55962 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -12992,6 +12992,7 @@ type Mutation {
terraformStateDelete(input: TerraformStateDeleteInput!): TerraformStateDeletePayload
terraformStateLock(input: TerraformStateLockInput!): TerraformStateLockPayload
terraformStateUnlock(input: TerraformStateUnlockInput!): TerraformStateUnlockPayload
+ todoCreate(input: TodoCreateInput!): TodoCreatePayload
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
todoRestoreMany(input: TodoRestoreManyInput!): TodoRestoreManyPayload
@@ -13276,6 +13277,11 @@ enum NamespaceProjectSort {
Most similar to the search query
"""
SIMILARITY
+
+ """
+ Sort by storage size
+ """
+ STORAGE
}
input NegatedBoardIssueInput {
@@ -20173,6 +20179,41 @@ type TodoConnection {
}
"""
+Autogenerated input type of TodoCreate
+"""
+input TodoCreateInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported
+ """
+ targetId: TodoableID!
+}
+
+"""
+Autogenerated return type of TodoCreate
+"""
+type TodoCreatePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The to-do created
+ """
+ todo: Todo
+}
+
+"""
An edge in a connection.
"""
type TodoEdge {
@@ -20340,6 +20381,11 @@ enum TodoTargetEnum {
}
"""
+Identifier of Todoable
+"""
+scalar TodoableID
+
+"""
Autogenerated input type of TodosMarkAllDone
"""
input TodosMarkAllDoneInput {
@@ -21542,6 +21588,11 @@ type User {
email: String
"""
+ Group count for the user. Available only when feature flag `user_group_counts` is enabled
+ """
+ groupCount: Int
+
+ """
Group memberships of the user
"""
groupMemberships(
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 8181bfcf727..a5b780f8821 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -37871,6 +37871,33 @@
"deprecationReason": null
},
{
+ "name": "todoCreate",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "TodoCreateInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "TodoCreatePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "todoMarkDone",
"description": null,
"args": [
@@ -39133,6 +39160,12 @@
"description": "Most similar to the search query",
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "STORAGE",
+ "description": "Sort by storage size",
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"possibleTypes": null
@@ -58682,6 +58715,108 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "TodoCreateInput",
+ "description": "Autogenerated input type of TodoCreate",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "targetId",
+ "description": "The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "TodoableID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "TodoCreatePayload",
+ "description": "Autogenerated return type of TodoCreate",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "todo",
+ "description": "The to-do created",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Todo",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "TodoEdge",
"description": "An edge in a connection.",
@@ -59167,6 +59302,16 @@
"possibleTypes": null
},
{
+ "kind": "SCALAR",
+ "name": "TodoableID",
+ "description": "Identifier of Todoable",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "INPUT_OBJECT",
"name": "TodosMarkAllDoneInput",
"description": "Autogenerated input type of TodosMarkAllDone",
@@ -62401,6 +62546,20 @@
"deprecationReason": null
},
{
+ "name": "groupCount",
+ "description": "Group count for the user. Available only when feature flag `user_group_counts` is enabled",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "groupMemberships",
"description": "Group memberships of the user",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index e820b953d49..17209955654 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -2838,6 +2838,16 @@ Representing a todo entry.
| `state` | TodoStateEnum! | State of the todo |
| `targetType` | TodoTargetEnum! | Target type of the todo |
+### TodoCreatePayload
+
+Autogenerated return type of TodoCreate.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `todo` | Todo | The to-do created |
+
### TodoMarkDonePayload
Autogenerated return type of TodoMarkDone.
@@ -3041,6 +3051,7 @@ Autogenerated return type of UpdateSnippet.
| ----- | ---- | ----------- |
| `avatarUrl` | String | URL of the user's avatar |
| `email` | String | User email |
+| `groupCount` | Int | Group count for the user. Available only when feature flag `user_group_counts` is enabled |
| `id` | ID! | ID of the user |
| `name` | String! | Human-readable name of the user |
| `state` | UserState! | State of the user |
@@ -3770,6 +3781,7 @@ Values for sorting projects.
| Value | Description |
| ----- | ----------- |
| `SIMILARITY` | Most similar to the search query |
+| `STORAGE` | Sort by storage size |
### PackageTypeEnum
diff --git a/doc/api/scim.md b/doc/api/scim.md
index 0c4ca5e898f..653d56f3e06 100644
--- a/doc/api/scim.md
+++ b/doc/api/scim.md
@@ -1,25 +1,23 @@
---
-stage: none
-group: unassigned
+type: reference, howto
+stage: Manage
+group: Access
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
---
-# SCIM API **(SILVER ONLY)**
+# SCIM API (SYSTEM ONLY) **(SILVER ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9388) in [GitLab Silver](https://about.gitlab.com/pricing/) 11.10.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9388) in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.10.
-The SCIM API implements the [RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
+The SCIM API implements the [RFC7644 protocol](https://tools.ietf.org/html/rfc7644). As this API is for
+**system** use for SCIM provider integration, it is subject to change without notice.
-CAUTION: **Caution:**
-This API is for internal system use for connecting with a SCIM provider. While it can be used directly, it is subject to change without notice.
-
-NOTE: **Note:**
-[Group SSO](../user/group/saml_sso/index.md) must be enabled for the group. For more information, see [SCIM setup documentation](../user/group/saml_sso/scim_setup.md#requirements).
+To use this API, [Group SSO](../user/group/saml_sso/index.md) must be enabled for the group.
+This API is only in use where [SCIM for Group SSO](../user/group/saml_sso/scim_setup.md) is enabled. It's a prerequisite to the creation of SCIM identities.
## Get a list of SAML users
-NOTE: **Note:**
-This endpoint is used as part of the SCIM syncing mechanism and it only returns
+This endpoint is used as part of the SCIM syncing mechanism. It only returns
a single user based on a unique ID which should match the `extern_uid` of the user.
```plaintext
diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md
index dcc3c21842b..1be40a0add0 100644
--- a/doc/ci/cloud_deployment/index.md
+++ b/doc/ci/cloud_deployment/index.md
@@ -282,10 +282,10 @@ When running your project pipeline at this point:
on the related JSON object's content. The deployment job finishes whenever the deployment to EC2
is done or has failed.
-#### Deploy Amazon EKS
+### Deploy to Amazon EKS
- [How to deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/)
-#### Deploy to Google Cloud
+## Deploy to Google Cloud
- [Deploying with GitLab on Google Cloud](https://about.gitlab.com/solutions/google-cloud-platform/)
diff --git a/doc/ci/environments/img/protected_access_group_v13_6.png b/doc/ci/environments/img/protected_access_group_v13_6.png
new file mode 100644
index 00000000000..a67a332b7e5
--- /dev/null
+++ b/doc/ci/environments/img/protected_access_group_v13_6.png
Binary files differ
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
index b965c04bb8f..eeb95947ba1 100644
--- a/doc/ci/environments/protected_environments.md
+++ b/doc/ci/environments/protected_environments.md
@@ -45,6 +45,61 @@ To protect an environment:
The protected environment will now appear in the list of protected environments.
+### Use the API to protect an environment
+
+Alternatively, you can use the API to protect an environment:
+
+1. Use a project with a CI that creates an environment. For example:
+
+ ```yaml
+ stages:
+ - test
+ - deploy
+
+ test:
+ stage: test
+ script:
+ - 'echo "Testing Application: ${CI_PROJECT_NAME}"'
+
+ production:
+ stage: deploy
+ when: manual
+ script:
+ - 'echo "Deploying to ${CI_ENVIRONMENT_NAME}"'
+ environment:
+ name: ${CI_JOB_NAME}
+ ```
+
+1. Use the UI to [create a new group](../../user/group/index.md#create-a-new-group).
+ For example, this group is called `protected-access-group` and has the group ID `9899826`. Note
+ that the rest of the examples in these steps use this group.
+
+ ![Group Access](img/protected_access_group_v13_6.png)
+
+1. Use the API to add a user to the group as a reporter:
+
+ ```shell
+ $ curl --request POST --header "PRIVATE-TOKEN: xxxxxxxxxxxx" --data "user_id=3222377&access_level=20" "https://gitlab.com/api/v4/groups/9899826/members"
+
+ {"id":3222377,"name":"Sean Carroll","username":"sfcarroll","state":"active","avatar_url":"https://assets.gitlab-static.net/uploads/-/system/user/avatar/3222377/avatar.png","web_url":"https://gitlab.com/sfcarroll","access_level":20,"created_at":"2020-10-26T17:37:50.309Z","expires_at":null}
+ ```
+
+1. Use the API to add the group to the project as a reporter:
+
+ ```shell
+ $ curl --request POST --header "PRIVATE-TOKEN: xxxxxxxxxxxx" --request POST "https://gitlab.com/api/v4/projects/22034114/share?group_id=9899826&group_access=20"
+
+ {"id":1233335,"project_id":22034114,"group_id":9899826,"group_access":20,"expires_at":null}
+ ```
+
+1. Use the API to add the group with protected environment access:
+
+ ```shell
+ curl --header 'Content-Type: application/json' --request POST --data '{"name": "production", "deploy_access_levels": [{"group_id": 9899826}]}' --header "PRIVATE-TOKEN: xxxxxxxxxxx" "https://gitlab.com/api/v4/projects/22034114/protected_environments"
+ ```
+
+The group now has access and can be seen in the UI.
+
## Environment access by group membership
A user may be granted access to protected environments as part of
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index ac9cda4e46c..6e95bcc5df2 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -128,6 +128,12 @@ This helps you avoid having to add the `only:` rule to all of your jobs to make
them always run. You can use this format to set up a Review App, helping to
save resources.
+### Using SAST, DAST, and other Secure Templates with Pipelines for Merge Requests
+
+To use [Secure templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/Security)
+with pipelines for merge requests, you may need to apply a `rules: if: merge_request_event` for the
+Secure scans to run in the same pipeline as the commit.
+
#### Excluding certain branches
Pipelines for merge requests require special treatment when
diff --git a/doc/development/adding_service_component.md b/doc/development/adding_service_component.md
index 33f63ac7ffe..d2bab4a2a07 100644
--- a/doc/development/adding_service_component.md
+++ b/doc/development/adding_service_component.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Adding a new Service Component to GitLab
The GitLab product is made up of several service components that run as independent system processes in communication with each other. These services can be run on the same instance, or spread across different instances. A list of the existing components can be found in the [GitLab architecture overview](architecture.md).
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index cdd1cb2838f..fcdda7f8138 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# API style guide
This style guide recommends best practices for API development.
diff --git a/doc/development/application_limits.md b/doc/development/application_limits.md
index f96ed2e7f57..4ae238cbbf2 100644
--- a/doc/development/application_limits.md
+++ b/doc/development/application_limits.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Application limits development
This document provides a development guide for contributors to add application
diff --git a/doc/development/application_secrets.md b/doc/development/application_secrets.md
index 24755586cf8..abc5ff7b985 100644
--- a/doc/development/application_secrets.md
+++ b/doc/development/application_secrets.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Application secrets
This page is a development guide for application secrets.
diff --git a/doc/development/approval_rules.md b/doc/development/approval_rules.md
index f295c20a36f..fb4546371fe 100644
--- a/doc/development/approval_rules.md
+++ b/doc/development/approval_rules.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Approval Rules **(STARTER)**
This document explains the backend design and flow of all related functionality
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 513af491576..761b4bd3cfb 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab architecture overview
## Software delivery
diff --git a/doc/development/chaos_endpoints.md b/doc/development/chaos_endpoints.md
index adda9e6199f..75354c4b4db 100644
--- a/doc/development/chaos_endpoints.md
+++ b/doc/development/chaos_endpoints.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Generating chaos in a test GitLab instance
As [Werner Vogels](https://twitter.com/Werner), the CTO at Amazon Web Services, famously put it, **Everything fails, all the time**.
diff --git a/doc/development/code_comments.md b/doc/development/code_comments.md
index b0e83b29cb0..d9ab719d18a 100644
--- a/doc/development/code_comments.md
+++ b/doc/development/code_comments.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Code comments
Whenever you add comment to the code that is expected to be addressed at any time
diff --git a/doc/development/code_intelligence/index.md b/doc/development/code_intelligence/index.md
index bd11f0bff79..24abf57e9d6 100644
--- a/doc/development/code_intelligence/index.md
+++ b/doc/development/code_intelligence/index.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Source Code
+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
+---
+
# Code Intelligence
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/1576) in GitLab 13.1.
diff --git a/doc/development/creating_enums.md b/doc/development/creating_enums.md
index af9bf919b2b..559a79700d2 100644
--- a/doc/development/creating_enums.md
+++ b/doc/development/creating_enums.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Creating enums
When creating a new enum, it should use the database type `SMALLINT`.
diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md
index 6fda394c10d..4feec5c093a 100644
--- a/doc/development/dangerbot.md
+++ b/doc/development/dangerbot.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Danger bot
The GitLab CI/CD pipeline includes a `danger-review` job that uses [Danger](https://github.com/danger/danger)
diff --git a/doc/development/database/setting_multiple_values.md b/doc/development/database/setting_multiple_values.md
index fda502eefbc..86cf80007f6 100644
--- a/doc/development/database/setting_multiple_values.md
+++ b/doc/development/database/setting_multiple_values.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Setting Multiple Values
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32921) in GitLab 13.5.
diff --git a/doc/development/deleting_migrations.md b/doc/development/deleting_migrations.md
index b8f23019ac2..df36715fa6a 100644
--- a/doc/development/deleting_migrations.md
+++ b/doc/development/deleting_migrations.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Delete existing migrations
When removing existing migrations from the GitLab project, you have to take into account
diff --git a/doc/development/deprecation_guidelines/index.md b/doc/development/deprecation_guidelines/index.md
index 1ee22644bbc..5cc408bb57b 100644
--- a/doc/development/deprecation_guidelines/index.md
+++ b/doc/development/deprecation_guidelines/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Deprecation guidelines
This page includes information about how and when to remove or make breaking
diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
index 9fce9b4e4b3..e4aa48edb34 100644
--- a/doc/development/documentation/site_architecture/global_nav.md
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
description: "Learn how GitLab docs' global navigation works and how to add new items."
---
diff --git a/doc/development/documentation/site_architecture/release_process.md b/doc/development/documentation/site_architecture/release_process.md
index d04d34ff786..bb99f2522e0 100644
--- a/doc/development/documentation/site_architecture/release_process.md
+++ b/doc/development/documentation/site_architecture/release_process.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab Docs monthly release process
When a new GitLab version is released on the 22nd, we need to create the respective
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 836cebd006c..c3a232a58e6 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Guidelines for implementing Enterprise Edition features
- **Write the code and the tests.**: As with any code, EE features should have
diff --git a/doc/development/emails.md b/doc/development/emails.md
index de9607fef64..4b43bff0e02 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Dealing with email in development
## Ensuring compatibility with mailer Sidekiq jobs
diff --git a/doc/development/experiment_guide/index.md b/doc/development/experiment_guide/index.md
index 18b606450c2..b62d4a60c59 100644
--- a/doc/development/experiment_guide/index.md
+++ b/doc/development/experiment_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Experiment Guide
Experiments can be conducted by any GitLab team, most often the teams from the [Growth Sub-department](https://about.gitlab.com/handbook/engineering/development/growth/). Experiments are not tied to releases because they will primarily target GitLab.com.
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index 669c93eb251..8cb61019556 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Accessibility & Readability
## Resources
diff --git a/doc/development/fe_guide/architecture.md b/doc/development/fe_guide/architecture.md
index c7e1ba59f23..4b45fb97c76 100644
--- a/doc/development/fe_guide/architecture.md
+++ b/doc/development/fe_guide/architecture.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Architecture
When you are developing a new feature that requires architectural design, or if
diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md
index 38a8c8f1086..856b03e4b47 100644
--- a/doc/development/fe_guide/axios.md
+++ b/doc/development/fe_guide/axios.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Axios
We use [Axios](https://github.com/axios/axios) to communicate with the server in Vue applications and most new code.
diff --git a/doc/development/fe_guide/dependencies.md b/doc/development/fe_guide/dependencies.md
index ba97e7e39be..bbe4cdc285d 100644
--- a/doc/development/fe_guide/dependencies.md
+++ b/doc/development/fe_guide/dependencies.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Frontend dependencies
## Package manager
diff --git a/doc/development/fe_guide/design_patterns.md b/doc/development/fe_guide/design_patterns.md
index 5b7b16a9a8a..ed4d91f34bd 100644
--- a/doc/development/fe_guide/design_patterns.md
+++ b/doc/development/fe_guide/design_patterns.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Design Patterns
## Singletons
diff --git a/doc/development/fe_guide/development_process.md b/doc/development/fe_guide/development_process.md
index 2b64534e7c9..915dab06ac2 100644
--- a/doc/development/fe_guide/development_process.md
+++ b/doc/development/fe_guide/development_process.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Frontend Development Process
You can find more about the organization of the frontend team in the [handbook](https://about.gitlab.com/handbook/engineering/frontend/).
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index 83bc4216403..6648ecf4dbf 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# DropLab
A generic dropdown for all of your custom dropdown needs.
diff --git a/doc/development/fe_guide/droplab/plugins/ajax.md b/doc/development/fe_guide/droplab/plugins/ajax.md
index f22d95064dd..88e83eb57e6 100644
--- a/doc/development/fe_guide/droplab/plugins/ajax.md
+++ b/doc/development/fe_guide/droplab/plugins/ajax.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Ajax
`Ajax` is a droplab plugin that allows for retrieving and rendering list data from a server.
diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md
index e8194e45a41..f3b1ebc6d2b 100644
--- a/doc/development/fe_guide/droplab/plugins/filter.md
+++ b/doc/development/fe_guide/droplab/plugins/filter.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Filter
`Filter` is a plugin that allows for filtering data that has been added
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index b873b7a14ee..41010944a1b 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# InputSetter
`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked.
diff --git a/doc/development/fe_guide/editor_lite.md b/doc/development/fe_guide/editor_lite.md
new file mode 100644
index 00000000000..c27fbe5ba8d
--- /dev/null
+++ b/doc/development/fe_guide/editor_lite.md
@@ -0,0 +1,218 @@
+# Editor Lite
+
+## Background
+
+**Editor Lite** is a technological product driving [Web Editor](../../user/project/repository/web_editor.md), [Snippets](../../user/snippets.md), [CI Linter](../../ci/lint.md), etc. Editor Lite is the driving technology for any single-file editing experience across the product.
+
+Editor Lite is a thin wrapper around [the Monaco editor](https://microsoft.github.io/monaco-editor/index.html) that provides the necessary helpers and abstractions and extends Monaco using extensions.
+
+## How to use Editor Lite
+
+Editor Lite is framework-agnostic and can be used in any application, whether it's Rails or Vue. For the convenience of integration, we have [the dedicated `<editor-lite>` Vue component](#vue-component), but in general, the integration of Editor Lite is pretty straightforward:
+
+1. Import Editor Lite:
+
+```javascript
+import EditorLite from '~/editor/editor_lite';
+```
+
+1. Initialize global editor for the view:
+
+```javascript
+const editor = new EditorLite({
+ // Editor Options.
+ // The list of all accepted options can be found at
+ // https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html
+});
+```
+
+1. Create an editor's instance:
+
+```javascript
+editor.createInstance({
+ // Editor Lite configuration options.
+})
+```
+
+An instance of Editor Lite accepts the following configuration options:
+
+| Option | Required? | Description |
+| ---- | ---- | ---- |
+| `el` | `true` | `HTML Node`: element on which to render the editor |
+| `blobPath` | `false` | `String`: the name of a file to render in the editor. It is used to identify the correct syntax highlighter to use with that or another file type. Can accept wildcard as in `*.js` when the actual filename isn't known or doesn't play any role |
+| `blobContent` | `false` | `String`: the initial content to be rendered in the editor |
+| `extensions` | `false` | `Array`: extensions to use in this instance |
+| `blobGlobalId` | `false` | `String`: auto-generated property.</br>**Note:** this prop might go away in the future. Do not pass `blobGlobalId` unless you know what you're doing.|
+| [Editor Options](https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html) | `false` | `Object(s)`: any prop outside of the list above is treated as an Editor Option for this particular instance. This way, one can override global Editor Options on the instance level. |
+
+## API
+
+The editor follows the same public API as [provided by Monaco editor](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) with just a few additional functions on the instance level:
+
+| Function | Arguments | Description
+| ----- | ----- | ----- |
+| `updateModelLanguage` | `path`: String | Updates the instance's syntax highlighting to follow the extension of the passed `path`. Available only on _instance_ level|
+| `use` | Array of objects | Array of **extensions** to apply to the instance. Note: `use` accepts only the array of _objects_, which means that the extensions' ES6 modules should be fetched and resolved in your views/components before being passed to `use`. This prop is available on _instance_ (applies extension to this particular instance) and _global edtor_ (applies the same extension to all instances) levels. |
+| Monaco Editor options | See [documentation](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) | Default Monaco editor options |
+
+## Tips
+
+1. Editor's loading state.
+
+Editor Lite comes with the loading state built-in, making spinners and loaders rarely needed in HTML. To benefit the built-in loading state, set the `data-editor-loading` property on the HTML element that is supposed to contain the editor. Editor Lite will show the loader automatically while it's bootstrapping.
+![Editor Lite: loading state](img/editor_lite_loading.png)
+
+1. Update syntax highlighting if the file name changes.
+
+```javascript
+// fileNameEl here is the HTML input element that contains the file name
+fileNameEl.addEventListener('change', () => {
+ this.editor.updateModelLanguage(fileNameEl.value);
+});
+```
+
+1. Get the editor's content.
+
+We might set up listeners on the editor for every change but it rapidly can become an expensive operation. Instead , we can get editor's content when it's needed. For example on a form's submission:
+
+```javascript
+form.addEventListener('submit', () => {
+ my_content_variable = this.editor.getValue();
+});
+```
+
+1. Performance
+
+Even though Editor Lite itself is extremely slim, it still depends on Monaco editor. Monaco is not an easily tree-shakeable module. Hence, every time you add Editor Lite to a view, the JavaScript bundle's size significantly increases, affecting your view's loading performance. To avoid that, it is recommended to import the editor on demand on those views where it is not 100% certain that the editor will be used. Or if the editor is a secondary element of the view. Loading Editor Lite on demand is no different from loading any other module:
+
+```javascript
+someActionFunction() {
+ import(/* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite').
+ then(({ default: EditorLite }) => {
+ const editor = new EditorLite();
+ ...
+ });
+ ...
+}
+```
+
+## Extensions
+
+Editor Lite has been built to provide a universal, extensible editing tool to the whole product, which would not depend on any particular group. Even though the Editor Lite's core is owned by [Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor-fe/), the main functional elements — extensions — can be owned by any group. Editor Lite extensions' main idea is that the core of the editor remains very slim and stable. At the same time, whatever new functionality is needed can be added as an extension to this core, without touching the core itself. It allows any group to build and own any new editing functionality without being afraid of it being broken or overridden with the Editor Lite changes.
+
+Structurally, the complete implementation of Editor Lite could be presented as the following diagram:
+
+```mermaid
+graph TD;
+ B[Extension 1]---A[Editor Lite]
+ C[Extension 2]---A[Editor Lite]
+ D[Extension 3]---A[Editor Lite]
+ E[...]---A[Editor Lite]
+ F[Extension N]---A[Editor Lite]
+ A[Editor Lite]---Z[Monaco]
+```
+
+Technically, an extension is just an ES6 module that exports a JavaScript object:
+
+```javascript
+import { Position } from 'monaco-editor';
+
+export default {
+ navigateFileStart() {
+ this.setPosition(new Position(1, 1));
+ },
+};
+
+```
+
+Important things to note here:
+
+- We can depend on other modules in our extensions. This organization helps keep the size of Editor Lite's core at bay by importing dependencies only when needed.
+- `this` in extension's functions refers to the current Editor Lite instance. Using `this`, you get access to the complete instance's API, such as the `setPosition()` method in this particular case.
+
+### Using an existing extension
+
+Adding an extension to Editor Lite's instance is simple:
+
+```javascript
+import EditorLite from '~/editor/editor_lite';
+import MyExtension from '~/my_extension';
+
+const editor = new EditorLite().createInstance({
+ ...
+});
+editor.use(MyExtension);
+```
+
+### Creating an extension
+
+Let's create our first Editor Lite extension. As aforementioned, extensions are ES6 modules exporting the simple `Object` that is used to extend Editor Lite's functionality. As the most straightforward test, let's create an extension that extends Editor Lite with a new function that, when called, will output editor's content in `alert`.
+
+`~/my_folder/my_fancy_extension.js:`
+
+```javascript
+export default {
+ throwContentAtMe() {
+ alert(this.getValue());
+ },
+};
+```
+
+And that's it with our extension! Note that we're using `this` as a reference to the instance. And through it, we get access to the complete underlying [Monaco editor API](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) like `getValue()` in this case.
+
+Now let's use our extension:
+
+`~/my_folder/component_bundle.js`:
+
+```javascript
+import EditorLite from '~/editor/editor_lite';
+import MyFancyExtension from './my_fancy_extension';
+
+const editor = new EditorLite().createInstance({
+ ...
+});
+editor.use(MyFancyExtension);
+...
+someButton.addEventListener('click', () => {
+ editor.throwContentAtMe();
+});
+```
+
+First of all, we import Editor Lite and our new extension. Then we create the editor and its instance. By default Editor Lite has no `throwContentAtMe` method. But the `editor.use(MyFancyExtension)` line brings that method to our instance. After that, we can use it any time we need it. In this case, we call it when some theoretical button has been clicked.
+
+This script would result in an alert containing the editor's content when `someButton` is clicked.
+
+![Editor Lite new extension's result](img/editor_lite_create_ext.png)
+
+### Tips
+
+1. Performance
+
+Just like Editor Lite itself, any extension can be loaded on demand to not harm loading performance of the views:
+
+```javascript
+const EditorPromise = import(
+ /* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite'
+);
+const MarkdownExtensionPromise = import('~/editor/editor_markdown_ext');
+
+Promise.all([EditorPromise, MarkdownExtensionPromise])
+ .then(([{ default: EditorLite }, { default: MarkdownExtension }]) => {
+ const editor = new EditorLite().createInstance({
+ ...
+ });
+ editor.use(MarkdownExtension);
+ });
+```
+
+1. Using multiple extensions
+
+Just pass the array of extensions to your `use` method:
+
+```javascript
+editor.use([FileTemplateExtension, MyFancyExtension]);
+```
+
+## <a id="vue-component"></a>`<editor-lite>` Vue component
+
+TBD
diff --git a/doc/development/fe_guide/emojis.md b/doc/development/fe_guide/emojis.md
index 3cd14c0dfd3..9b1abaa14ae 100644
--- a/doc/development/fe_guide/emojis.md
+++ b/doc/development/fe_guide/emojis.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Emojis
GitLab supports native Unicode emojis and falls back to image-based emojis selectively
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
index 0a26fdba934..5881d4f2d4f 100644
--- a/doc/development/fe_guide/frontend_faq.md
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Frontend FAQ
## Rules of Frontend FAQ
diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md
index ef559638871..994a3aa36fc 100644
--- a/doc/development/fe_guide/icons.md
+++ b/doc/development/fe_guide/icons.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Icons and SVG Illustrations
We manage our own icon and illustration library in the [`gitlab-svgs`](https://gitlab.com/gitlab-org/gitlab-svgs)
diff --git a/doc/development/fe_guide/img/editor_lite_create_ext.png b/doc/development/fe_guide/img/editor_lite_create_ext.png
new file mode 100644
index 00000000000..d341eec3e96
--- /dev/null
+++ b/doc/development/fe_guide/img/editor_lite_create_ext.png
Binary files differ
diff --git a/doc/development/fe_guide/img/editor_lite_loading.png b/doc/development/fe_guide/img/editor_lite_loading.png
new file mode 100644
index 00000000000..183a9b02429
--- /dev/null
+++ b/doc/development/fe_guide/img/editor_lite_loading.png
Binary files differ
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index f909866d44e..9f98876bc8e 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Frontend Development Guidelines
This document describes various guidelines to ensure consistency and quality
diff --git a/doc/development/fe_guide/keyboard_shortcuts.md b/doc/development/fe_guide/keyboard_shortcuts.md
index da9b3702a8d..227b5cfd22c 100644
--- a/doc/development/fe_guide/keyboard_shortcuts.md
+++ b/doc/development/fe_guide/keyboard_shortcuts.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Implementing keyboard shortcuts
We use [Mousetrap](https://craig.is/killing/mice) to implement keyboard
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 5d2b699c40d..6fff6faf04f 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Performance
## Best Practices
diff --git a/doc/development/fe_guide/principles.md b/doc/development/fe_guide/principles.md
index 2bef48fddcf..75954a5f988 100644
--- a/doc/development/fe_guide/principles.md
+++ b/doc/development/fe_guide/principles.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Principles
These principles will ensure that your frontend contribution starts off in the right direction.
diff --git a/doc/development/fe_guide/security.md b/doc/development/fe_guide/security.md
index a001dd83ab7..64095ce3a60 100644
--- a/doc/development/fe_guide/security.md
+++ b/doc/development/fe_guide/security.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Security
## Resources
diff --git a/doc/development/fe_guide/style/html.md b/doc/development/fe_guide/style/html.md
index 1445da3f0e1..61a724cf3c7 100644
--- a/doc/development/fe_guide/style/html.md
+++ b/doc/development/fe_guide/style/html.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# HTML style guide
## Buttons
diff --git a/doc/development/fe_guide/style/index.md b/doc/development/fe_guide/style/index.md
index 4ca409664de..0d73b16b5d3 100644
--- a/doc/development/fe_guide/style/index.md
+++ b/doc/development/fe_guide/style/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab development style guides
See below for the relevant style guides, guidelines, linting, and other information for developing GitLab.
diff --git a/doc/development/fe_guide/style/javascript.md b/doc/development/fe_guide/style/javascript.md
index 2a06a473878..363813da077 100644
--- a/doc/development/fe_guide/style/javascript.md
+++ b/doc/development/fe_guide/style/javascript.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
disqus_identifier: 'https://docs.gitlab.com/ee/development/fe_guide/style_guide_js.html'
---
diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md
index dba39eeb98c..0b5c4a2142b 100644
--- a/doc/development/fe_guide/style/scss.md
+++ b/doc/development/fe_guide/style/scss.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
disqus_identifier: 'https://docs.gitlab.com/ee/development/fe_guide/style_guide_scss.html'
---
diff --git a/doc/development/fe_guide/style/vue.md b/doc/development/fe_guide/style/vue.md
index 1a64db443bc..c2f6b1e1b8f 100644
--- a/doc/development/fe_guide/style/vue.md
+++ b/doc/development/fe_guide/style/vue.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Vue.js style guide
## Linting
@@ -400,6 +406,199 @@ Useful links:
$('span').tooltip('_fixTitle');
```
+## Vue testing
+
+Over time, a number of programming patterns and style preferences have emerged in our efforts to effectively test Vue components.
+The following guide describes some of these. **These are not strict guidelines**, but rather a collection of suggestions and
+good practices that aim to provide insight into how we write Vue tests at GitLab.
+
+### Mounting a component
+
+Typically, when testing a Vue component, the component should be "re-mounted" in every test block.
+
+To achieve this:
+
+1. Create a mutable `wrapper` variable inside the top-level `describe` block.
+1. Mount the component using [`mount`](https://vue-test-utils.vuejs.org/api/#mount)/[`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount).
+1. Reassign the resulting [`Wrapper`](https://vue-test-utils.vuejs.org/api/wrapper/#wrapper) instance to our `wrapper` variable.
+
+Creating a global, mutable wrapper provides a number of advantages, including the ability to:
+
+- Define common functions for finding components/DOM elements:
+
+ ```javascript
+ import MyComponent from '~/path/to/my_component.vue';
+ describe('MyComponent', () => {
+ let wrapper;
+
+ // this can now be reused across tests
+ const findMyComponent = wrapper.find(MyComponent);
+ // ...
+ })
+ ```
+
+- Use a `beforeEach` block to mount the component (see [the `createComponent` factory](#the-createcomponent-factory) for more information).
+- Use an `afterEach` block to destroy the component, for example, `wrapper.destroy()`.
+
+#### The `createComponent` factory
+
+To avoid duplicating our mounting logic, it's useful to define a `createComponent` factory function
+that we can reuse in each test block. This is a closure which should reassign our `wrapper` variable
+to the result of [`mount`](https://vue-test-utils.vuejs.org/api/#mount) and [`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount):
+
+```javascript
+import MyComponent from '~/path/to/my_component.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('MyComponent', () => {
+ // Initiate the "global" wrapper variable. This will be used throughout our test:
+ let wrapper;
+
+ // Define our `createComponent` factory:
+ function createComponent() {
+ // Mount component and reassign `wrapper`:
+ wrapper = shallowMount(MyComponent);
+ }
+
+ it('mounts', () => {
+ createComponent();
+
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('`isLoading` prop defaults to `false`', () => {
+ createComponent();
+
+ expect(wrapper.props('isLoading')).toBe(false);
+ });
+})
+```
+
+Similarly, we could further de-duplicate our test by calling `createComponent` in a `beforeEach` block:
+
+```javascript
+import MyComponent from '~/path/to/my_component.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('MyComponent', () => {
+ // Initiate the "global" wrapper variable. This will be used throughout our test
+ let wrapper;
+
+ // define our `createComponent` factory
+ function createComponent() {
+ // mount component and reassign `wrapper`
+ wrapper = shallowMount(MyComponent);
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('mounts', () => {
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('`isLoading` prop defaults to `false`', () => {
+ expect(wrapper.props('isLoading')).toBe(false);
+ });
+})
+```
+
+#### `createComponent` best practices
+
+1. Consider using a single (or a limited number of) object arguments over many arguments.
+ Defining single parameters for common data like `props` is okay,
+ but keep in mind our [JavaScript style guide](javascript.md#limit-number-of-parameters) and stay within the parameter number limit:
+
+ ```javascript
+ // bad
+ function createComponent(data, props, methods, isLoading, mountFn) { }
+
+ // good
+ function createComponent({ data, props, methods, stubs, isLoading } = {}) { }
+
+ // good
+ function createComponent(props = {}, { data, methods, stubs, isLoading } = {}) { }
+ ```
+
+1. If you require both `mount` _and_ `shallowMount` within the same set of tests, it
+can be useful define a `mountFn` parameter for the `createComponent` factory that accepts
+the mounting function (`mount` or `shallowMount`) to be used to mount the component:
+
+ ```javascript
+ import { shallowMount } from '@vue/test-utils';
+
+ function createComponent({ mountFn = shallowMount } = {}) { }
+ ```
+
+### Setting component state
+
+1. Avoid using [`setProps`](https://vue-test-utils.vuejs.org/api/wrapper/#setprops) to set
+component state wherever possible. Instead, set the component's
+[`propsData`](https://vue-test-utils.vuejs.org/api/options.html#propsdata) when mounting the component:
+
+ ```javascript
+ // bad
+ wrapper = shallowMount(MyComponent);
+ wrapper.setProps({
+ myProp: 'my cool prop'
+ });
+
+ // good
+ wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
+ ```
+
+ The exception here is when you wish to test component reactivity in some way.
+ For example, you may want to test the output of a component when after a particular watcher has executed.
+ Using `setProps` to test such behavior is okay.
+
+### Accessing component state
+
+1. When accessing props or attributes, prefer the `wrapper.props('myProp')` syntax over `wrapper.props().myProp`:
+
+ ```javascript
+ // good
+ expect(wrapper.props().myProp).toBe(true);
+ expect(wrapper.attributes().myAttr).toBe(true);
+
+ // better
+ expect(wrapper.props('myProp').toBe(true);
+ expect(wrapper.attributes('myAttr')).toBe(true);
+ ```
+
+1. When asserting multiple props, check the deep equality of the `props()` object with [`toEqual`](https://jestjs.io/docs/en/expect#toequalvalue):
+
+ ```javascript
+ // good
+ expect(wrapper.props('propA')).toBe('valueA');
+ expect(wrapper.props('propB')).toBe('valueB');
+ expect(wrapper.props('propC')).toBe('valueC');
+
+ // better
+ expect(wrapper.props()).toEqual({
+ propA: 'valueA',
+ propB: 'valueB',
+ propC: 'valueC',
+ });
+ ```
+
+1. If you are only interested in some of the props, you can use [`toMatchObject`](https://jestjs.io/docs/en/expect#tomatchobjectobject).
+Prefer `toMatchObject` over [`expect.objectContaining`](https://jestjs.io/docs/en/expect#expectobjectcontainingobject):
+
+ ```javascript
+ // good
+ expect(wrapper.props()).toEqual(expect.objectContaining({
+ propA: 'valueA',
+ propB: 'valueB',
+ }));
+
+ // better
+ expect(wrapper.props()).toMatchObject({
+ propA: 'valueA',
+ propB: 'valueB',
+ });
+ ```
+
## The JavaScript/Vue Accord
The goal of this accord is to make sure we are all on the same page.
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 58a8332589d..2a9e35da116 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Vue
To get started with Vue, read through [their documentation](https://vuejs.org/v2/guide/).
@@ -182,10 +188,13 @@ Check this [page](vuex.md) for more details.
## Style guide
Please refer to the Vue section of our [style guide](style/vue.md)
-for best practices while writing your Vue components and templates.
+for best practices while writing and testing your Vue components and templates.
## Testing Vue Components
+Please refer to the [Vue testing style guide](style/vue.md#vue-testing)
+for guidelines and best practices for testing your Vue components.
+
Each Vue component has a unique output. This output is always present in the render function.
Although we can test each method of a Vue component individually, our goal must be to test the output
diff --git a/doc/development/fe_guide/vue3_migration.md b/doc/development/fe_guide/vue3_migration.md
index afdb2690c02..fd7fc317b6d 100644
--- a/doc/development/fe_guide/vue3_migration.md
+++ b/doc/development/fe_guide/vue3_migration.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Migration to Vue 3
In order to prepare for the eventual migration to Vue 3.x, we should be wary about adding the following features to the codebase:
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 528dcb3b7f4..e3a01aef523 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Vuex
When there's a clear benefit to separating state management from components (e.g. due to state complexity) we recommend using [Vuex](https://vuex.vuejs.org) over any other Flux pattern. Otherwise, feel free to manage state within the components.
diff --git a/doc/development/features_inside_dot_gitlab.md b/doc/development/features_inside_dot_gitlab.md
index cb883471adf..e8918c76d04 100644
--- a/doc/development/features_inside_dot_gitlab.md
+++ b/doc/development/features_inside_dot_gitlab.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Features inside the `.gitlab/` directory
We have implemented standard features that depend on configuration files in the `.gitlab/` directory. You can find `.gitlab/` in various GitLab repositories.
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index e8ae5a11d48..2b8f657a1e0 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# File Storage in GitLab
We use the [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) gem to handle file upload, store and retrieval.
diff --git a/doc/development/foreign_keys.md b/doc/development/foreign_keys.md
index 8a81dc158a7..f6e092e6d20 100644
--- a/doc/development/foreign_keys.md
+++ b/doc/development/foreign_keys.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Foreign Keys & Associations
When adding an association to a model you must also add a foreign key. For
diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md
index 8d93c52e7bc..1daa3ad5cba 100644
--- a/doc/development/gemfile.md
+++ b/doc/development/gemfile.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# `Gemfile` guidelines
When adding a new entry to `Gemfile` or upgrading an existing dependency pay
diff --git a/doc/development/go_guide/dependencies.md b/doc/development/go_guide/dependencies.md
index b85344635c6..461ee394533 100644
--- a/doc/development/go_guide/dependencies.md
+++ b/doc/development/go_guide/dependencies.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Dependency Management in Go
Go takes an unusual approach to dependency management, in that it is
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index cc3db267d53..07cbfbaf2f3 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Gotchas
The purpose of this guide is to document potential "gotchas" that contributors
diff --git a/doc/development/graphql_guide/index.md b/doc/development/graphql_guide/index.md
index 9d7fb5ba0a8..12b4f9796c7 100644
--- a/doc/development/graphql_guide/index.md
+++ b/doc/development/graphql_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GraphQL development guidelines
This guide contains all the information to successfully contribute to GitLab's
diff --git a/doc/development/graphql_guide/pagination.md b/doc/development/graphql_guide/pagination.md
index 287356681ab..fc4b1a1045c 100644
--- a/doc/development/graphql_guide/pagination.md
+++ b/doc/development/graphql_guide/pagination.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GraphQL pagination
## Types of pagination
diff --git a/doc/development/hash_indexes.md b/doc/development/hash_indexes.md
index 1ed76e35f69..dcf6eb1c8dc 100644
--- a/doc/development/hash_indexes.md
+++ b/doc/development/hash_indexes.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Hash Indexes
PostgreSQL supports hash indexes besides the regular B-tree
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 59399e54c3e..7fbde3036a6 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Internationalization for GitLab
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10669) in GitLab 9.2.
diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md
index 2d84fe4536f..070a55e7a34 100644
--- a/doc/development/i18n/index.md
+++ b/doc/development/i18n/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Translate GitLab to your language
The text in GitLab's user interface is in American English by default.
diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md
index 8b3357c41d3..4cd1f8deef0 100644
--- a/doc/development/i18n/merging_translations.md
+++ b/doc/development/i18n/merging_translations.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Merging translations from CrowdIn
CrowdIn automatically syncs the `gitlab.pot` file with the CrowdIn service, presenting
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 1916f96801d..74a8267354a 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Proofread Translations
Most translations are contributed, reviewed, and accepted by the community. We
diff --git a/doc/development/i18n/translation.md b/doc/development/i18n/translation.md
index ed205c20c0d..a98d6d758f6 100644
--- a/doc/development/i18n/translation.md
+++ b/doc/development/i18n/translation.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Translating GitLab
For managing the translation process we use [CrowdIn](https://crowdin.com).
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index 8d2be1baf24..59228126d7b 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Import/Export development documentation
Troubleshooting and general development guidelines and tips for the [Import/Export feature](../user/project/settings/import_export.md).
diff --git a/doc/development/import_project.md b/doc/development/import_project.md
index 9e2f5af6738..5fca76bc9a8 100644
--- a/doc/development/import_project.md
+++ b/doc/development/import_project.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Test Import Project
For testing, we can import our own [GitLab CE](https://gitlab.com/gitlab-org/gitlab-foss/) project (named `gitlabhq` in this case) under a group named `qa-perf-testing`. Project tarballs that can be used for testing can be found over on the [performance-data](https://gitlab.com/gitlab-org/quality/performance-data) project. A different project could be used if required.
diff --git a/doc/development/insert_into_tables_in_batches.md b/doc/development/insert_into_tables_in_batches.md
index f65d2478d2e..2b411f98d20 100644
--- a/doc/development/insert_into_tables_in_batches.md
+++ b/doc/development/insert_into_tables_in_batches.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
description: "Sometimes it is necessary to store large amounts of records at once, which can be inefficient
when iterating collections and performing individual `save`s. With the arrival of `insert_all`
in Rails 6, which operates at the row level (that is, using `Hash`es), GitLab has added a set
diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md
index 1f9b03075f0..884681c8de6 100644
--- a/doc/development/integrations/jira_connect.md
+++ b/doc/development/integrations/jira_connect.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Setting up a development environment
The following are required to install and test the app:
diff --git a/doc/development/interacting_components.md b/doc/development/interacting_components.md
index 697c64986b1..87bbe30d9fd 100644
--- a/doc/development/interacting_components.md
+++ b/doc/development/interacting_components.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Developing against interacting components or features
It's not uncommon that a single code change can reflect and interact with multiple parts of GitLab
diff --git a/doc/development/iterating_tables_in_batches.md b/doc/development/iterating_tables_in_batches.md
index 56cbb3a0e9e..0b7ed09fd7b 100644
--- a/doc/development/iterating_tables_in_batches.md
+++ b/doc/development/iterating_tables_in_batches.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Iterating Tables In Batches
Rails provides a method called `in_batches` that can be used to iterate over
diff --git a/doc/development/licensed_feature_availability.md b/doc/development/licensed_feature_availability.md
index 4350c7fb4d4..f83a57fe1ca 100644
--- a/doc/development/licensed_feature_availability.md
+++ b/doc/development/licensed_feature_availability.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Licensed feature availability **(STARTER)**
As of GitLab 9.4, we've been supporting a simplified version of licensed
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 8cda3d5f361..b56b657af64 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab Licensing and Compatibility
[GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-foss/) (CE) is licensed [under the terms of the MIT License](https://gitlab.com/gitlab-org/gitlab-foss/blob/master/LICENSE). [GitLab Enterprise Edition](https://gitlab.com/gitlab-org/gitlab/) (EE) is licensed under "[The GitLab Enterprise Edition (EE) license](https://gitlab.com/gitlab-org/gitlab/blob/master/LICENSE)" wherein there are more restrictions.
diff --git a/doc/development/mass_insert.md b/doc/development/mass_insert.md
index c19850ca67e..dfffab564bc 100644
--- a/doc/development/mass_insert.md
+++ b/doc/development/mass_insert.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Mass inserting Rails models
Setting the environment variable [`MASS_INSERT=1`](rake_tasks.md#environment-variables)
diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md
index 2f084937cc9..a69494fcf60 100644
--- a/doc/development/merge_request_performance_guidelines.md
+++ b/doc/development/merge_request_performance_guidelines.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Merge Request Performance Guidelines
Each new introduced merge request **should be performant by default**.
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 71191d1d871..2fb4c96d6bf 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Migration Style Guide
When writing migrations for GitLab, you have to take into account that
diff --git a/doc/development/module_with_instance_variables.md b/doc/development/module_with_instance_variables.md
index 2229c99c99b..80e926f800c 100644
--- a/doc/development/module_with_instance_variables.md
+++ b/doc/development/module_with_instance_variables.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Modules with instance variables could be considered harmful
## Background
diff --git a/doc/development/namespaces_storage_statistics.md b/doc/development/namespaces_storage_statistics.md
index 5207276ba73..563b397ac2d 100644
--- a/doc/development/namespaces_storage_statistics.md
+++ b/doc/development/namespaces_storage_statistics.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Database case study: Namespaces storage statistics
## Introduction
diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/new_fe_guide/dependencies.md
index afdf6e27b37..fad004f9df5 100644
--- a/doc/development/new_fe_guide/dependencies.md
+++ b/doc/development/new_fe_guide/dependencies.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Dependencies
## Adding Dependencies
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
index 9c63ccad6e1..a714b5fbe57 100644
--- a/doc/development/new_fe_guide/development/accessibility.md
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Accessibility
Using semantic HTML plays a key role when it comes to accessibility.
diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/new_fe_guide/development/components.md
index b7233f5d7c0..1597d4e62e7 100644
--- a/doc/development/new_fe_guide/development/components.md
+++ b/doc/development/new_fe_guide/development/components.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Components
## Graphs
diff --git a/doc/development/new_fe_guide/development/index.md b/doc/development/new_fe_guide/development/index.md
index 119dbc58012..52435ca719d 100644
--- a/doc/development/new_fe_guide/development/index.md
+++ b/doc/development/new_fe_guide/development/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Development
## [Components](components.md)
diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/new_fe_guide/development/performance.md
index ad6fdc0b85c..44f5bccde95 100644
--- a/doc/development/new_fe_guide/development/performance.md
+++ b/doc/development/new_fe_guide/development/performance.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Performance
## Monitoring
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index 9e9c367807f..c9b655c2274 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Frontend Development Guidelines
This guide contains all the information to successfully contribute to GitLab's frontend.
diff --git a/doc/development/new_fe_guide/modules/dirty_submit.md b/doc/development/new_fe_guide/modules/dirty_submit.md
index dd336ad3a90..17cdab3f240 100644
--- a/doc/development/new_fe_guide/modules/dirty_submit.md
+++ b/doc/development/new_fe_guide/modules/dirty_submit.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Dirty Submit
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/21115) in GitLab 11.3.
diff --git a/doc/development/new_fe_guide/modules/index.md b/doc/development/new_fe_guide/modules/index.md
index 1752be6edcd..18c5b05432c 100644
--- a/doc/development/new_fe_guide/modules/index.md
+++ b/doc/development/new_fe_guide/modules/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Modules
- [DirtySubmit](dirty_submit.md)
diff --git a/doc/development/new_fe_guide/modules/widget_extensions.md b/doc/development/new_fe_guide/modules/widget_extensions.md
index 70d15743207..16f82beaece 100644
--- a/doc/development/new_fe_guide/modules/widget_extensions.md
+++ b/doc/development/new_fe_guide/modules/widget_extensions.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Merge request widget extensions
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44616) in GitLab 13.6.
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index c65266a3f25..fa00b8bee1e 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Tips
## Clearing production compiled assets
diff --git a/doc/development/newlines_styleguide.md b/doc/development/newlines_styleguide.md
index e298ba1d935..f123482fa5a 100644
--- a/doc/development/newlines_styleguide.md
+++ b/doc/development/newlines_styleguide.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Newlines style guide
This style guide recommends best practices for newlines in Ruby code.
diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md
index deaf72d2ecf..84c395e2e57 100644
--- a/doc/development/omnibus.md
+++ b/doc/development/omnibus.md
@@ -1,3 +1,9 @@
+---
+stage: Enablement
+group: Distribution
+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
+---
+
# What you should know about Omnibus packages
Most users install GitLab using our Omnibus packages. As a developer it can be
diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md
index 18788d0b86e..c6fcbd7d60f 100644
--- a/doc/development/ordering_table_columns.md
+++ b/doc/development/ordering_table_columns.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Ordering Table Columns in PostgreSQL
For GitLab we require that columns of new tables are ordered to use the
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 3b59393bae6..80933c7bbbc 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Performance Guidelines
This document describes various guidelines to follow to ensure good and
diff --git a/doc/development/permissions.md b/doc/development/permissions.md
index e930345caec..af427427cb6 100644
--- a/doc/development/permissions.md
+++ b/doc/development/permissions.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab permissions guide
There are multiple types of permissions across GitLab, and when implementing
diff --git a/doc/development/policies.md b/doc/development/policies.md
index 8dfd4763551..3b5b073c6a2 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# `DeclarativePolicy` framework
The DeclarativePolicy framework is designed to assist in performance of policy checks, and to enable ease of extension for EE. The DSL code in `app/policies` is what `Ability.allowed?` uses to check whether a particular action is allowed on a subject.
diff --git a/doc/development/polling.md b/doc/development/polling.md
index 47cfc32d934..b06507787b0 100644
--- a/doc/development/polling.md
+++ b/doc/development/polling.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Polling with ETag caching
Polling for changes (repeatedly asking server if there are any new changes)
diff --git a/doc/development/polymorphic_associations.md b/doc/development/polymorphic_associations.md
index b6567704d8e..66ea974063a 100644
--- a/doc/development/polymorphic_associations.md
+++ b/doc/development/polymorphic_associations.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Polymorphic Associations
**Summary:** always use separate tables instead of polymorphic associations.
diff --git a/doc/development/post_deployment_migrations.md b/doc/development/post_deployment_migrations.md
index 4d523178a21..2550f9547df 100644
--- a/doc/development/post_deployment_migrations.md
+++ b/doc/development/post_deployment_migrations.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Post Deployment Migrations
Post deployment migrations are regular Rails migrations that can optionally be
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index f5a4d1edb92..4edd34c3aff 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Profiling
To make it easier to track down performance problems GitLab comes with a set of
diff --git a/doc/development/projections.md b/doc/development/projections.md
index 9d5702da530..b8f476c53e9 100644
--- a/doc/development/projections.md
+++ b/doc/development/projections.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Projections
Projections are a way to define relations between files. Every file can have a
diff --git a/doc/development/pry_debugging.md b/doc/development/pry_debugging.md
index 0558a0a515a..7f9a49d4d50 100644
--- a/doc/development/pry_debugging.md
+++ b/doc/development/pry_debugging.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Pry debugging
## Invoking pry debugging
diff --git a/doc/development/python_guide/index.md b/doc/development/python_guide/index.md
index fae2e495417..22a01c3b877 100644
--- a/doc/development/python_guide/index.md
+++ b/doc/development/python_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Python Development Guidelines
GitLab requires Python as a dependency for [reStructuredText](https://docutils.sourceforge.io/rst.html)
diff --git a/doc/development/query_count_limits.md b/doc/development/query_count_limits.md
index b3ecaf30d8a..07dcb6c7d90 100644
--- a/doc/development/query_count_limits.md
+++ b/doc/development/query_count_limits.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Query Count Limits
Each controller or API endpoint is allowed to execute up to 100 SQL queries and
diff --git a/doc/development/query_recorder.md b/doc/development/query_recorder.md
index ef9a3c657aa..ae5c2947d40 100644
--- a/doc/development/query_recorder.md
+++ b/doc/development/query_recorder.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# QueryRecorder
QueryRecorder is a tool for detecting the [N+1 queries problem](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) from tests.
diff --git a/doc/development/rails_initializers.md b/doc/development/rails_initializers.md
index 6473baf58d4..bc0ac963f3f 100644
--- a/doc/development/rails_initializers.md
+++ b/doc/development/rails_initializers.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Rails initializers
By default, Rails loads Zeitwerk after the initializers in `config/initializers` are loaded.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index fd5dee69fc3..c718d5654dc 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Rake tasks for developers
Rake tasks are available for developers and others contributing to GitLab.
diff --git a/doc/development/reactive_caching.md b/doc/development/reactive_caching.md
index cf125c46565..106a9b5de31 100644
--- a/doc/development/reactive_caching.md
+++ b/doc/development/reactive_caching.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# `ReactiveCaching`
> This doc refers to <https://gitlab.com/gitlab-org/gitlab/blob/master/app/models/concerns/reactive_caching.rb>.
diff --git a/doc/development/redis.md b/doc/development/redis.md
index a0ae84beb8d..7ee8afda36a 100644
--- a/doc/development/redis.md
+++ b/doc/development/redis.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Redis guidelines
GitLab uses [Redis](https://redis.io) for the following distinct purposes:
diff --git a/doc/development/refactoring_guide/index.md b/doc/development/refactoring_guide/index.md
index a9ff9556aed..17deffb4cb2 100644
--- a/doc/development/refactoring_guide/index.md
+++ b/doc/development/refactoring_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Refactoring guide
This document is a collection of techniques and best practices to consider while performing a refactor.
diff --git a/doc/development/reference_processing.md b/doc/development/reference_processing.md
index cf587043cae..eec8a176c5f 100644
--- a/doc/development/reference_processing.md
+++ b/doc/development/reference_processing.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
description: 'An introduction to reference parsers and reference filters, and a guide to their implementation.'
---
diff --git a/doc/development/renaming_features.md b/doc/development/renaming_features.md
index daf437027db..02d1851dbfe 100644
--- a/doc/development/renaming_features.md
+++ b/doc/development/renaming_features.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Renaming features
Sometimes the business asks to change the name of a feature. Broadly speaking, there are 2 approaches to that task. They basically trade between immediate effort and future complexity/bug risk:
diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md
index 8711bac69e0..77534eea076 100644
--- a/doc/development/reusing_abstractions.md
+++ b/doc/development/reusing_abstractions.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Guidelines for reusing abstractions
As GitLab has grown, different patterns emerged across the codebase. Service
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index 6bad91d6287..cff88388ba0 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -1 +1,5 @@
+---
+redirect_to: 'feature_flags/index.md'
+---
+
This document was moved to [another location](feature_flags/index.md).
diff --git a/doc/development/routing.md b/doc/development/routing.md
index e164431853f..cbae2b38427 100644
--- a/doc/development/routing.md
+++ b/doc/development/routing.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Routing
The GitLab backend is written primarily with Rails so it uses [Rails
diff --git a/doc/development/scalability.md b/doc/development/scalability.md
index 0fb54d89913..73f7c5e0915 100644
--- a/doc/development/scalability.md
+++ b/doc/development/scalability.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab scalability
This section describes the current architecture of GitLab as it relates to
diff --git a/doc/development/serializing_data.md b/doc/development/serializing_data.md
index af791f5a4e0..0e394185b0d 100644
--- a/doc/development/serializing_data.md
+++ b/doc/development/serializing_data.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Serializing Data
**Summary:** don't store serialized data in the database, use separate columns
diff --git a/doc/development/service_measurement.md b/doc/development/service_measurement.md
index 24b16aac95b..5516afe6720 100644
--- a/doc/development/service_measurement.md
+++ b/doc/development/service_measurement.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab Developers Guide to service measurement
You can enable service measurement in order to debug any slow service's execution time, number of SQL calls, garbage collection stats, memory usage, etc.
diff --git a/doc/development/session.md b/doc/development/session.md
index 971795d8816..a7f69f570c3 100644
--- a/doc/development/session.md
+++ b/doc/development/session.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Accessing session data
Session data in GitLab is stored in Redis and can be accessed in a variety of ways.
diff --git a/doc/development/sha1_as_binary.md b/doc/development/sha1_as_binary.md
index 6c4252ec634..e69ba149cde 100644
--- a/doc/development/sha1_as_binary.md
+++ b/doc/development/sha1_as_binary.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Storing SHA1 Hashes As Binary
Storing SHA1 hashes as strings is not very space efficient. A SHA1 as a string
diff --git a/doc/development/shared_files.md b/doc/development/shared_files.md
index fcd905b54a4..2c6b1222cfb 100644
--- a/doc/development/shared_files.md
+++ b/doc/development/shared_files.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Shared files
Historically, GitLab has been storing shared files in many different
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index b71fcbdc2ab..25113b4ac29 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Guidelines for shell commands in the GitLab codebase
This document contains guidelines for working with processes and files in the GitLab codebase.
diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
index b6a11dd813d..e0f74b39c9a 100644
--- a/doc/development/sidekiq_debugging.md
+++ b/doc/development/sidekiq_debugging.md
@@ -1,6 +1,5 @@
-# Sidekiq debugging
+---
+redirect_to: '../administration/troubleshooting/sidekiq.md'
+---
-## Log arguments to Sidekiq jobs
-
-This content has been moved to the
-[Troubleshooting Sidekiq docs](../administration/troubleshooting/sidekiq.md).
+This document was moved to [another location](../administration/troubleshooting/sidekiq.md).
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index b341c31e21d..1094505e00a 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Sidekiq Style Guide
This document outlines various guidelines that should be followed when adding or
diff --git a/doc/development/single_table_inheritance.md b/doc/development/single_table_inheritance.md
index 27c3c4f3199..51268092225 100644
--- a/doc/development/single_table_inheritance.md
+++ b/doc/development/single_table_inheritance.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Single Table Inheritance
**Summary:** don't use Single Table Inheritance (STI), use separate tables
diff --git a/doc/development/sql.md b/doc/development/sql.md
index 55a8192578b..0b7b2a4024f 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# SQL Query Guidelines
This document describes various guidelines to follow when writing SQL queries,
diff --git a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
index 32b1c304a9a..871b3f80c18 100644
--- a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
+++ b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Dynamic Element Validation
We devised a solution to solve common test automation problems such as the dreaded `NoSuchElementException`.
diff --git a/doc/development/testing_guide/end_to_end/environment_selection.md b/doc/development/testing_guide/end_to_end/environment_selection.md
index 325f251b280..c369804928d 100644
--- a/doc/development/testing_guide/end_to_end/environment_selection.md
+++ b/doc/development/testing_guide/end_to_end/environment_selection.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Environment selection
Some tests are designed to be run against specific environments or [pipelines](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#scheduled-qa-test-pipelines).
@@ -42,9 +48,9 @@ RSpec.describe 'Area' do
it 'runs in dev environment', only: { tld: '.org', domain: 'gitlab', subdomain: 'dev' } do; end
it 'runs in prod and staging environments', only: { subdomain: /(staging.)?/, domain: 'gitlab' } {}
-
+
it 'runs only in nightly pipeline', only: { pipeline: :nightly } do; end
-
+
it 'runs in nightly and canary pipelines', only: { pipeline: [:nightly, :canary] } do; end
end
```
diff --git a/doc/development/testing_guide/end_to_end/feature_flags.md b/doc/development/testing_guide/end_to_end/feature_flags.md
index e571774167d..2ff1c9f6dc3 100644
--- a/doc/development/testing_guide/end_to_end/feature_flags.md
+++ b/doc/development/testing_guide/end_to_end/feature_flags.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Testing with feature flags
To run a specific test with a feature flag enabled you can use the `QA::Runtime::Feature` class to
diff --git a/doc/development/testing_guide/end_to_end/flows.md b/doc/development/testing_guide/end_to_end/flows.md
index fb1d82914aa..291d8bd5319 100644
--- a/doc/development/testing_guide/end_to_end/flows.md
+++ b/doc/development/testing_guide/end_to_end/flows.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Flows in GitLab QA
Flows are frequently used sequences of actions. They are a higher level
diff --git a/doc/development/testing_guide/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md
index 6ce44b2d359..7eacaf4b08a 100644
--- a/doc/development/testing_guide/end_to_end/page_objects.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Page objects in GitLab QA
In GitLab QA we are using a known pattern, called _Page Objects_.
diff --git a/doc/development/testing_guide/end_to_end/resources.md b/doc/development/testing_guide/end_to_end/resources.md
index b8a093c54c6..d73bae331d5 100644
--- a/doc/development/testing_guide/end_to_end/resources.md
+++ b/doc/development/testing_guide/end_to_end/resources.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Resource class in GitLab QA
Resources are primarily created using Browser UI steps, but can also
diff --git a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
index 3a1303d9c0c..66922ca8471 100644
--- a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
+++ b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# RSpec metadata for end-to-end tests
This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata)
diff --git a/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md b/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
index 658839fcf96..af7ea210edb 100644
--- a/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
+++ b/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Running tests that require special setup
## Jenkins spec
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
index 645c2633831..e6c96bca93e 100644
--- a/doc/development/testing_guide/end_to_end/style_guide.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Style guide for writing end-to-end tests
This document describes the conventions used at GitLab for writing End-to-end (E2E) tests using the GitLab QA project.
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index 7aed908c4f6..60fdf3447bd 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Flaky tests
## What's a flaky test?
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 67ac7934346..dd37c2c89fa 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Frontend testing standards and style guidelines
There are two types of test suites you'll encounter while developing frontend code
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index a61a700594c..840c8c9206c 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Testing standards and style guidelines
This document describes various guidelines and best practices for automated
diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md
index 5a144b43478..76484dd193b 100644
--- a/doc/development/testing_guide/smoke.md
+++ b/doc/development/testing_guide/smoke.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Smoke Tests
It is imperative in any testing suite that we have Smoke Tests. In short, smoke
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 88d7cf9ca08..d9e7edfa0c8 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Testing levels
![Testing priority triangle](img/testing_triangle.png)
diff --git a/doc/development/testing_guide/testing_rake_tasks.md b/doc/development/testing_guide/testing_rake_tasks.md
index db8ca87e9f8..384739d1adb 100644
--- a/doc/development/testing_guide/testing_rake_tasks.md
+++ b/doc/development/testing_guide/testing_rake_tasks.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Testing Rake tasks
To make testing Rake tasks a little easier, there is a helper that can be included
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 797dfc676eb..1e9740be4d7 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Understanding EXPLAIN plans
PostgreSQL allows you to obtain query plans using the `EXPLAIN` command. This
diff --git a/doc/development/uploads.md b/doc/development/uploads.md
index ee94553c200..b0c7bc85605 100644
--- a/doc/development/uploads.md
+++ b/doc/development/uploads.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Uploads development documentation
[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) has special rules for handling uploads.
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index 52151d37038..aca14507fcd 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# GitLab utilities
We have developed a number of utilities to help ease development:
diff --git a/doc/development/verifying_database_capabilities.md b/doc/development/verifying_database_capabilities.md
index f6c78e51299..930f40ef65e 100644
--- a/doc/development/verifying_database_capabilities.md
+++ b/doc/development/verifying_database_capabilities.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Verifying Database Capabilities
Sometimes certain bits of code may only work on a certain database
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 9063fb867e2..e30addaad54 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# What requires downtime?
When working with a database certain operations can be performed without taking
diff --git a/doc/development/windows.md b/doc/development/windows.md
index 3301e4f7c8f..2ca99508746 100644
--- a/doc/development/windows.md
+++ b/doc/development/windows.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference, howto
---
diff --git a/doc/security/README.md b/doc/security/README.md
index f8b9e423c04..a8947ef3de9 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
comments: false
type: index
---
diff --git a/doc/security/asset_proxy.md b/doc/security/asset_proxy.md
index fdceecdf386..7eb6d5067e2 100644
--- a/doc/security/asset_proxy.md
+++ b/doc/security/asset_proxy.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Proxying assets
A possible security concern when managing a public facing GitLab instance is
diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md
index 2496029d93e..4571f0051d8 100644
--- a/doc/security/crime_vulnerability.md
+++ b/doc/security/crime_vulnerability.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference
---
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
index 7c3d7284f25..a8c4a4e878e 100644
--- a/doc/security/information_exclusivity.md
+++ b/doc/security/information_exclusivity.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: concepts
---
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
index 5354fe30082..38d552229c4 100644
--- a/doc/security/password_length_limits.md
+++ b/doc/security/password_length_limits.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference, howto
---
diff --git a/doc/security/password_storage.md b/doc/security/password_storage.md
index 96487a75d8d..ca4d350dc06 100644
--- a/doc/security/password_storage.md
+++ b/doc/security/password_storage.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference
---
diff --git a/doc/security/passwords_for_integrated_authentication_methods.md b/doc/security/passwords_for_integrated_authentication_methods.md
index f2597ef1578..4872f26a0ad 100644
--- a/doc/security/passwords_for_integrated_authentication_methods.md
+++ b/doc/security/passwords_for_integrated_authentication_methods.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference
---
diff --git a/doc/security/project_import_decompressed_archive_size_limits.md b/doc/security/project_import_decompressed_archive_size_limits.md
index 16821e1f192..9e50290afcc 100644
--- a/doc/security/project_import_decompressed_archive_size_limits.md
+++ b/doc/security/project_import_decompressed_archive_size_limits.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference, howto
---
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index b386917f399..a84ecc8e47d 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference, howto
---
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index 9e754cf1917..94cc446c804 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference, howto
---
diff --git a/doc/security/reset_user_password.md b/doc/security/reset_user_password.md
index bc8de882afe..cfe950d7c96 100644
--- a/doc/security/reset_user_password.md
+++ b/doc/security/reset_user_password.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: howto
---
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
index bf3bbbb701e..4013bfb7cae 100644
--- a/doc/security/unlock_user.md
+++ b/doc/security/unlock_user.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: howto
---
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index 3d7aa3026ab..2e2fb093916 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: concepts, reference, howto
---
diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md
index 128e9d07282..948fee9230c 100644
--- a/doc/subscriptions/gitlab_com/index.md
+++ b/doc/subscriptions/gitlab_com/index.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: index, reference
---
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index bc58c9e899d..ef9c771a034 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: index, reference
---
diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md
index 5f232bd4ed2..e03a863f153 100644
--- a/doc/subscriptions/self_managed/index.md
+++ b/doc/subscriptions/self_managed/index.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: index, reference
---
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index d2bdf935aa1..64d7f789850 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference
---
diff --git a/doc/tools/email.md b/doc/tools/email.md
index 69aca200311..41a78f3a68a 100644
--- a/doc/tools/email.md
+++ b/doc/tools/email.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: howto, reference
---
diff --git a/doc/update/README.md b/doc/update/README.md
index 901e187d2f5..97b002d58c2 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Updating GitLab
Depending on the installation method and your GitLab version, there are multiple
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 0759d45147a..10c1a5017b5 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Migrating from MySQL to PostgreSQL
This guide documents how to take a working GitLab instance that uses MySQL and
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 0092df7b89d..081df16be81 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
comments: false
---
diff --git a/doc/update/restore_after_failure.md b/doc/update/restore_after_failure.md
index 2c70e38d041..e35d7bff562 100644
--- a/doc/update/restore_after_failure.md
+++ b/doc/update/restore_after_failure.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Restoring from backup after a failed upgrade
Upgrades are usually smooth and restoring from backup is a rare occurrence.
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index f82f5001c89..71d857ce18f 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
comments: false
---
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index e570042e607..648fd18d546 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
comments: false
---
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
index 5133e4cf4e5..4d8b58cc3af 100644
--- a/doc/update/upgrading_postgresql_using_slony.md
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Upgrading PostgreSQL Using Slony
This guide describes the steps one can take to upgrade their PostgreSQL database
diff --git a/doc/user/group/repositories_analytics/index.md b/doc/user/group/repositories_analytics/index.md
index c9a10146440..9c3a4deb2b6 100644
--- a/doc/user/group/repositories_analytics/index.md
+++ b/doc/user/group/repositories_analytics/index.md
@@ -12,6 +12,29 @@ info: To determine the technical writer assigned to the Stage/Group associated w
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
+## Latest project test coverage list
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267624) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
+> - It's [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
+> - It's disabled on GitLab.com
+> - It can be enabled or disabled per-group.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-latest-project-test-coverage).
+
+To see the latest code coverage for each project in your group:
+
+1. Go to **Analytics > Repositories** in the group (not from a project).
+1. In the **Latest test coverage results** section, use the **Select projects** dropdown to choose the projects you want to check.
+
+### Enable or disable latest project test coverage
+
+This feature comes with the `:group_coverage_data_report` feature flag disabled by default. It is disabled on GitLab.com.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it for your instance.
+The group test coverage table can be enabled or disabled per-group.
+
+## Download historic test coverage data
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215104) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
+
You can get a CSV of the code coverage data for all of the projects in your group. This report has a maximum of 1000 records. To get the report:
1. Go to your group's **Analytics > Repositories** page
diff --git a/doc/user/instance/clusters/index.md b/doc/user/instance/clusters/index.md
index c370f9d8725..68af74b69fe 100644
--- a/doc/user/instance/clusters/index.md
+++ b/doc/user/instance/clusters/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Instance-level Kubernetes clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/39840) in GitLab 11.11.
diff --git a/doc/user/project/integrations/ewm.md b/doc/user/project/integrations/ewm.md
index be89323a246..822483a1d5b 100644
--- a/doc/user/project/integrations/ewm.md
+++ b/doc/user/project/integrations/ewm.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# IBM Engineering Workflow Management (EWM) Integration **(CORE)**
This service allows you to navigate from GitLab to EWM work items mentioned in merge request descriptions and commit messages. Each work item reference is automatically converted to a link back to the work item.
diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md
index af01534a67f..af9a6401474 100644
--- a/doc/user/project/issues/csv_export.md
+++ b/doc/user/project/issues/csv_export.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Export Issues to CSV
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1126) in [GitLab Starter 9.0](https://about.gitlab.com/releases/2017/03/22/gitlab-9-0-released/#export-issues-ees-eep).
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
index 807f4fcffdf..2cac88b1b29 100644
--- a/doc/user/project/issues/csv_import.md
+++ b/doc/user/project/issues/csv_import.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Importing issues from CSV
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23532) in GitLab 11.7.
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index d8579c4cd8e..c66103604ed 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Members of a project
You can manage the groups and users and their access levels in all of your
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index 033d69cbbfa..395f4353f47 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Share Projects with other Groups
You can share projects with other [groups](../../group/index.md). This makes it
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 9581c974414..0922ffb2943 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference, concepts
---
diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
index 02d1dd7898a..b3dabe86043 100644
--- a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
+++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
description: "How to secure GitLab Pages websites with Let's Encrypt (manual process, deprecated)."
---
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 4da9b5f88ff..4b1aac3472b 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, how-to
---
-# Web IDE
+# Web IDE **(CORE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4539) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.4.
> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44157) to GitLab Core in 10.7.
@@ -244,6 +244,8 @@ quickly share your project with others.
### Enabling Live Preview
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/268288) in GitLab 12.9, third-party assets and libraries required for Live Preview are hosted at `https://sandbox-prod.gitlab-static.net` when it is enabled. However, some libraries are still served from other third-party services which may or may not be desirable in your environment.
+
The Live Preview feature needs to be enabled in the GitLab instances
admin settings. Live Preview is enabled for all projects on
GitLab.com
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 3c37268fa09..ea5b018b5b2 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Reserved project and group names
Not all project & group names are allowed because they would conflict with
diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md
index c34d5be5899..6361aa856fe 100644
--- a/doc/user/shortcuts.md
+++ b/doc/user/shortcuts.md
@@ -1,4 +1,7 @@
---
+stage: none
+group: unassigned
+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
type: reference
disqus_identifier: 'https://docs.gitlab.com/ee/workflow/shortcuts.html'
---
diff --git a/doc/user/upgrade_email_bypass.md b/doc/user/upgrade_email_bypass.md
index 35fddc4a1d4..97d4b5e6a0a 100644
--- a/doc/user/upgrade_email_bypass.md
+++ b/doc/user/upgrade_email_bypass.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+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
+---
+
# Updating to GitLab 13.2: Email confirmation issues
In the [GitLab 13.0.1 security release](https://about.gitlab.com/releases/2020/05/27/security-release-13-0-1-released/),
diff --git a/lib/api/project_snapshots.rb b/lib/api/project_snapshots.rb
index e19afb6e8e4..d33d2976b1c 100644
--- a/lib/api/project_snapshots.rb
+++ b/lib/api/project_snapshots.rb
@@ -6,6 +6,8 @@ module API
before { authorize_read_git_snapshot! }
+ feature_category :source_code_management
+
resource :projects do
desc 'Download a (possibly inconsistent) snapshot of a repository' do
detail 'This feature was introduced in GitLab 10.7'
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index b4de260fe49..899984fe0ba 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -6,6 +6,8 @@ module API
before { check_snippets_enabled }
+ feature_category :snippets
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/project_statistics.rb b/lib/api/project_statistics.rb
index 1ead969fc81..3db8d20ebac 100644
--- a/lib/api/project_statistics.rb
+++ b/lib/api/project_statistics.rb
@@ -2,6 +2,8 @@
module API
class ProjectStatistics < ::API::Base
+ feature_category :source_code_management
+
before do
authenticate!
authorize! :daily_statistics, user_project
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index 7d851de0237..af5d96969ef 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -12,6 +12,8 @@ module API
before { authenticate_non_get! }
+ feature_category :templates
+
params do
requires :id, type: String, desc: 'The ID of a project'
requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses|metrics_dashboard_ymls|issues|merge_requests) of the template'
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index a448682d8bd..17574739a7c 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
+ feature_category :source_code_management
+
helpers Helpers::ProtectedBranchesHelpers
params do
diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb
index dd3e407ffc9..b9385df1f8d 100644
--- a/lib/api/protected_tags.rb
+++ b/lib/api/protected_tags.rb
@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
+ feature_category :source_code_management
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index 23de9f9fc9f..d3a185a51c8 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -10,6 +10,8 @@ module API
before { authorize! :read_release, user_project }
+ feature_category :release_orchestration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 3bd6ea77403..fa760324917 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -9,6 +9,8 @@ module API
before { authorize_read_releases! }
+ feature_category :release_orchestration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index f63ea04a529..83096772d32 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -4,6 +4,8 @@ module API
class RemoteMirrors < ::API::Base
include PaginationParams
+ feature_category :source_code_management
+
before do
unauthorized! unless can?(current_user, :admin_remote_mirror, user_project)
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 8704c37cbd7..8af8ffc3b63 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -12,6 +12,8 @@ module API
before { authorize! :download_code, user_project }
+ feature_category :source_code_management
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 75371743b53..d6a1115d018 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :global_search
+
helpers do
SCOPE_ENTITY = {
merge_requests: Entities::MergeRequestBasic,
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 5f3d14010a8..cfcae13e518 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module API
class Services < ::API::Base
+ feature_category :integrations
+
services = Helpers::ServicesHelpers.services
service_classes = Helpers::ServicesHelpers.service_classes
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index dc917d9c529..b40af368f36 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -4,6 +4,8 @@ module API
class Settings < ::API::Base
before { authenticated_as_admin! }
+ feature_category :not_owned
+
helpers Helpers::SettingsHelpers
helpers do
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index b025dbfab37..680363d036e 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -6,6 +6,8 @@ module API
class SidekiqMetrics < ::API::Base
before { authenticated_as_admin! }
+ feature_category :not_owned
+
helpers do
def queue_metrics
Sidekiq::Queue.all.each_with_object({}) do |queue, hash|
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 2e67b9649bc..52b597fb788 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -5,6 +5,8 @@ module API
class Snippets < ::API::Base
include PaginationParams
+ feature_category :snippets
+
resource :snippets do
helpers Helpers::SnippetsHelpers
helpers do
diff --git a/lib/api/statistics.rb b/lib/api/statistics.rb
index fa7176491ba..1814e1a6782 100644
--- a/lib/api/statistics.rb
+++ b/lib/api/statistics.rb
@@ -4,6 +4,8 @@ module API
class Statistics < ::API::Base
before { authenticated_as_admin! }
+ feature_category :instance_statistics
+
COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
MergeRequest, Note, Snippet, Key, Milestone].freeze
diff --git a/lib/api/submodules.rb b/lib/api/submodules.rb
index e2ceb49c119..5c71a18c6d0 100644
--- a/lib/api/submodules.rb
+++ b/lib/api/submodules.rb
@@ -4,6 +4,8 @@ module API
class Submodules < ::API::Base
before { authenticate! }
+ feature_category :source_code_management
+
helpers do
def commit_params(attrs)
{
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 35a28da4736..914bab52929 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -11,25 +11,29 @@ module API
type: 'merge_requests',
entity: Entities::MergeRequest,
source: Project,
- finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) }
+ finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) },
+ feature_category: :code_review
},
{
type: 'issues',
entity: Entities::Issue,
source: Project,
- finder: ->(id) { find_project_issue(id) }
+ finder: ->(id) { find_project_issue(id) },
+ feature_category: :issue_tracking
},
{
type: 'labels',
entity: Entities::ProjectLabel,
source: Project,
- finder: ->(id) { find_label(user_project, id) }
+ finder: ->(id) { find_label(user_project, id) },
+ feature_category: :issue_tracking
},
{
type: 'labels',
entity: Entities::GroupLabel,
source: Group,
- finder: ->(id) { find_label(user_group, id) }
+ finder: ->(id) { find_label(user_group, id) },
+ feature_category: :issue_tracking
}
]
@@ -44,7 +48,7 @@ module API
desc 'Subscribe to a resource' do
success subscribable[:entity]
end
- post ":id/#{subscribable[:type]}/:subscribable_id/subscribe" do
+ post ":id/#{subscribable[:type]}/:subscribable_id/subscribe", subscribable.slice(:feature_category) do
parent = parent_resource(source_type)
resource = instance_exec(params[:subscribable_id], &subscribable[:finder])
@@ -59,7 +63,7 @@ module API
desc 'Unsubscribe from a resource' do
success subscribable[:entity]
end
- post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe" do
+ post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe", subscribable.slice(:feature_category) do
parent = parent_resource(source_type)
resource = instance_exec(params[:subscribable_id], &subscribable[:finder])
diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb
index f23d279c3f4..a024d6de874 100644
--- a/lib/api/suggestions.rb
+++ b/lib/api/suggestions.rb
@@ -4,6 +4,8 @@ module API
class Suggestions < ::API::Base
before { authenticate! }
+ feature_category :code_review
+
resource :suggestions do
desc 'Apply suggestion patch in the Merge Request it was created' do
success Entities::Suggestion
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 2820d305d0f..42e16d47a0b 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -4,6 +4,8 @@ module API
class SystemHooks < ::API::Base
include PaginationParams
+ feature_category :integrations
+
before do
authenticate!
authenticated_as_admin!
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index b969394ec47..7636c45bdac 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -23,7 +23,7 @@ module API
optional :search, type: String, desc: 'Return list of tags matching the search criteria'
use :pagination
end
- get ':id/repository/tags' do
+ get ':id/repository/tags', feature_category: :source_code_management do
tags = ::TagsFinder.new(user_project.repository,
sort: "#{params[:order_by]}_#{params[:sort]}",
search: params[:search]).execute
@@ -37,7 +37,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
- get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :source_code_management do
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
@@ -54,7 +54,7 @@ module API
optional :message, type: String, desc: 'Specifying a message creates an annotated tag'
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)'
end
- post ':id/repository/tags' do
+ post ':id/repository/tags', :release_orchestration do
authorize_admin_tag
result = ::Tags::CreateService.new(user_project, current_user)
@@ -86,7 +86,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
- delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :source_code_management do
authorize_admin_tag
tag = user_project.repository.find_tag(params[:tag_name])
@@ -112,7 +112,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
requires :description, type: String, desc: 'Release notes with markdown support'
end
- post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
authorize_create_release!
##
@@ -144,7 +144,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
requires :description, type: String, desc: 'Release notes with markdown support'
end
- put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do
+ put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
authorize_update_release!
result = ::Releases::UpdateService
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 0b427bbf5b9..b7fb35eac03 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -4,6 +4,8 @@ module API
class Templates < ::API::Base
include PaginationParams
+ feature_category :templates
+
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
gitlab_version: 8.8
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index ff6c931751c..f9a3830dcb1 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -7,6 +7,8 @@ module API
class State < ::API::Base
include ::Gitlab::Utils::StrongMemoize
+ feature_category :infrastructure_as_code
+
default_format :json
before do
diff --git a/lib/api/terraform/state_version.rb b/lib/api/terraform/state_version.rb
index b4a0efd7a2b..d3680323b9f 100644
--- a/lib/api/terraform/state_version.rb
+++ b/lib/api/terraform/state_version.rb
@@ -5,6 +5,8 @@ module API
class StateVersion < ::API::Base
default_format :json
+ feature_category :infrastructure_as_code
+
before do
authenticate!
authorize! :read_terraform_state, user_project
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index ce07d13cc9a..03850ba1c4e 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ feature_category :issue_tracking
+
ISSUABLE_TYPES = {
'merge_requests' => ->(iid) { find_merge_request_with_access(iid) },
'issues' => ->(iid) { find_project_issue(iid) }
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 960d004a04c..aebbc95cbea 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -6,6 +6,8 @@ module API
HTTP_GITLAB_EVENT_HEADER = "HTTP_#{WebHookService::GITLAB_EVENT_HEADER}".underscore.upcase
+ feature_category :continuous_integration
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb
index 907422118f1..3148c56339a 100644
--- a/lib/api/unleash.rb
+++ b/lib/api/unleash.rb
@@ -4,6 +4,8 @@ module API
class Unleash < ::API::Base
include PaginationParams
+ feature_category :feature_flags
+
namespace :feature_flags do
resource :unleash, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index fa5bfc1cbe9..7b038ec74bb 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -4,6 +4,8 @@ module API
class UsageData < ::API::Base
before { authenticate! }
+ feature_category :collection
+
namespace 'usage_data' do
before do
not_found! unless Feature.enabled?(:usage_data_api, default_enabled: true)
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
index 6d9db53fec8..3071f08e1de 100644
--- a/lib/api/user_counts.rb
+++ b/lib/api/user_counts.rb
@@ -2,6 +2,8 @@
module API
class UserCounts < ::API::Base
+ feature_category :navigation
+
resource :user_counts do
desc 'Return the user specific counts' do
detail 'Open MR Count'
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index f5de3d844e6..94fa98b7a14 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
+ feature_category :continuous_integration
+
helpers do
def filter_variable_parameters(params)
# This method exists so that EE can more easily filter out certain
diff --git a/lib/api/version.rb b/lib/api/version.rb
index 841b55f8d6c..f8072658cc6 100644
--- a/lib/api/version.rb
+++ b/lib/api/version.rb
@@ -9,6 +9,8 @@ module API
before { authenticate! }
+ feature_category :not_owned
+
METADATA_QUERY = <<~EOF
{
metadata {
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 21f457046f1..3fa42be47a9 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -4,6 +4,8 @@ module API
class Wikis < ::API::Base
helpers ::API::Helpers::WikisHelpers
+ feature_category :wiki
+
helpers do
attr_reader :container
diff --git a/lib/gitlab/middleware/handle_malformed_strings.rb b/lib/gitlab/middleware/handle_malformed_strings.rb
index 5fe3e6a1c73..bb2a8ead525 100644
--- a/lib/gitlab/middleware/handle_malformed_strings.rb
+++ b/lib/gitlab/middleware/handle_malformed_strings.rb
@@ -26,13 +26,20 @@ module Gitlab
request = Rack::Request.new(request)
- return true if string_malformed?(request.path)
+ return true if malformed_path?(request.path)
request.params.values.any? do |value|
param_has_null_byte?(value)
end
end
+ def malformed_path?(path)
+ string_malformed?(Rack::Utils.unescape(path))
+ rescue ArgumentError
+ # Rack::Utils.unescape raised this, path is malformed.
+ true
+ end
+
def param_has_null_byte?(value, depth = 0)
# Guard against possible attack sending large amounts of nested params
# Should be safe as deeply nested params are highly uncommon.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b41dbc2b0c6..ec2ea27cb1c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22492,7 +22492,10 @@ msgstr ""
msgid "RepositoriesAnalytics|Coverage"
msgstr ""
-msgid "RepositoriesAnalytics|Download Historic Test Coverage Data"
+msgid "RepositoriesAnalytics|Coverage Jobs"
+msgstr ""
+
+msgid "RepositoriesAnalytics|Download historic test coverage data"
msgstr ""
msgid "RepositoriesAnalytics|Download historic test coverage data (.csv)"
@@ -22507,7 +22510,7 @@ msgstr ""
msgid "RepositoriesAnalytics|Last Update"
msgstr ""
-msgid "RepositoriesAnalytics|Number of Coverages"
+msgid "RepositoriesAnalytics|Latest test coverage results"
msgstr ""
msgid "RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data."
@@ -23328,9 +23331,6 @@ msgstr ""
msgid "Search forks"
msgstr ""
-msgid "Search groups"
-msgstr ""
-
msgid "Search merge requests"
msgstr ""
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index a1afba1a371..bd77e6003e3 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -18,15 +18,15 @@ RSpec.describe 'User uses search filters', :js do
it 'shows group projects' do
visit search_path
- find('.js-search-group-dropdown').click
+ find('[data-testid="group-filter"]').click
wait_for_requests
- page.within('.search-page-form') do
- click_link(group.name)
+ page.within('[data-testid="group-filter"]') do
+ click_on(group.name)
end
- expect(find('.js-search-group-dropdown')).to have_content(group.name)
+ expect(find('[data-testid="group-filter"]')).to have_content(group.name)
page.within('[data-testid="project-filter"]') do
find('.js-search-project-dropdown').click
@@ -44,10 +44,11 @@ RSpec.describe 'User uses search filters', :js do
describe 'clear filter button' do
it 'removes Group and Project filters' do
- link = find('[data-testid="group-filter"] .js-search-clear')
- params = CGI.parse(URI.parse(link[:href]).query)
+ find('[data-testid="group-filter"] [data-testid="clear-icon"]').click
+
+ wait_for_requests
- expect(params).not_to include(:group_id, :project_id)
+ expect(page).to have_current_path(search_path(search: "test"))
end
end
end
diff --git a/spec/finders/user_groups_counter_spec.rb b/spec/finders/user_groups_counter_spec.rb
new file mode 100644
index 00000000000..49587e6dada
--- /dev/null
+++ b/spec/finders/user_groups_counter_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UserGroupsCounter do
+ subject { described_class.new(user_ids).execute }
+
+ describe '#execute' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group_member1) { create(:group_member, source: group1, user_id: user.id, access_level: Gitlab::Access::OWNER) }
+ let_it_be(:user_ids) { [user.id] }
+
+ it 'returns authorized group count for the user' do
+ expect(subject[user.id]).to eq(1)
+ end
+
+ context 'when request to join group is pending' do
+ let_it_be(:pending_group) { create(:group) }
+ let_it_be(:pending_group_member) { create(:group_member, requested_at: Time.current.utc, source: pending_group, user_id: user.id) }
+
+ it 'does not include pending group in the count' do
+ expect(subject[user.id]).to eq(1)
+ end
+ end
+
+ context 'when user is part of sub group' do
+ let_it_be(:sub_group) { create(:group, parent: create(:group)) }
+ let_it_be(:sub_group_member1) { create(:group_member, source: sub_group, user_id: user.id, access_level: Gitlab::Access::DEVELOPER) }
+
+ it 'includes sub group in the count' do
+ expect(subject[user.id]).to eq(2)
+ end
+ end
+
+ context 'when user is part of namespaced project' do
+ let_it_be(:project) { create(:project, group: create(:group)) }
+ let_it_be(:project_member) { create(:project_member, source: project, user_id: user.id, access_level: Gitlab::Access::REPORTER) }
+
+ it 'includes the project group' do
+ expect(subject[user.id]).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/frontend/jobs/components/sidebar_job_details_container_spec.js b/spec/frontend/jobs/components/sidebar_job_details_container_spec.js
new file mode 100644
index 00000000000..1807ed2fae8
--- /dev/null
+++ b/spec/frontend/jobs/components/sidebar_job_details_container_spec.js
@@ -0,0 +1,132 @@
+import { shallowMount } from '@vue/test-utils';
+import SidebarJobDetailsContainer from '~/jobs/components/sidebar_job_details_container.vue';
+import DetailRow from '~/jobs/components/sidebar_detail_row.vue';
+import createStore from '~/jobs/store';
+import { extendedWrapper } from '../../helpers/vue_test_utils_helper';
+import job from '../mock_data';
+
+describe('Sidebar Job Details Container', () => {
+ let store;
+ let wrapper;
+
+ const findJobTimeout = () => wrapper.findByTestId('job-timeout');
+ const findJobTags = () => wrapper.findByTestId('job-tags');
+ const findAllDetailsRow = () => wrapper.findAll(DetailRow);
+
+ const createWrapper = ({ props = {} } = {}) => {
+ store = createStore();
+ wrapper = extendedWrapper(
+ shallowMount(SidebarJobDetailsContainer, {
+ propsData: props,
+ store,
+ stubs: {
+ DetailRow,
+ },
+ }),
+ );
+ };
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ describe('when no details are available', () => {
+ it('should render an empty container', () => {
+ createWrapper();
+
+ expect(wrapper.isEmpty()).toBe(true);
+ });
+ });
+
+ describe('when some of the details are available', () => {
+ beforeEach(createWrapper);
+
+ it.each([
+ ['duration', 'Duration: 6 seconds'],
+ ['erased_at', 'Erased: 3 weeks ago'],
+ ['finished_at', 'Finished: 3 weeks ago'],
+ ['queued', 'Queued: 9 seconds'],
+ ['runner', 'Runner: local ci runner (#1)'],
+ ['coverage', 'Coverage: 20%'],
+ ])('uses %s to render job-%s', async (detail, value) => {
+ await store.dispatch('receiveJobSuccess', { [detail]: job[detail] });
+ const detailsRow = findAllDetailsRow();
+
+ expect(wrapper.isEmpty()).toBe(false);
+ expect(detailsRow).toHaveLength(1);
+ expect(detailsRow.at(0).text()).toBe(value);
+ });
+
+ it('only renders tags', async () => {
+ const { tags } = job;
+ await store.dispatch('receiveJobSuccess', { tags });
+ const tagsComponent = findJobTags();
+
+ expect(wrapper.isEmpty()).toBe(false);
+ expect(tagsComponent.text()).toBe('Tags: tag');
+ });
+ });
+
+ describe('when all the info are available', () => {
+ it('renders all the details components', async () => {
+ createWrapper();
+ await store.dispatch('receiveJobSuccess', job);
+
+ expect(findAllDetailsRow()).toHaveLength(7);
+ });
+ });
+
+ describe('timeout', () => {
+ const {
+ metadata: { timeout_human_readable, timeout_source },
+ } = job;
+
+ beforeEach(createWrapper);
+
+ it('does not render if metadata is empty', async () => {
+ const metadata = {};
+ await store.dispatch('receiveJobSuccess', { metadata });
+ const detailsRow = findAllDetailsRow();
+
+ expect(wrapper.isEmpty()).toBe(true);
+ expect(detailsRow.exists()).toBe(false);
+ });
+
+ it('uses metadata to render timeout', async () => {
+ const metadata = { timeout_human_readable };
+ await store.dispatch('receiveJobSuccess', { metadata });
+ const detailsRow = findAllDetailsRow();
+
+ expect(wrapper.isEmpty()).toBe(false);
+ expect(detailsRow).toHaveLength(1);
+ expect(detailsRow.at(0).text()).toBe('Timeout: 1m 40s');
+ });
+
+ it('uses metadata to render timeout and the source', async () => {
+ const metadata = { timeout_human_readable, timeout_source };
+ await store.dispatch('receiveJobSuccess', { metadata });
+ const detailsRow = findAllDetailsRow();
+
+ expect(detailsRow.at(0).text()).toBe('Timeout: 1m 40s (from runner)');
+ });
+
+ it('should not render when no time is provided', async () => {
+ const metadata = { timeout_source };
+ await store.dispatch('receiveJobSuccess', { metadata });
+
+ expect(findJobTimeout().exists()).toBe(false);
+ });
+
+ it('should pass the help URL', async () => {
+ const helpUrl = 'fakeUrl';
+ const props = { runnerHelpUrl: helpUrl };
+ createWrapper({ props });
+ await store.dispatch('receiveJobSuccess', { metadata: { timeout_human_readable } });
+
+ expect(findJobTimeout().props('helpUrl')).toBe(helpUrl);
+ });
+ });
+});
diff --git a/spec/frontend/jobs/components/sidebar_spec.js b/spec/frontend/jobs/components/sidebar_spec.js
index 48788df0c93..a591e8777b0 100644
--- a/spec/frontend/jobs/components/sidebar_spec.js
+++ b/spec/frontend/jobs/components/sidebar_spec.js
@@ -1,167 +1,108 @@
-import Vue from 'vue';
-import sidebarDetailsBlock from '~/jobs/components/sidebar.vue';
+import { shallowMount } from '@vue/test-utils';
+import Sidebar from '~/jobs/components/sidebar.vue';
+import StagesDropdown from '~/jobs/components/stages_dropdown.vue';
+import JobsContainer from '~/jobs/components/jobs_container.vue';
import createStore from '~/jobs/store';
import job, { jobsInStage } from '../mock_data';
-import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
-import { trimText } from '../../helpers/text_helper';
+import { extendedWrapper } from '../../helpers/vue_test_utils_helper';
describe('Sidebar details block', () => {
- const SidebarComponent = Vue.extend(sidebarDetailsBlock);
- let vm;
let store;
+ let wrapper;
- beforeEach(() => {
+ const createWrapper = ({ props = {} } = {}) => {
store = createStore();
- });
+ wrapper = extendedWrapper(
+ shallowMount(Sidebar, {
+ ...props,
+ store,
+ }),
+ );
+ };
afterEach(() => {
- vm.$destroy();
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
});
describe('when there is no retry path retry', () => {
- it('should not render a retry button', () => {
- const copy = { ...job };
- delete copy.retry_path;
-
- store.dispatch('receiveJobSuccess', copy);
- vm = mountComponentWithStore(SidebarComponent, {
- store,
- });
+ it('should not render a retry button', async () => {
+ createWrapper();
+ const copy = { ...job, retry_path: null };
+ await store.dispatch('receiveJobSuccess', copy);
- expect(vm.$el.querySelector('.js-retry-button')).toBeNull();
+ expect(wrapper.find('.js-retry-button').exists()).toBe(false);
});
});
describe('without terminal path', () => {
- it('does not render terminal link', () => {
- store.dispatch('receiveJobSuccess', job);
- vm = mountComponentWithStore(SidebarComponent, { store });
+ it('does not render terminal link', async () => {
+ createWrapper();
+ await store.dispatch('receiveJobSuccess', job);
- expect(vm.$el.querySelector('.js-terminal-link')).toBeNull();
+ expect(wrapper.find('.js-terminal-link').exists()).toBe(false);
});
});
describe('with terminal path', () => {
- it('renders terminal link', () => {
- store.dispatch('receiveJobSuccess', { ...job, terminal_path: 'job/43123/terminal' });
- vm = mountComponentWithStore(SidebarComponent, {
- store,
- });
+ it('renders terminal link', async () => {
+ createWrapper();
+ await store.dispatch('receiveJobSuccess', { ...job, terminal_path: 'job/43123/terminal' });
- expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull();
+ expect(wrapper.find('.js-terminal-link').exists()).toBe(true);
});
});
- beforeEach(() => {
- store.dispatch('receiveJobSuccess', job);
- vm = mountComponentWithStore(SidebarComponent, { store });
- });
-
describe('actions', () => {
- it('should render link to new issue', () => {
- expect(vm.$el.querySelector('[data-testid="job-new-issue"]').getAttribute('href')).toEqual(
- job.new_issue_path,
- );
+ beforeEach(() => {
+ createWrapper();
+ return store.dispatch('receiveJobSuccess', job);
+ });
- expect(vm.$el.querySelector('[data-testid="job-new-issue"]').textContent.trim()).toEqual(
- 'New issue',
- );
+ it('should render link to new issue', () => {
+ expect(wrapper.findByTestId('job-new-issue').attributes('href')).toBe(job.new_issue_path);
+ expect(wrapper.find('[data-testid="job-new-issue"]').text()).toBe('New issue');
});
it('should render link to retry job', () => {
- expect(vm.$el.querySelector('.js-retry-button').getAttribute('href')).toEqual(job.retry_path);
+ expect(wrapper.find('.js-retry-button').attributes('href')).toBe(job.retry_path);
});
it('should render link to cancel job', () => {
- expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path);
- });
- });
-
- describe('information', () => {
- it('should render job duration', () => {
- expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual(
- 'Duration: 6 seconds',
- );
- });
-
- it('should render erased date', () => {
- expect(trimText(vm.$el.querySelector('.js-job-erased').textContent)).toEqual(
- 'Erased: 3 weeks ago',
- );
- });
-
- it('should render finished date', () => {
- expect(trimText(vm.$el.querySelector('.js-job-finished').textContent)).toEqual(
- 'Finished: 3 weeks ago',
- );
- });
-
- it('should render queued date', () => {
- expect(trimText(vm.$el.querySelector('.js-job-queued').textContent)).toEqual(
- 'Queued: 9 seconds',
- );
- });
-
- it('should render runner ID', () => {
- expect(trimText(vm.$el.querySelector('.js-job-runner').textContent)).toEqual(
- 'Runner: local ci runner (#1)',
- );
- });
-
- it('should render timeout information', () => {
- expect(trimText(vm.$el.querySelector('.js-job-timeout').textContent)).toEqual(
- 'Timeout: 1m 40s (from runner)',
- );
- });
-
- it('should render coverage', () => {
- expect(trimText(vm.$el.querySelector('.js-job-coverage').textContent)).toEqual(
- 'Coverage: 20%',
- );
- });
-
- it('should render tags', () => {
- expect(trimText(vm.$el.querySelector('.js-job-tags').textContent)).toEqual('Tags: tag');
+ expect(wrapper.find('.js-cancel-job').attributes('href')).toBe(job.cancel_path);
});
});
describe('stages dropdown', () => {
beforeEach(() => {
- store.dispatch('receiveJobSuccess', job);
+ createWrapper();
+ return store.dispatch('receiveJobSuccess', { ...job, stage: 'aStage' });
});
describe('with stages', () => {
- beforeEach(() => {
- vm = mountComponentWithStore(SidebarComponent, { store });
- });
-
it('renders value provided as selectedStage as selected', () => {
- expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual(
- vm.selectedStage,
- );
+ expect(wrapper.find(StagesDropdown).props('selectedStage')).toBe('aStage');
});
});
describe('without jobs for stages', () => {
- beforeEach(() => {
- store.dispatch('receiveJobSuccess', job);
- vm = mountComponentWithStore(SidebarComponent, { store });
- });
+ beforeEach(() => store.dispatch('receiveJobSuccess', job));
it('does not render job container', () => {
- expect(vm.$el.querySelector('.js-jobs-container')).toBeNull();
+ expect(wrapper.find('.js-jobs-container').exists()).toBe(false);
});
});
describe('with jobs for stages', () => {
- beforeEach(() => {
- store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
- vm = mountComponentWithStore(SidebarComponent, { store });
+ beforeEach(async () => {
+ await store.dispatch('receiveJobSuccess', job);
+ await store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
});
it('renders list of jobs', () => {
- expect(vm.$el.querySelector('.js-jobs-container')).not.toBeNull();
+ expect(wrapper.find(JobsContainer).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/search/dropdown_filter/components/dropdown_filter_spec.js b/spec/frontend/search/dropdown_filter/components/dropdown_filter_spec.js
index 2f7a564026c..f795a23404e 100644
--- a/spec/frontend/search/dropdown_filter/components/dropdown_filter_spec.js
+++ b/spec/frontend/search/dropdown_filter/components/dropdown_filter_spec.js
@@ -1,12 +1,12 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { MOCK_QUERY } from 'jest/search/mock_data';
import * as urlUtils from '~/lib/utils/url_utility';
import initStore from '~/search/store';
import DropdownFilter from '~/search/dropdown_filter/components/dropdown_filter.vue';
import stateFilterData from '~/search/dropdown_filter/constants/state_filter_data';
import confidentialFilterData from '~/search/dropdown_filter/constants/confidential_filter_data';
-import { MOCK_QUERY } from '../mock_data';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
diff --git a/spec/frontend/search/dropdown_filter/mock_data.js b/spec/frontend/search/dropdown_filter/mock_data.js
deleted file mode 100644
index f11ab3d9951..00000000000
--- a/spec/frontend/search/dropdown_filter/mock_data.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const MOCK_QUERY = {
- scope: 'issues',
- state: 'all',
- confidential: null,
-};
diff --git a/spec/frontend/search/group_filter/components/group_filter_spec.js b/spec/frontend/search/group_filter/components/group_filter_spec.js
new file mode 100644
index 00000000000..fd3a4449f41
--- /dev/null
+++ b/spec/frontend/search/group_filter/components/group_filter_spec.js
@@ -0,0 +1,172 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
+import * as urlUtils from '~/lib/utils/url_utility';
+import GroupFilter from '~/search/group_filter/components/group_filter.vue';
+import { GROUP_QUERY_PARAM, PROJECT_QUERY_PARAM, ANY_GROUP } from '~/search/group_filter/constants';
+import { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from '../../mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+jest.mock('~/flash');
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+ setUrlParams: jest.fn(),
+}));
+
+describe('Global Search Group Filter', () => {
+ let wrapper;
+
+ const actionSpies = {
+ fetchGroups: jest.fn(),
+ };
+
+ const defaultProps = {
+ initialGroup: null,
+ };
+
+ const createComponent = (initialState, props = {}, mountFn = shallowMount) => {
+ const store = new Vuex.Store({
+ state: {
+ query: MOCK_QUERY,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = mountFn(GroupFilter, {
+ localVue,
+ store,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findGlDropdown = () => wrapper.find(GlDropdown);
+ const findGlDropdownSearch = () => findGlDropdown().find(GlSearchBoxByType);
+ const findDropdownText = () => findGlDropdown().find('.dropdown-toggle-text');
+ const findDropdownItems = () => findGlDropdown().findAll(GlDropdownItem);
+ const findDropdownItemsText = () => findDropdownItems().wrappers.map(w => w.text());
+ const findAnyDropdownItem = () => findDropdownItems().at(0);
+ const findFirstGroupDropdownItem = () => findDropdownItems().at(1);
+ const findLoader = () => wrapper.find(GlSkeletonLoader);
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders GlDropdown', () => {
+ expect(findGlDropdown().exists()).toBe(true);
+ });
+
+ describe('findGlDropdownSearch', () => {
+ it('renders always', () => {
+ expect(findGlDropdownSearch().exists()).toBe(true);
+ });
+
+ it('has debounce prop', () => {
+ expect(findGlDropdownSearch().attributes('debounce')).toBe('500');
+ });
+
+ describe('onSearch', () => {
+ const groupSearch = 'test search';
+
+ beforeEach(() => {
+ findGlDropdownSearch().vm.$emit('input', groupSearch);
+ });
+
+ it('calls fetchGroups when input event is fired from GlSearchBoxByType', () => {
+ expect(actionSpies.fetchGroups).toHaveBeenCalledWith(expect.any(Object), groupSearch);
+ });
+ });
+ });
+
+ describe('findDropdownItems', () => {
+ describe('when fetchingGroups is false', () => {
+ beforeEach(() => {
+ createComponent({ groups: MOCK_GROUPS });
+ });
+
+ it('does not render loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('renders an instance for each namespace', () => {
+ const groupsIncludingAny = ['Any'].concat(MOCK_GROUPS.map(n => n.full_name));
+ expect(findDropdownItemsText()).toStrictEqual(groupsIncludingAny);
+ });
+ });
+
+ describe('when fetchingGroups is true', () => {
+ beforeEach(() => {
+ createComponent({ fetchingGroups: true, groups: MOCK_GROUPS });
+ });
+
+ it('does render loader', () => {
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('renders only Any in dropdown', () => {
+ expect(findDropdownItemsText()).toStrictEqual(['Any']);
+ });
+ });
+ });
+
+ describe('Dropdown Text', () => {
+ describe('when initialGroup is null', () => {
+ beforeEach(() => {
+ createComponent({}, {}, mount);
+ });
+
+ it('sets dropdown text to Any', () => {
+ expect(findDropdownText().text()).toBe(ANY_GROUP.name);
+ });
+ });
+
+ describe('initialGroup is set', () => {
+ beforeEach(() => {
+ createComponent({}, { initialGroup: MOCK_GROUP }, mount);
+ });
+
+ it('sets dropdown text to group name', () => {
+ expect(findDropdownText().text()).toBe(MOCK_GROUP.name);
+ });
+ });
+ });
+ });
+
+ describe('actions', () => {
+ beforeEach(() => {
+ createComponent({ groups: MOCK_GROUPS });
+ });
+
+ it('clicking "Any" dropdown item calls setUrlParams with group id null, project id null,and visitUrl', () => {
+ findAnyDropdownItem().vm.$emit('click');
+
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
+ [GROUP_QUERY_PARAM]: ANY_GROUP.id,
+ [PROJECT_QUERY_PARAM]: null,
+ });
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
+ });
+
+ it('clicking group dropdown item calls setUrlParams with group id, project id null, and visitUrl', () => {
+ findFirstGroupDropdownItem().vm.$emit('click');
+
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
+ [GROUP_QUERY_PARAM]: MOCK_GROUPS[0].id,
+ [PROJECT_QUERY_PARAM]: null,
+ });
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
new file mode 100644
index 00000000000..68fc432881a
--- /dev/null
+++ b/spec/frontend/search/mock_data.js
@@ -0,0 +1,24 @@
+export const MOCK_QUERY = {
+ scope: 'issues',
+ state: 'all',
+ confidential: null,
+};
+
+export const MOCK_GROUP = {
+ name: 'test group',
+ full_name: 'full name test group',
+ id: 'test_1',
+};
+
+export const MOCK_GROUPS = [
+ {
+ name: 'test group',
+ full_name: 'full name test group',
+ id: 'test_1',
+ },
+ {
+ name: 'test group 2',
+ full_name: 'full name test group 2',
+ id: 'test_2',
+ },
+];
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
new file mode 100644
index 00000000000..a44b42e6363
--- /dev/null
+++ b/spec/frontend/search/store/actions_spec.js
@@ -0,0 +1,45 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import * as actions from '~/search/store/actions';
+import * as types from '~/search/store/mutation_types';
+import state from '~/search/store/state';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import { MOCK_GROUPS } from '../mock_data';
+
+jest.mock('~/flash');
+
+describe('Global Search Store Actions', () => {
+ let mock;
+
+ const noCallback = () => {};
+ const flashCallback = () => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ createFlash.mockClear();
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe.each`
+ action | axiosMock | type | mutationCalls | callback
+ ${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback}
+ ${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback}
+ `(`axios calls`, ({ action, axiosMock, type, mutationCalls, callback }) => {
+ describe(action.name, () => {
+ describe(`on ${type}`, () => {
+ beforeEach(() => {
+ mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res);
+ });
+ it(`should dispatch the correct mutations`, () => {
+ return testAction(action, null, state, mutationCalls, []).then(() => callback());
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js
new file mode 100644
index 00000000000..097a2ff46fd
--- /dev/null
+++ b/spec/frontend/search/store/mutations_spec.js
@@ -0,0 +1,38 @@
+import mutations from '~/search/store/mutations';
+import createState from '~/search/store/state';
+import * as types from '~/search/store/mutation_types';
+import { MOCK_QUERY, MOCK_GROUPS } from '../mock_data';
+
+describe('Global Search Store Mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState({ query: MOCK_QUERY });
+ });
+
+ describe('REQUEST_GROUPS', () => {
+ it('sets fetchingGroups to true', () => {
+ mutations[types.REQUEST_GROUPS](state);
+
+ expect(state.fetchingGroups).toBe(true);
+ });
+ });
+
+ describe('RECEIVE_GROUPS_SUCCESS', () => {
+ it('sets fetchingGroups to false and sets groups', () => {
+ mutations[types.RECEIVE_GROUPS_SUCCESS](state, MOCK_GROUPS);
+
+ expect(state.fetchingGroups).toBe(false);
+ expect(state.groups).toBe(MOCK_GROUPS);
+ });
+ });
+
+ describe('RECEIVE_GROUPS_ERROR', () => {
+ it('sets fetchingGroups to false and clears groups', () => {
+ mutations[types.RECEIVE_GROUPS_ERROR](state);
+
+ expect(state.fetchingGroups).toBe(false);
+ expect(state.groups).toEqual([]);
+ });
+ });
+});
diff --git a/spec/frontend/search_spec.js b/spec/frontend/search_spec.js
index 7f480fe2a2b..cbbc2df6c78 100644
--- a/spec/frontend/search_spec.js
+++ b/spec/frontend/search_spec.js
@@ -36,16 +36,6 @@ describe('Search', () => {
new Search(); // eslint-disable-line no-new
});
- it('requests groups from backend when filtering', () => {
- jest.spyOn(Api, 'groups').mockImplementation(term => {
- expect(term).toBe(searchTerm);
- });
-
- const inputElement = fillDropdownInput('.js-search-group-dropdown');
-
- $(inputElement).trigger('input');
- });
-
it('requests projects from backend when filtering', () => {
jest.spyOn(Api, 'projects').mockImplementation(term => {
expect(term).toBe(searchTerm);
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js
index 8013e712558..36ace9fdab3 100644
--- a/spec/frontend/static_site_editor/components/edit_area_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_area_spec.js
@@ -16,6 +16,7 @@ import {
sourceContentBody as body,
returnUrl,
mounts,
+ project,
} from '../mock_data';
jest.mock('~/static_site_editor/services/formatter', () => jest.fn(str => `${str} format-pass`));
@@ -33,6 +34,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
content,
returnUrl,
mounts,
+ project,
savingChanges,
...propsData,
},
diff --git a/spec/frontend/static_site_editor/mock_data.js b/spec/frontend/static_site_editor/mock_data.js
index ce7c52e8277..956aadb23f3 100644
--- a/spec/frontend/static_site_editor/mock_data.js
+++ b/spec/frontend/static_site_editor/mock_data.js
@@ -27,6 +27,7 @@ export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser';
export const projectId = '123456';
+export const project = 'user1/project1';
export const returnUrl = 'https://www.gitlab.com';
export const sourcePath = 'foobar.md.html';
export const mergeRequestMeta = {
diff --git a/spec/frontend/static_site_editor/services/renderers/render_image_spec.js b/spec/frontend/static_site_editor/services/renderers/render_image_spec.js
new file mode 100644
index 00000000000..ab375d9e970
--- /dev/null
+++ b/spec/frontend/static_site_editor/services/renderers/render_image_spec.js
@@ -0,0 +1,63 @@
+import imageRenderer from '~/static_site_editor/services/renderers/render_image';
+import { mounts, project } from '../../mock_data';
+
+describe('rich_content_editor/renderers/render_image', () => {
+ let renderer;
+
+ beforeEach(() => {
+ renderer = imageRenderer.build(mounts, project);
+ });
+
+ describe('build', () => {
+ it('builds a renderer object containing `canRender` and `render` functions', () => {
+ expect(renderer).toHaveProperty('canRender', expect.any(Function));
+ expect(renderer).toHaveProperty('render', expect.any(Function));
+ });
+ });
+
+ describe('canRender', () => {
+ it.each`
+ input | result
+ ${{ type: 'image' }} | ${true}
+ ${{ type: 'text' }} | ${false}
+ ${{ type: 'htmlBlock' }} | ${false}
+ `('returns $result when input is $input', ({ input, result }) => {
+ expect(renderer.canRender(input)).toBe(result);
+ });
+ });
+
+ describe('render', () => {
+ let context;
+ let result;
+ const skipChildren = jest.fn();
+
+ beforeEach(() => {
+ const node = {
+ destination: '/some/path/image.png',
+ firstChild: {
+ type: 'img',
+ literal: 'Some Image',
+ },
+ };
+
+ context = { skipChildren };
+ result = renderer.render(node, context);
+ });
+
+ it('invokes `skipChildren`', () => {
+ expect(skipChildren).toHaveBeenCalled();
+ });
+
+ it('returns an image', () => {
+ expect(result).toEqual({
+ type: 'openTag',
+ tagName: 'img',
+ selfClose: true,
+ attributes: {
+ src: '/some/path/image.png',
+ alt: 'Some Image',
+ },
+ });
+ });
+ });
+});
diff --git a/spec/graphql/mutations/todos/create_spec.rb b/spec/graphql/mutations/todos/create_spec.rb
new file mode 100644
index 00000000000..bbb033e2f33
--- /dev/null
+++ b/spec/graphql/mutations/todos/create_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Todos::Create do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ describe '#resolve' do
+ context 'when target does not support todos' do
+ it 'raises error' do
+ current_user = create(:user)
+ mutation = described_class.new(object: nil, context: { current_user: current_user }, field: nil)
+
+ target = create(:milestone)
+
+ expect { mutation.resolve(target_id: global_id_of(target)) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+ end
+
+ context 'with issue as target' do
+ it_behaves_like 'create todo mutation' do
+ let_it_be(:target) { create(:issue) }
+ end
+ end
+
+ context 'with merge request as target' do
+ it_behaves_like 'create todo mutation' do
+ let_it_be(:target) { create(:merge_request) }
+ end
+ end
+
+ context 'with design as target' do
+ before do
+ enable_design_management
+ end
+
+ it_behaves_like 'create todo mutation' do
+ let_it_be(:target) { create(:design) }
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/users/group_count_resolver_spec.rb b/spec/graphql/resolvers/users/group_count_resolver_spec.rb
new file mode 100644
index 00000000000..47160a33646
--- /dev/null
+++ b/spec/graphql/resolvers/users/group_count_resolver_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Users::GroupCountResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project) { create(:project, group: create(:group)) }
+ let_it_be(:group_member1) { create(:group_member, source: group1, user_id: user1.id, access_level: Gitlab::Access::OWNER) }
+ let_it_be(:project_member1) { create(:project_member, source: project, user_id: user1.id, access_level: Gitlab::Access::DEVELOPER) }
+ let_it_be(:group_member2) { create(:group_member, source: group2, user_id: user2.id, access_level: Gitlab::Access::DEVELOPER) }
+
+ it 'resolves group count for users' do
+ current_user = user1
+
+ result = batch_sync do
+ [user1, user2].map { |user| resolve_group_count(user, current_user) }
+ end
+
+ expect(result).to eq([2, nil])
+ end
+
+ context 'permissions' do
+ context 'when current_user is an admin', :enable_admin_mode do
+ let_it_be(:admin) { create(:admin) }
+
+ it do
+ result = batch_sync do
+ [user1, user2].map { |user| resolve_group_count(user, admin) }
+ end
+
+ expect(result).to eq([2, 1])
+ end
+ end
+
+ context 'when current_user does not have access to the requested resource' do
+ it do
+ result = batch_sync { resolve_group_count(user1, user2) }
+
+ expect(result).to be nil
+ end
+ end
+
+ context 'when current_user does not exist' do
+ it do
+ result = batch_sync { resolve_group_count(user1, nil) }
+
+ expect(result).to be nil
+ end
+ end
+ end
+ end
+
+ def resolve_group_count(user, current_user)
+ resolve(described_class, obj: user, ctx: { current_user: current_user })
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 1d5af24b3d9..c8953d9ccb7 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['User'] do
authoredMergeRequests
assignedMergeRequests
groupMemberships
+ groupCount
projectMemberships
starredProjects
]
diff --git a/spec/lib/api/every_api_endpoint_spec.rb b/spec/lib/api/every_api_endpoint_spec.rb
index 2f5c646c61f..8bb4bfe3f3c 100644
--- a/spec/lib/api/every_api_endpoint_spec.rb
+++ b/spec/lib/api/every_api_endpoint_spec.rb
@@ -45,7 +45,16 @@ RSpec.describe 'Every API endpoint' do
::API::NpmPackages, ::API::GenericPackages, ::API::GoProxy, ::API::Pages,
::API::PagesDomains, ::API::ProjectClusters, ::API::ProjectContainerRepositories,
::API::ProjectEvents, ::API::ProjectExport, ::API::ProjectImport, ::API::ProjectHooks,
- ::API::ProjectMilestones, ::API::ProjectRepositoryStorageMoves, ::API::Projects
+ ::API::ProjectMilestones, ::API::ProjectRepositoryStorageMoves, ::API::Projects,
+ ::API::ProjectSnapshots, ::API::ProjectSnippets, ::API::ProjectStatistics,
+ ::API::ProjectTemplates, ::API::Terraform::State, ::API::Terraform::StateVersion,
+ ::API::ProtectedBranches, ::API::ProtectedTags, ::API::Releases, ::API::Release::Links,
+ ::API::RemoteMirrors, ::API::Repositories, ::API::Search, ::API::Services,
+ ::API::Settings, ::API::SidekiqMetrics, ::API::Snippets, ::API::Statistics,
+ ::API::Submodules, ::API::Subscriptions, ::API::Suggestions, ::API::SystemHooks,
+ ::API::Tags, ::API::Templates, ::API::Todos, ::API::Triggers, ::API::Unleash,
+ ::API::UsageData, ::API::UserCounts, ::API::Variables, ::API::Version,
+ ::API::Wikis
]
next unless completed_classes.include?(klass)
diff --git a/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb b/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb
index 6680720e403..5ed1580fa8d 100644
--- a/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb
+++ b/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb
@@ -5,7 +5,9 @@ require "rack/test"
RSpec.describe Gitlab::Middleware::HandleMalformedStrings do
let(:null_byte) { "\u0000" }
+ let(:escaped_null_byte) { "%00" }
let(:invalid_string) { "mal\xC0formed" }
+ let(:escaped_invalid_string) { "mal%c0formed" }
let(:error_400) { [400, { 'Content-Type' => 'text/plain' }, ['Bad Request']] }
let(:app) { double(:app) }
@@ -30,6 +32,14 @@ RSpec.describe Gitlab::Middleware::HandleMalformedStrings do
expect(subject.call(env)).to eq error_400
end
+ it 'rejects escaped null bytes' do
+ # We have to create the env separately or Rack::MockRequest complains about invalid URI
+ env = env_for
+ env['PATH_INFO'] = "/someplace/withan#{escaped_null_byte}escaped nullbyte"
+
+ expect(subject.call(env)).to eq error_400
+ end
+
it 'rejects malformed strings' do
# We have to create the env separately or Rack::MockRequest complains about invalid URI
env = env_for
@@ -37,6 +47,14 @@ RSpec.describe Gitlab::Middleware::HandleMalformedStrings do
expect(subject.call(env)).to eq error_400
end
+
+ it 'rejects escaped malformed strings' do
+ # We have to create the env separately or Rack::MockRequest complains about invalid URI
+ env = env_for
+ env['PATH_INFO'] = "/someplace/with_an/#{escaped_invalid_string}"
+
+ expect(subject.call(env)).to eq error_400
+ end
end
context 'in params' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index cc29e20710a..ac91281e7e7 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -308,8 +308,10 @@ RSpec.describe Group do
end
describe 'scopes' do
- let!(:private_group) { create(:group, :private) }
- let!(:internal_group) { create(:group, :internal) }
+ let_it_be(:private_group) { create(:group, :private) }
+ let_it_be(:internal_group) { create(:group, :internal) }
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
describe 'public_only' do
subject { described_class.public_only.to_a }
@@ -328,6 +330,27 @@ RSpec.describe Group do
it { is_expected.to match_array([private_group, internal_group]) }
end
+
+ describe 'for_authorized_group_members' do
+ let_it_be(:group_member1) { create(:group_member, source: private_group, user_id: user1.id, access_level: Gitlab::Access::OWNER) }
+
+ it do
+ result = described_class.for_authorized_group_members([user1.id, user2.id])
+
+ expect(result).to match_array([private_group])
+ end
+ end
+
+ describe 'for_authorized_project_members' do
+ let_it_be(:project) { create(:project, group: internal_group) }
+ let_it_be(:project_member1) { create(:project_member, source: project, user_id: user1.id, access_level: Gitlab::Access::DEVELOPER) }
+
+ it do
+ result = described_class.for_authorized_project_members([user1.id, user2.id])
+
+ expect(result).to match_array([internal_group])
+ end
+ end
end
describe '#to_reference' do
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index e352b990159..76788ae2cb7 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -139,8 +139,13 @@ RSpec.describe IssuePolicy do
create(:project_group_link, group: group, project: project)
end
+ it 'does not allow guest to create todos' do
+ expect(permissions(nil, issue)).to be_allowed(:read_issue)
+ expect(permissions(nil, issue)).to be_disallowed(:create_todo)
+ end
+
it 'allows guests to read issues' do
- expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid)
+ expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid, :create_todo)
expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue)
expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 3a46d5b9226..744822f58d1 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -24,6 +24,7 @@ RSpec.describe MergeRequestPolicy do
mr_perms = %i[create_merge_request_in
create_merge_request_from
read_merge_request
+ create_todo
approve_merge_request
create_note].freeze
@@ -47,6 +48,18 @@ RSpec.describe MergeRequestPolicy do
end
end
+ context 'when merge request is public' do
+ context 'and user is anonymous' do
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ subject { permissions(nil, merge_request) }
+
+ it do
+ is_expected.to be_disallowed(:create_todo)
+ end
+ end
+ end
+
context 'when merge requests have been disabled' do
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 38641558b6b..c57d345ef56 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -102,4 +102,22 @@ RSpec.describe UserPolicy do
end
end
end
+
+ describe "reading a user's group count" do
+ context "when current_user is an admin", :enable_admin_mode do
+ let(:current_user) { create(:user, :admin) }
+
+ it { is_expected.to be_allowed(:read_group_count) }
+ end
+
+ context "for self users" do
+ let(:user) { current_user }
+
+ it { is_expected.to be_allowed(:read_group_count) }
+ end
+
+ context "when accessing a different user's group count" do
+ it { is_expected.not_to be_allowed(:read_group_count) }
+ end
+ end
end
diff --git a/spec/requests/api/graphql/mutations/todos/create_spec.rb b/spec/requests/api/graphql/mutations/todos/create_spec.rb
new file mode 100644
index 00000000000..aca00519682
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/todos/create_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create a todo' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:target) { create(:issue) }
+
+ let(:input) do
+ {
+ 'targetId' => target.to_global_id.to_s
+ }
+ end
+
+ let(:mutation) { graphql_mutation(:todoCreate, input) }
+
+ let(:mutation_response) { graphql_mutation_response(:todoCreate) }
+
+ context 'the user is not allowed to create todo' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to create todo' do
+ before do
+ target.project.add_guest(current_user)
+ end
+
+ it 'creates todo' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['todo']['body']).to eq(target.title)
+ expect(mutation_response['todo']['state']).to eq('pending')
+ end
+ end
+end
diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb
index 6278f00e878..674382ee14f 100644
--- a/spec/services/bulk_create_integration_service_spec.rb
+++ b/spec/services/bulk_create_integration_service_spec.rb
@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe BulkCreateIntegrationService do
include JiraServiceHelper
- before do
+ before_all do
stub_jira_service_test
end
- let_it_be(:instance_integration) { create(:jira_service, :instance) }
- let_it_be(:template_integration) { create(:jira_service, :template) }
+ let_it_be(:excluded_group) { create(:group) }
+ let_it_be(:excluded_project) { create(:project, group: excluded_group) }
+ let(:instance_integration) { create(:jira_service, :instance) }
+ let(:template_integration) { create(:jira_service, :template) }
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
shared_examples 'creates integration from batch ids' do
@@ -46,15 +48,19 @@ RSpec.describe BulkCreateIntegrationService do
described_class.new(integration, batch, association).execute
expect(project.reload.has_external_issue_tracker).to eq(true)
+ expect(excluded_project.reload.has_external_issue_tracker).to eq(false)
end
context 'with an external wiki integration' do
- let(:integration) { create(:external_wiki_service, :instance) }
+ before do
+ integration.update!(category: 'common', type: 'ExternalWikiService')
+ end
it 'updates projects#has_external_wiki for external wiki services' do
described_class.new(integration, batch, association).execute
expect(project.reload.has_external_wiki).to eq(true)
+ expect(excluded_project.reload.has_external_wiki).to eq(false)
end
end
end
@@ -77,14 +83,14 @@ RSpec.describe BulkCreateIntegrationService do
end
end
- context 'with an instance-level integration' do
+ context 'passing an instance-level integration' do
let(:integration) { instance_integration }
let(:inherit_from_id) { integration.id }
context 'with a project association' do
let!(:project) { create(:project) }
let(:created_integration) { project.jira_service }
- let(:batch) { Project.without_integration(integration) }
+ let(:batch) { Project.where(id: project.id) }
let(:association) { 'project' }
it_behaves_like 'creates integration from batch ids'
@@ -101,36 +107,52 @@ RSpec.describe BulkCreateIntegrationService do
end
context 'with a group association' do
- let_it_be(:group) { create(:group) }
+ let!(:group) { create(:group) }
let(:created_integration) { Service.find_by(group: group) }
- let(:batch) { Group.all }
+ let(:batch) { Group.where(id: group.id) }
let(:association) { 'group' }
it_behaves_like 'creates integration from batch ids'
it_behaves_like 'updates inherit_from_id'
+ end
+ end
- context 'with a subgroup association' do
- let_it_be(:group_integration) { create(:jira_service, group: group, project: nil, inherit_from_id: instance_integration.id) }
- let_it_be(:subgroup) { create(:group, parent: group) }
- let(:integration) { group_integration }
- let(:created_integration) { Service.find_by(group: subgroup) }
- let(:batch) { Group.all }
- let(:association) { 'group' }
- let(:inherit_from_id) { instance_integration.id }
-
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
- end
+ context 'passing a group integration' do
+ let_it_be(:group) { create(:group) }
+
+ context 'with a project association' do
+ let!(:project) { create(:project, group: group) }
+ let(:integration) { create(:jira_service, group: group, project: nil) }
+ let(:created_integration) { project.jira_service }
+ let(:batch) { Project.where(id: Project.minimum(:id)..Project.maximum(:id)).without_integration(integration).in_namespace(integration.group.self_and_descendants) }
+ let(:association) { 'project' }
+ let(:inherit_from_id) { integration.id }
+
+ it_behaves_like 'creates integration from batch ids'
+ it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'updates project callbacks'
+ end
+
+ context 'with a group association' do
+ let!(:subgroup) { create(:group, parent: group) }
+ let(:integration) { create(:jira_service, group: group, project: nil, inherit_from_id: instance_integration.id) }
+ let(:created_integration) { Service.find_by(group: subgroup) }
+ let(:batch) { Group.where(id: subgroup.id) }
+ let(:association) { 'group' }
+ let(:inherit_from_id) { instance_integration.id }
+
+ it_behaves_like 'creates integration from batch ids'
+ it_behaves_like 'updates inherit_from_id'
end
end
- context 'with a template integration' do
+ context 'passing a template integration' do
let(:integration) { template_integration }
context 'with a project association' do
let!(:project) { create(:project) }
let(:created_integration) { project.jira_service }
- let(:batch) { Project.all }
+ let(:batch) { Project.where(id: project.id) }
let(:association) { 'project' }
let(:inherit_from_id) { integration.id }
diff --git a/spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb
new file mode 100644
index 00000000000..fbef8be9e88
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'create todo mutation' do
+ let_it_be(:current_user) { create(:user) }
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ context 'when user does not have permission to create todo' do
+ it 'raises error' do
+ expect { mutation.resolve(target_id: global_id_of(target)) }
+ .to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when user has permission to create todo' do
+ it 'creates a todo' do
+ target.resource_parent.add_reporter(current_user)
+
+ result = mutation.resolve(target_id: global_id_of(target))
+
+ expect(result[:todo]).to be_valid
+ expect(result[:todo].target).to eq(target)
+ expect(result[:todo].state).to eq('pending')
+ end
+ end
+end
diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb
index eb32528e3c7..9a5ff2e4518 100644
--- a/spec/views/search/_filter.html.haml_spec.rb
+++ b/spec/views/search/_filter.html.haml_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'search/_filter' do
render
expect(rendered).to have_selector('label[for="dashboard_search_group"]')
- expect(rendered).to have_selector('button#dashboard_search_group')
+ expect(rendered).to have_selector('input#js-search-group-dropdown')
expect(rendered).to have_selector('label[for="dashboard_search_project"]')
expect(rendered).to have_selector('button#dashboard_search_project')