summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-04 18:09:51 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-04 18:09:51 +0000
commitbd979acf95124119d41f75d34cab231229f4dd81 (patch)
treea7ce5127c8ab6d42b27aa8342889c54d2f0090b2
parent4bdfcf93f224edb9c4daff90d95b0c6c92766ea3 (diff)
downloadgitlab-ce-bd979acf95124119d41f75d34cab231229f4dd81.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS78
-rw-r--r--.rubocop_todo/rspec/variable_name.yml4
-rw-r--r--app/assets/javascripts/api/projects_api.js8
-rw-r--r--app/assets/javascripts/batch_comments/components/submit_dropdown.vue4
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js35
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js13
-rw-r--r--app/assets/javascripts/blob/components/blob_edit_header.vue4
-rw-r--r--app/assets/javascripts/ci/runner/admin_runner_show/index.js5
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_jobs.vue5
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue25
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor.vue46
-rw-r--r--app/assets/javascripts/content_editor/components/formatting_toolbar.vue171
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue1
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_button.vue3
-rw-r--r--app/assets/javascripts/deprecated_notes.js11
-rw-r--r--app/assets/javascripts/environments/environment_details/index.vue8
-rw-r--r--app/assets/javascripts/feature_flags/components/feature_flags_table.vue21
-rw-r--r--app/assets/javascripts/invite_members/components/group_select.vue20
-rw-r--r--app/assets/javascripts/invite_members/components/invite_groups_modal.vue2
-rw-r--r--app/assets/javascripts/notes/components/comment_field_layout.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue97
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue2
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue2
-rw-r--r--app/assets/javascripts/snippets/components/snippet_description_edit.vue6
-rw-r--r--app/assets/javascripts/super_sidebar/utils.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue58
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue483
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue48
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue3
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_add_note.vue2
-rw-r--r--app/assets/stylesheets/components/content_editor.scss13
-rw-r--r--app/assets/stylesheets/framework/diffs.scss2
-rw-r--r--app/assets/stylesheets/framework/files.scss5
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss42
-rw-r--r--app/assets/stylesheets/framework/source_editor.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/editor.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss19
-rw-r--r--app/helpers/markup_helper.rb2
-rw-r--r--app/models/resource_events/issue_assignment_event.rb4
-rw-r--r--app/models/resource_events/merge_request_assignment_event.rb4
-rw-r--r--app/services/google_cloud/generate_pipeline_service.rb2
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/projects/_commit_button.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml9
-rw-r--r--app/views/projects/snippets/new.html.haml1
-rw-r--r--app/views/shared/_commit_message_container.html.haml25
-rw-r--r--app/views/shared/_md_preview.html.haml21
-rw-r--r--app/views/shared/_new_commit_form.html.haml11
-rw-r--r--app/views/shared/_zen.html.haml1
-rw-r--r--app/views/shared/blob/_markdown_buttons.html.haml70
-rw-r--r--app/views/shared/notes/_hints.html.haml6
-rw-r--r--config/metrics/counts_all/20210216175520_ci_runners.yml1
-rw-r--r--config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml1
-rw-r--r--config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml1
-rw-r--r--config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml1
-rw-r--r--config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml1
-rw-r--r--config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml1
-rw-r--r--config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml1
-rw-r--r--doc/development/testing_guide/best_practices.md6
-rw-r--r--doc/development/value_stream_analytics.md21
-rw-r--r--doc/user/permissions.md2
-rw-r--r--lib/gitlab/config/loader/yaml.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric.rb17
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric.rb15
-rw-r--r--lib/gitlab/usage_data.rb13
-rw-r--r--lib/gitlab/usage_data_metrics.rb4
-rw-r--r--locale/gitlab.pot46
-rw-r--r--package.json4
-rw-r--r--qa/qa/page/component/snippet.rb10
-rw-r--r--qa/qa/page/component/wiki_page_form.rb5
-rw-r--r--spec/features/abuse_report_spec.rb2
-rw-r--r--spec/features/groups/milestone_spec.rb6
-rw-r--r--spec/features/issuables/markdown_references/jira_spec.rb2
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb10
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb10
-rw-r--r--spec/features/merge_request/batch_comments_spec.rb4
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb4
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb6
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb4
-rw-r--r--spec/features/projects/commit/comments/user_deletes_comments_spec.rb2
-rw-r--r--spec/features/projects/commit/user_comments_on_commit_spec.rb4
-rw-r--r--spec/features/projects/releases/user_creates_release_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb1
-rw-r--r--spec/frontend/api/projects_api_spec.js25
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap2
-rw-r--r--spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js37
-rw-r--r--spec/frontend/ci/runner/components/runner_jobs_spec.js9
-rw-r--r--spec/frontend/ci/runner/mock_data.js2
-rw-r--r--spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap2
-rw-r--r--spec/frontend/content_editor/components/content_editor_spec.js8
-rw-r--r--spec/frontend/content_editor/components/formatting_toolbar_spec.js8
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js18
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_table_spec.js48
-rw-r--r--spec/frontend/invite_members/components/group_select_spec.js121
-rw-r--r--spec/frontend/invite_members/components/invite_groups_modal_spec.js12
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js4
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js35
-rw-r--r--spec/frontend/notes/components/note_form_spec.js4
-rw-r--r--spec/frontend/shortcuts_spec.js8
-rw-r--r--spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap4
-rw-r--r--spec/frontend/super_sidebar/utils_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/form/__snapshots__/form_footer_actions_spec.js.snap14
-rw-r--r--spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js54
-rw-r--r--spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js37
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js70
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js58
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/markdown/toolbar_spec.js15
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric_spec.rb17
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric_spec.rb18
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric_spec.rb13
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric_spec.rb13
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric_spec.rb13
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric_spec.rb17
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric_spec.rb18
-rw-r--r--spec/lib/gitlab/usage_data_metrics_spec.rb12
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb23
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/support/helpers/content_editor_helpers.rb3
-rw-r--r--spec/support/helpers/features/notes_helpers.rb2
-rw-r--r--spec/support/helpers/note_interaction_helpers.rb2
-rw-r--r--spec/support/helpers/usage_data_helpers.rb1
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/reportable_note_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb61
-rw-r--r--yarn.lock16
143 files changed, 1596 insertions, 1032 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 2da26513452..fb0c3383fc5 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1365,6 +1365,84 @@ lib/gitlab/checks/**
/**/javascripts/admin/application_settings/runner_token_expiration/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/usage_quotas/pipelines/ @gitlab-org/ci-cd/verify/frontend @sheldonled @aalakkad @kpalchyk
+## Verify:Runner Fleet Backend
+
+/app/controllers/admin/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/controllers/concerns/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/controllers/groups/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/controllers/projects/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/controllers/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/finders/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/graphql/mutations/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/graphql/resolvers/ci/*_runners_resolver.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/graphql/resolvers/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/graphql/types/ci/runner_*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/graphql/types/namespace/shared_runners_setting_enum.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/graphql/types/permission_types/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/models/ci/build_runner_session.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/models/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/models/concerns/ci/has_runner_executor.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/models/concerns/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/models/preloaders/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/policies/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/presenters/ci/runner_*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/serializers/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/services/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/app/workers/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/db/docs/ci_runner*.yml @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/controllers/ee/admin/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/controllers/ee/groups/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/graphql/ee/mutations/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/graphql/ee/types/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/graphql/resolvers/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/models/ee/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/policies/ee/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/services/audit_events/*runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/services/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/services/ee/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/app/workers/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/graphql/ee/mutations/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/graphql/resolvers/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/graphql/types/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/models/ee/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/policies/ee/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/requests/api/graphql/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/services/audit_events/*runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/services/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/ee/spec/workers/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/api/ci/helpers/runner.rb @gitlab-org/maintainers/cicd-verify @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/api/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/api/ci/runner.rb @gitlab-org/maintainers/cicd-verify @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/api/entities/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/gitlab/audit/ci_runner_token_author.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/gitlab/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/gitlab/seeders/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/lib/tasks/gitlab/seed/runner_fleet.rake @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/factories/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/finders/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/graphql/mutations/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/graphql/resolvers/ci/*runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/graphql/types/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/graphql/types/permission_types/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/lib/api/ci/helpers/runner_*_spec.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/lib/api/ci/helpers/runner_spec.rb @gitlab-org/maintainers/cicd-verify @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/lib/gitlab/ci/runner_*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/lib/gitlab/seeders/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/models/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/models/concerns/runners*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/models/preloaders/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/requests/api/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/requests/api/graphql/ci/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/requests/api/graphql/mutations/ci/runner/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/requests/api/graphql/mutations/ci/runners_registration_token/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/requests/api/graphql/project/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/services/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/support/shared_contexts/graphql/resolvers/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/support/shared_examples/features/runner*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/tasks/gitlab/seed/runner_fleet_*.rb @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+/spec/workers/ci/runners/ @gitlab-org/ci-cd/runner-fleet-team/backend-approvers
+
# CI/CD templates require approval from specific owners.
/lib/gitlab/ci/templates/ @gitlab-org/maintainers/cicd-templates
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
diff --git a/.rubocop_todo/rspec/variable_name.yml b/.rubocop_todo/rspec/variable_name.yml
deleted file mode 100644
index 8858fbd9eb7..00000000000
--- a/.rubocop_todo/rspec/variable_name.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-RSpec/VariableName:
- Exclude:
- - 'spec/models/user_spec.rb'
diff --git a/app/assets/javascripts/api/projects_api.js b/app/assets/javascripts/api/projects_api.js
index c72a913aacd..f69bb8ad7cb 100644
--- a/app/assets/javascripts/api/projects_api.js
+++ b/app/assets/javascripts/api/projects_api.js
@@ -8,6 +8,7 @@ const PROJECT_ALL_MEMBERS_PATH = '/api/:version/projects/:id/members/all';
const PROJECT_IMPORT_MEMBERS_PATH = '/api/:version/projects/:id/import_project_members/:project_id';
const PROJECT_REPOSITORY_SIZE_PATH = '/api/:version/projects/:id/repository_size';
const PROJECT_TRANSFER_LOCATIONS_PATH = 'api/:version/projects/:id/transfer_locations';
+const PROJECT_SHARE_LOCATIONS = 'api/:version/projects/:id/share_locations';
export function getProjects(query, options, callback = () => {}) {
const url = buildApiUrl(PROJECTS_PATH);
@@ -70,3 +71,10 @@ export const getProjectMembers = (projectId, inherited = false) => {
return axios.get(url);
};
+
+export const getProjectShareLocations = (projectId, params = {}) => {
+ const url = buildApiUrl(PROJECT_SHARE_LOCATIONS).replace(':id', projectId);
+ const defaultParams = { per_page: DEFAULT_PER_PAGE };
+
+ return axios.get(url, { params: { ...defaultParams, ...params } });
+};
diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
index 9005c1b1220..6aa5bb715b2 100644
--- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
+++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
@@ -117,9 +117,7 @@ export default {
{{ __('Summary comment (optional)') }}
</template>
<div class="common-note-form gfm-form">
- <div
- class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-base gl-border-gray-100"
- >
+ <div class="comment-warning-wrapper-large gl-border-0 gl-bg-white">
<markdown-field
:is-submitting="isSubmitting"
:add-spacing-classes="false"
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index dc408f5a950..bcd92d09033 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -121,9 +121,7 @@ MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form)
const markdownPreview = new MarkdownPreview();
const previewButtonSelector = '.js-md-preview-button';
-const writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null;
-const markdownToolbar = $('.md-header-toolbar');
$(document).on('markdown-preview:show', (e, $form) => {
if (!$form) {
@@ -134,13 +132,15 @@ $(document).on('markdown-preview:show', (e, $form) => {
lastTextareaHeight = lastTextareaPreviewed.height();
// toggle tabs
- $form.find(writeButtonSelector).parent().removeClass('active');
- $form.find(previewButtonSelector).parent().addClass('active');
+ $form.find(previewButtonSelector).val('edit');
+ $form.find(previewButtonSelector).children('span.gl-button-text').text(__('Continue editing'));
+ $form.find(previewButtonSelector).addClass('gl-shadow-none! gl-bg-transparent!');
// toggle content
$form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show();
- markdownToolbar.removeClass('active');
+ $form.find('.md-header-toolbar, .js-zen-enter').addClass('gl-display-none!');
+
markdownPreview.showPreview($form);
});
@@ -155,14 +155,14 @@ $(document).on('markdown-preview:hide', (e, $form) => {
}
// toggle tabs
- $form.find(writeButtonSelector).parent().addClass('active');
- $form.find(previewButtonSelector).parent().removeClass('active');
+ $form.find(previewButtonSelector).val('preview');
+ $form.find(previewButtonSelector).children('span.gl-button-text').text(__('Preview'));
// toggle content
$form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus();
$form.find('.md-preview-holder').hide();
- markdownToolbar.addClass('active');
+ $form.find('.md-header-toolbar, .js-zen-enter').removeClass('gl-display-none!');
markdownPreview.hideReferencedCommands($form);
});
@@ -183,13 +183,26 @@ $(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
$(document).on('click', previewButtonSelector, function (e) {
e.preventDefault();
const $form = $(this).closest('form');
- $(document).triggerHandler('markdown-preview:show', [$form]);
+ const eventName = e.currentTarget.getAttribute('value') === 'preview' ? 'show' : 'hide';
+ $(document).triggerHandler(`markdown-preview:${eventName}`, [$form]);
+});
+
+$(document).on('mousedown', previewButtonSelector, function (e) {
+ e.preventDefault();
+ const $form = $(this).closest('form');
+ $form.find(previewButtonSelector).removeClass('gl-shadow-none! gl-bg-transparent!');
+});
+
+$(document).on('mouseenter', previewButtonSelector, function (e) {
+ e.preventDefault();
+ const $form = $(this).closest('form');
+ $form.find(previewButtonSelector).removeClass('gl-bg-transparent!');
});
-$(document).on('click', writeButtonSelector, function (e) {
+$(document).on('mouseleave', previewButtonSelector, function (e) {
e.preventDefault();
const $form = $(this).closest('form');
- $(document).triggerHandler('markdown-preview:hide', [$form]);
+ $form.find(previewButtonSelector).addClass('gl-bg-transparent!');
});
export default MarkdownPreview;
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 5c6e4665a5c..efb462f4778 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -186,13 +186,14 @@ export default class Shortcuts {
}
static toggleMarkdownPreview(e) {
- // Check if short-cut was triggered while in Write Mode
- const $target = $(e.target);
- const $form = $target.closest('form');
+ const $form = $(e.target).closest('form');
+ const toggle = $('.js-md-preview-button', $form).get(0);
+
+ if (!toggle) return;
+
+ toggle.focus();
+ toggle.click();
- if ($target.hasClass('js-note-text')) {
- $('.js-md-preview-button', $form).focus();
- }
$(document).triggerHandler('markdown-preview:toggle', [e]);
}
diff --git a/app/assets/javascripts/blob/components/blob_edit_header.vue b/app/assets/javascripts/blob/components/blob_edit_header.vue
index 5715635fd13..8fd3f03ff71 100644
--- a/app/assets/javascripts/blob/components/blob_edit_header.vue
+++ b/app/assets/javascripts/blob/components/blob_edit_header.vue
@@ -35,9 +35,7 @@ export default {
<div class="gl-display-flex gl-align-items-center gl-w-full">
<gl-form-input
v-model="name"
- :placeholder="
- s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
- "
+ :placeholder="s__('Snippets|File name (e.g. test.rb)')"
name="snippet_file_name"
class="form-control js-snippet-file-name"
type="text"
diff --git a/app/assets/javascripts/ci/runner/admin_runner_show/index.js b/app/assets/javascripts/ci/runner/admin_runner_show/index.js
index cbd25819303..f34cd5508ce 100644
--- a/app/assets/javascripts/ci/runner/admin_runner_show/index.js
+++ b/app/assets/javascripts/ci/runner/admin_runner_show/index.js
@@ -17,7 +17,7 @@ export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => {
return null;
}
- const { runnerId, runnersPath } = el.dataset;
+ const { runnerId, runnersPath, emptyStateImage } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -26,6 +26,9 @@ export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => {
return new Vue({
el,
apolloProvider,
+ provide: {
+ emptyStateImage,
+ },
render(h) {
return h(AdminRunnerShowApp, {
props: {
diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs.vue b/app/assets/javascripts/ci/runner/components/runner_jobs.vue
index f5287f597ab..62e09346c2c 100644
--- a/app/assets/javascripts/ci/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_jobs.vue
@@ -7,6 +7,7 @@ import { captureException } from '../sentry_utils';
import { getPaginationVariables } from '../utils';
import RunnerJobsTable from './runner_jobs_table.vue';
import RunnerPagination from './runner_pagination.vue';
+import RunnerJobsEmptyState from './runner_jobs_empty_state.vue';
export default {
name: 'RunnerJobs',
@@ -14,7 +15,9 @@ export default {
GlSkeletonLoader,
RunnerJobsTable,
RunnerPagination,
+ RunnerJobsEmptyState,
},
+
props: {
runner: {
type: Object,
@@ -75,7 +78,7 @@ export default {
<gl-skeleton-loader />
</div>
<runner-jobs-table v-else-if="jobs.items.length" :jobs="jobs.items" />
- <p v-else>{{ $options.I18N_NO_JOBS_FOUND }}</p>
+ <runner-jobs-empty-state v-else />
<runner-pagination :disabled="loading" :page-info="jobs.pageInfo" @input="onPaginationInput" />
</div>
diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue
new file mode 100644
index 00000000000..68fb38c1a29
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue
@@ -0,0 +1,25 @@
+<script>
+import { GlEmptyState } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ i18n: {
+ title: s__('Runners|This runner has not run any jobs'),
+ description: s__(
+ 'Runners|Make sure the runner is online and available to run jobs (not paused). Jobs display here when the runner picks them up.',
+ ),
+ },
+ components: {
+ GlEmptyState,
+ },
+ inject: ['emptyStateImage'],
+};
+</script>
+
+<template>
+ <gl-empty-state :svg-path="emptyStateImage" :title="$options.i18n.title">
+ <template #description>
+ <p>{{ $options.i18n.description }}</p>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue
index f9d48708473..2b2c4a5ac1c 100644
--- a/app/assets/javascripts/content_editor/components/content_editor.vue
+++ b/app/assets/javascripts/content_editor/components/content_editor.vue
@@ -3,7 +3,6 @@ import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2';
import { GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { VARIANT_DANGER } from '~/alert';
-import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
import { createContentEditor } from '../services/create_content_editor';
import { ALERT_EVENT, TIPTAP_AUTOFOCUS_OPTIONS } from '../constants';
import ContentEditorAlert from './content_editor_alert.vue';
@@ -30,7 +29,6 @@ export default {
LinkBubbleMenu,
MediaBubbleMenu,
EditorStateObserver,
- EditorModeDropdown,
},
props: {
renderMarkdown: {
@@ -100,6 +98,11 @@ export default {
latestMarkdown: null,
};
},
+ computed: {
+ showPlaceholder() {
+ return this.placeholder && !this.markdown && !this.focused;
+ },
+ },
watch: {
markdown(markdown) {
if (markdown !== this.latestMarkdown) {
@@ -196,11 +199,6 @@ export default {
markdown: this.latestMarkdown,
});
},
- handleEditorModeChanged(mode) {
- if (mode === 'markdown') {
- this.$emit('enableMarkdownEditor');
- }
- },
},
i18n: {
quickActionsText: s__(
@@ -226,34 +224,36 @@ export default {
:class="{ 'is-focused': focused }"
>
<formatting-toolbar ref="toolbar" @enableMarkdownEditor="$emit('enableMarkdownEditor')" />
- <div class="gl-relative gl-mt-4">
+ <div class="gl-relative">
<formatting-bubble-menu />
<code-block-bubble-menu />
<link-bubble-menu />
<media-bubble-menu />
- <div v-if="placeholder && !markdown && !focused" class="gl-absolute gl-text-gray-400">
+ <div v-if="showPlaceholder" class="gl-absolute gl-text-gray-400 gl-px-5 gl-pt-4">
{{ placeholder }}
</div>
<tiptap-editor-content
- class="md"
+ class="md gl-px-5"
data-testid="content_editor_editablebox"
:editor="contentEditor.tiptapEditor"
/>
<loading-indicator v-if="isLoading" />
- <div class="gl-display-flex gl-border-t gl-py-2 gl-text-secondary">
- <div class="gl-w-full">
- <template v-if="quickActionsDocsPath">
- <gl-sprintf :message="$options.i18n.quickActionsText">
- <template #keyboard="{ content }">
- <kbd>{{ content }}</kbd>
- </template>
- <template #quickActionsDocsLink="{ content }">
- <gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </template>
+ <div
+ v-if="quickActionsDocsPath"
+ class="gl-display-flex gl-align-items-center gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-4 gl-mx-2 gl-mb-2 gl-bg-gray-10 gl-text-secondary"
+ >
+ <div class="gl-w-full gl-line-height-32 gl-font-sm">
+ <gl-sprintf :message="$options.i18n.quickActionsText">
+ <template #keyboard="{ content }">
+ <kbd>{{ content }}</kbd>
+ </template>
+ <template #quickActionsDocsLink="{ content }">
+ <gl-link :href="quickActionsDocsPath" target="_blank" class="gl-font-sm">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
</div>
- <editor-mode-dropdown size="small" value="richText" @input="handleEditorModeChanged" />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue
index cd9fdeeca46..e7e520a55da 100644
--- a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue
@@ -1,4 +1,5 @@
<script>
+import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue';
import trackUIControl from '../services/track_ui_control';
import ToolbarButton from './toolbar_button.vue';
import ToolbarAttachmentButton from './toolbar_attachment_button.vue';
@@ -13,94 +14,106 @@ export default {
ToolbarTableButton,
ToolbarAttachmentButton,
ToolbarMoreDropdown,
+ EditorModeSwitcher,
},
methods: {
trackToolbarControlExecution({ contentType, value }) {
trackUIControl({ property: contentType, value });
},
+ handleEditorModeChanged() {
+ this.$emit('enableMarkdownEditor');
+ },
},
};
</script>
<template>
- <div
- class="gl-w-full gl-border-b gl-display-flex gl-justify-content-end"
- data-testid="formatting-toolbar"
- >
- <div class="gl-py-2 gl-display-flex gl-flex-wrap gl-align-items-end">
- <toolbar-text-style-dropdown
- data-testid="text-styles"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="bold"
- content-type="bold"
- icon-name="bold"
- editor-command="toggleBold"
- :label="__('Bold text')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="italic"
- content-type="italic"
- icon-name="italic"
- editor-command="toggleItalic"
- :label="__('Italic text')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="blockquote"
- content-type="blockquote"
- icon-name="quote"
- editor-command="toggleBlockquote"
- :label="__('Insert a quote')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="code"
- content-type="code"
- icon-name="code"
- editor-command="toggleCode"
- :label="__('Code')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="link"
- content-type="link"
- icon-name="link"
- editor-command="editLink"
- :label="__('Insert link')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="bullet-list"
- content-type="bulletList"
- icon-name="list-bulleted"
- class="gl-display-none gl-sm-display-inline"
- editor-command="toggleBulletList"
- :label="__('Add a bullet list')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="ordered-list"
- content-type="orderedList"
- icon-name="list-numbered"
- class="gl-display-none gl-sm-display-inline"
- editor-command="toggleOrderedList"
- :label="__('Add a numbered list')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-button
- data-testid="task-list"
- content-type="taskList"
- icon-name="list-task"
- class="gl-display-none gl-sm-display-inline"
- editor-command="toggleTaskList"
- :label="__('Add a checklist')"
- @execute="trackToolbarControlExecution"
- />
- <toolbar-table-button data-testid="table" @execute="trackToolbarControlExecution" />
- <toolbar-attachment-button data-testid="attachment" @execute="trackToolbarControlExecution" />
- <toolbar-more-dropdown data-testid="more" @execute="trackToolbarControlExecution" />
+ <div class="gl-mx-2 gl-mt-2">
+ <div
+ class="gl-w-full gl-display-flex gl-align-items-center gl-flex-wrap gl-bg-gray-50 gl-px-2 gl-rounded-base gl-justify-content-space-between"
+ data-testid="formatting-toolbar"
+ >
+ <div class="gl-py-2 gl-display-flex gl-flex-wrap-wrap">
+ <toolbar-text-style-dropdown
+ data-testid="text-styles"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="bold"
+ content-type="bold"
+ icon-name="bold"
+ editor-command="toggleBold"
+ :label="__('Bold text')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="italic"
+ content-type="italic"
+ icon-name="italic"
+ editor-command="toggleItalic"
+ :label="__('Italic text')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="blockquote"
+ content-type="blockquote"
+ icon-name="quote"
+ editor-command="toggleBlockquote"
+ :label="__('Insert a quote')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="code"
+ content-type="code"
+ icon-name="code"
+ editor-command="toggleCode"
+ :label="__('Code')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="link"
+ content-type="link"
+ icon-name="link"
+ editor-command="editLink"
+ :label="__('Insert link')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="bullet-list"
+ content-type="bulletList"
+ icon-name="list-bulleted"
+ class="gl-display-none gl-sm-display-inline"
+ editor-command="toggleBulletList"
+ :label="__('Add a bullet list')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="ordered-list"
+ content-type="orderedList"
+ icon-name="list-numbered"
+ class="gl-display-none gl-sm-display-inline"
+ editor-command="toggleOrderedList"
+ :label="__('Add a numbered list')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-button
+ data-testid="task-list"
+ content-type="taskList"
+ icon-name="list-task"
+ class="gl-display-none gl-sm-display-inline"
+ editor-command="toggleTaskList"
+ :label="__('Add a checklist')"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-table-button data-testid="table" @execute="trackToolbarControlExecution" />
+ <toolbar-attachment-button
+ data-testid="attachment"
+ @execute="trackToolbarControlExecution"
+ />
+ <toolbar-more-dropdown data-testid="more" @execute="trackToolbarControlExecution" />
+ </div>
+ <div class="content-editor-switcher gl-display-flex gl-align-items-center gl-ml-auto">
+ <editor-mode-switcher size="small" value="richText" @input="handleEditorModeChanged" />
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue b/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue
index efb9a5b07b5..1f18090e7d7 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue
@@ -46,6 +46,7 @@ export default {
:title="__('Attach a file or image')"
category="tertiary"
icon="paperclip"
+ size="small"
lazy
@click="openFileUpload"
/>
diff --git a/app/assets/javascripts/content_editor/components/toolbar_button.vue b/app/assets/javascripts/content_editor/components/toolbar_button.vue
index cef026c5bc6..1f3c7062b67 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_button.vue
@@ -47,7 +47,7 @@ export default {
size: {
type: String,
required: false,
- default: 'medium',
+ default: 'small',
},
},
data() {
@@ -82,6 +82,7 @@ export default {
:aria-label="label"
:title="label"
:icon="iconName"
+ class="gl-mr-3"
@click="execute"
/>
</editor-state-observer>
diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js
index 537c810bcff..a46a8d4affa 100644
--- a/app/assets/javascripts/deprecated_notes.js
+++ b/app/assets/javascripts/deprecated_notes.js
@@ -599,7 +599,10 @@ export default class Notes {
// remove validation errors
form.find('.js-errors').remove();
// reset text and preview
- form.find('.js-md-write-button').click();
+ if (form.find('.js-md-preview-button').val() === 'edit') {
+ form.find('.js-md-preview-button').click();
+ }
+
form.find('.js-note-text').val('').trigger('input');
form.find('.js-note-text').each(function reset() {
this.$autosave.reset();
@@ -939,6 +942,7 @@ export default class Notes {
const replyLink = $(target).closest('.js-discussion-reply-button');
// insert the form after the button
replyLink.closest('.discussion-reply-holder').hide().after(form);
+
// show the form
return this.setupDiscussionNoteForm(replyLink, form);
}
@@ -1241,7 +1245,10 @@ export default class Notes {
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
$editForm.find('.js-note-text').focus().val(originalContent);
- $editForm.find('.js-md-write-button').trigger('click');
+ // reset preview
+ if ($editForm.find('.js-md-preview-button').val() === 'edit') {
+ $editForm.find('.js-md-preview-button').click();
+ }
$editForm.find('.referenced-users').hide();
}
diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue
index 1b7320df674..ff2dd9935ae 100644
--- a/app/assets/javascripts/environments/environment_details/index.vue
+++ b/app/assets/javascripts/environments/environment_details/index.vue
@@ -1,5 +1,6 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { logError } from '~/lib/logger';
import { toggleQueryPollingByVisibility, etagQueryHeaders } from '~/graphql_shared/utils';
import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
@@ -147,6 +148,13 @@ export default {
this.isPrefetchingPages = false;
},
},
+ errorCaptured(error) {
+ Sentry.withScope((scope) => {
+ scope.setTag('vue_component', 'EnvironmentDetailsIndex');
+
+ Sentry.captureException(error);
+ });
+ },
mounted() {
if (this.graphqlEtagKey) {
toggleQueryPollingByVisibility(
diff --git a/app/assets/javascripts/feature_flags/components/feature_flags_table.vue b/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
index dee1d239c9f..37a0c679287 100644
--- a/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
+++ b/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
@@ -1,5 +1,5 @@
<script>
-import { GlBadge, GlButton, GlTooltipDirective, GlModal, GlToggle } from '@gitlab/ui';
+import { GlBadge, GlButton, GlTooltipDirective, GlIcon, GlModal, GlToggle } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { labelForStrategy } from '../utils';
@@ -15,6 +15,7 @@ export default {
components: {
GlBadge,
GlButton,
+ GlIcon,
GlModal,
GlToggle,
StrategyLabel,
@@ -116,7 +117,12 @@ export default {
</div>
<template v-for="featureFlag in featureFlags">
- <div :key="featureFlag.id" class="gl-responsive-table-row" role="row">
+ <div
+ :key="featureFlag.id"
+ :data-testid="featureFlag.id"
+ class="gl-responsive-table-row"
+ role="row"
+ >
<div class="table-section section-10" role="gridcell">
<div class="table-mobile-header" role="rowheader">{{ s__('FeatureFlags|ID') }}</div>
<div class="table-mobile-content js-feature-flag-id">
@@ -155,9 +161,14 @@ export default {
<div class="feature-flag-name text-monospace text-truncate">
{{ featureFlag.name }}
</div>
- </div>
- <div class="feature-flag-description text-secondary text-truncate">
- {{ featureFlag.description }}
+ <div class="feature-flag-description">
+ <gl-icon
+ v-if="featureFlag.description"
+ v-gl-tooltip.hover="featureFlag.description"
+ class="gl-ml-3 gl-mr-3"
+ name="information-o"
+ />
+ </div>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue
index 0e9781d77fe..5bb3b6b98e6 100644
--- a/app/assets/javascripts/invite_members/components/group_select.vue
+++ b/app/assets/javascripts/invite_members/components/group_select.vue
@@ -8,7 +8,7 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { s__ } from '~/locale';
-import { getGroups, getDescendentGroups } from '~/rest_api';
+import { getGroups, getDescendentGroups, getProjectShareLocations } from '~/rest_api';
import { SEARCH_DELAY, GROUP_FILTERS } from '../constants';
export default {
@@ -29,6 +29,10 @@ export default {
required: false,
default: GROUP_FILTERS.ALL,
},
+ sourceId: {
+ type: String,
+ required: true,
+ },
parentGroupId: {
type: Number,
required: false,
@@ -38,6 +42,10 @@ export default {
type: Array,
required: true,
},
+ isProject: {
+ type: Boolean,
+ required: true,
+ },
},
data() {
return {
@@ -79,7 +87,7 @@ export default {
const rawGroups = response.map((group) => ({
id: group.id,
name: group.full_name,
- path: group.path,
+ path: group.full_path,
avatarUrl: group.avatar_url,
}));
@@ -94,6 +102,14 @@ export default {
this.$emit('input', this.selectedGroup);
},
fetchGroups() {
+ if (this.isProject) {
+ return new Promise((resolve, reject) => {
+ getProjectShareLocations(this.sourceId, { search: this.searchTerm })
+ .then(({ data }) => resolve(data))
+ .catch(reject);
+ });
+ }
+
switch (this.groupsFilter) {
case GROUP_FILTERS.DESCENDANT_GROUPS:
return getDescendentGroups(
diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
index 51355baef99..03513f74aa6 100644
--- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
@@ -203,8 +203,10 @@ export default {
<group-select
v-model="groupToBeSharedWith"
:groups-filter="groupSelectFilter"
+ :source-id="id"
:parent-group-id="groupSelectParentId"
:invalid-groups="invalidGroups"
+ :is-project="isProject"
@input="clearValidation"
/>
</template>
diff --git a/app/assets/javascripts/notes/components/comment_field_layout.vue b/app/assets/javascripts/notes/components/comment_field_layout.vue
index 02d128eb119..cfe4baaa1f9 100644
--- a/app/assets/javascripts/notes/components/comment_field_layout.vue
+++ b/app/assets/javascripts/notes/components/comment_field_layout.vue
@@ -67,7 +67,7 @@ export default {
</script>
<template>
<div
- class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-base gl-border-gray-100"
+ class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-lg gl-border-gray-100 gl-bg-white"
>
<div
v-if="withAlertContainer"
diff --git a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
index 1dd07fe90d2..a0d2b47c89c 100644
--- a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
@@ -21,7 +21,7 @@ export default {
'li',
{
class:
- 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base clearfix',
+ 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base clearfix gl-pt-5',
},
[h('ul', { class: 'notes' }, children)],
);
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index bce17aebd64..27fb116d213 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,11 +1,16 @@
<script>
-import { GlTooltipDirective, GlIcon, GlButton, GlDropdownItem } from '@gitlab/ui';
+import {
+ GlTooltipDirective,
+ GlIcon,
+ GlButton,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import Api from '~/api';
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import { createAlert } from '~/alert';
import { TYPE_ISSUE } from '~/issues/constants';
-import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale';
import eventHub from '~/sidebar/event_hub';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
@@ -29,7 +34,8 @@ export default {
ReplyButton,
TimelineEventButton,
GlButton,
- GlDropdownItem,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
UserAccessRoleBadge,
EmojiPicker: () => import('~/emoji/components/picker.vue'),
AbuseCategorySelector,
@@ -208,18 +214,23 @@ export default {
methods: {
...mapActions(['toggleAwardRequest', 'promoteCommentToTimelineEvent']),
onEdit() {
+ this.closeMoreActionsDropdown();
this.$emit('handleEdit');
},
onDelete() {
+ this.closeMoreActionsDropdown();
this.$emit('handleDelete');
},
onResolve() {
this.$emit('handleResolve');
},
- closeTooltip() {
- this.$nextTick(() => {
- this.$root.$emit(BV_HIDE_TOOLTIP);
- });
+ onAbuse() {
+ this.closeMoreActionsDropdown();
+ this.toggleReportAbuseDrawer(true);
+ },
+ onCopyUrl() {
+ this.closeMoreActionsDropdown();
+ this.$toast.show(__('Link copied to clipboard.'));
},
handleAssigneeUpdate(assignees) {
this.$emit('updateAssignees', assignees);
@@ -230,6 +241,8 @@ export default {
let { assignees } = this;
const { project_id, iid } = this.getNoteableData;
+ this.closeMoreActionsDropdown();
+
if (this.isUserAssigned) {
assignees = assignees.filter((assignee) => assignee.id !== this.author.id);
} else {
@@ -258,6 +271,11 @@ export default {
toggleReportAbuseDrawer(isOpen) {
this.isReportAbuseDrawerOpen = isOpen;
},
+ closeMoreActionsDropdown() {
+ if (this.shouldShowActionsDropdown && this.$refs.moreActionsDropdown) {
+ this.$refs.moreActionsDropdown.close();
+ }
+ },
},
};
</script>
@@ -354,48 +372,61 @@ export default {
class="note-action-button js-note-delete"
@click="onDelete"
/>
- <div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions">
- <!-- eslint-disable @gitlab/vue-no-data-toggle -->
- <gl-button
+ <div v-else-if="shouldShowActionsDropdown" class="more-actions dropdown">
+ <gl-disclosure-dropdown
+ ref="moreActionsDropdown"
v-gl-tooltip
:title="$options.i18n.moreActionsLabel"
:aria-label="$options.i18n.moreActionsLabel"
icon="ellipsis_v"
category="tertiary"
+ placement="right"
class="note-action-button more-actions-toggle"
- data-toggle="dropdown"
- @click="closeTooltip"
- />
- <!-- eslint-enable @gitlab/vue-no-data-toggle -->
- <ul class="dropdown-menu more-actions-dropdown dropdown-menu-right">
- <gl-dropdown-item
+ no-caret
+ >
+ <gl-disclosure-dropdown-item
v-if="canEdit"
class="js-note-edit gl-sm-display-none!"
- @click.prevent="onEdit"
+ @action="onEdit"
>
- {{ __('Edit comment') }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ <template #list-item>
+ {{ __('Edit comment') }}
+ </template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item
v-if="canReportAsAbuse"
data-testid="report-abuse-button"
- @click="toggleReportAbuseDrawer(true)"
+ @action="onAbuse"
>
- {{ $options.i18n.reportAbuse }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ <template #list-item>
+ {{ $options.i18n.reportAbuse }}
+ </template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item
v-if="noteUrl"
class="js-btn-copy-note-link"
:data-clipboard-text="noteUrl"
+ @action="onCopyUrl"
+ >
+ <template #list-item>
+ {{ __('Copy link') }}
+ </template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item
+ v-if="canAssign"
+ data-testid="assign-user"
+ @action="assignUser"
>
- {{ __('Copy link') }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canAssign" data-testid="assign-user" @click="assignUser">
- {{ displayAssignUserText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canEdit" class="js-note-delete" @click.prevent="onDelete">
- <span class="text-danger">{{ __('Delete comment') }}</span>
- </gl-dropdown-item>
- </ul>
+ <template #list-item>
+ {{ displayAssignUserText }}
+ </template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item v-if="canEdit" class="js-note-delete" @action="onDelete">
+ <template #list-item>
+ <span class="text-danger">{{ __('Delete comment') }}</span>
+ </template>
+ </gl-disclosure-dropdown-item>
+ </gl-disclosure-dropdown>
</div>
<!-- IMPORTANT: show this component lazily because it causes layout thrashing -->
<!-- https://gitlab.com/gitlab-org/gitlab/-/issues/331172#note_1269378396 -->
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 3375e366ecf..375b16f6ce2 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -319,7 +319,7 @@ export default {
/>
<li
v-else-if="canShowReplyActions && showReplies"
- :class="{ 'is-replying': isReplying }"
+ :class="{ 'is-replying gl-bg-white! gl-pt-0!': isReplying }"
class="discussion-reply-holder gl-border-t-0! clearfix"
>
<discussion-actions
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index 151c38d01dc..1678e51a29d 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -256,7 +256,7 @@ export default {
<form-footer-actions>
<template #prepend>
<gl-button
- class="js-no-auto-disable"
+ class="js-no-auto-disable gl-mr-2"
category="primary"
type="submit"
variant="confirm"
diff --git a/app/assets/javascripts/snippets/components/snippet_description_edit.vue b/app/assets/javascripts/snippets/components/snippet_description_edit.vue
index bac423f6838..3ce7ea231ff 100644
--- a/app/assets/javascripts/snippets/components/snippet_description_edit.vue
+++ b/app/assets/javascripts/snippets/components/snippet_description_edit.vue
@@ -35,11 +35,7 @@ export default {
<div class="js-collapsed" :class="{ 'd-none': value }">
<gl-form-input
class="form-control"
- :placeholder="
- s__(
- 'Snippets|Optionally add a description about what your snippet does or how to use it…',
- )
- "
+ :placeholder="s__('Snippets|Describe what your snippet does or how to use it…')"
data-qa-selector="description_placeholder"
/>
</div>
diff --git a/app/assets/javascripts/super_sidebar/utils.js b/app/assets/javascripts/super_sidebar/utils.js
index 8e4250d0e39..4bbef67742c 100644
--- a/app/assets/javascripts/super_sidebar/utils.js
+++ b/app/assets/javascripts/super_sidebar/utils.js
@@ -26,7 +26,9 @@ const sortItemsByFrequencyAndLastAccess = (items) =>
// This imitates getTopFrequentItems from app/assets/javascripts/frequent_items/utils.js, but
// adjusts the rules to accommodate for the context switcher's designs.
-export const getTopFrequentItems = (items = [], maxCount) => {
+export const getTopFrequentItems = (items, maxCount) => {
+ if (!Array.isArray(items)) return [];
+
const frequentItems = items.filter((item) => item.frequency >= FREQUENT_ITEMS.ELIGIBLE_FREQUENCY);
sortItemsByFrequencyAndLastAccess(frequentItems);
diff --git a/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue b/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue
index fe221d2fefa..f22a17b4603 100644
--- a/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue
+++ b/app/assets/javascripts/vue_shared/components/form/form_footer_actions.vue
@@ -1,7 +1,7 @@
<template>
- <footer class="form-actions d-flex justify-content-between">
- <div><slot name="prepend"></slot></div>
- <div><slot></slot></div>
- <div><slot name="append"></slot></div>
+ <footer class="gl-mt-5 footer-block">
+ <slot name="prepend"></slot>
+ <slot></slot>
+ <slot name="append"></slot>
</footer>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
index feee132629f..1377a40fcf0 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
@@ -86,6 +86,7 @@ export default {
category="tertiary"
placement="right"
searchable
+ size="small"
class="comment-template-dropdown"
:searching="$apollo.queries.savedReplies.loading"
@shown="fetchCommentTemplates"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue
index a66becb5c92..e88c7f75745 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue
@@ -43,6 +43,7 @@ export default {
:aria-label="__('Insert or edit diagram')"
category="tertiary"
icon="diagram"
+ size="small"
@click="launchDrawioEditor"
/>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue
deleted file mode 100644
index 7803d6f53e0..00000000000
--- a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<script>
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- components: {
- GlDropdown,
- GlDropdownItem,
- },
- props: {
- size: {
- type: String,
- required: false,
- default: 'medium',
- },
- value: {
- type: String,
- required: true,
- },
- },
- computed: {
- markdownEditorSelected() {
- return this.value === 'markdown';
- },
- text() {
- return this.markdownEditorSelected ? __('Editing markdown') : __('Editing rich text');
- },
- },
-};
-</script>
-<template>
- <gl-dropdown
- category="tertiary"
- data-qa-selector="editing_mode_switcher"
- :size="size"
- :text="text"
- right
- >
- <gl-dropdown-item
- is-check-item
- :is-checked="!markdownEditorSelected"
- @click="$emit('input', 'richText')"
- ><div class="gl-font-weight-bold">{{ __('Rich text') }}</div>
- <div class="gl-text-secondary">
- {{ __('View the formatted output in real-time as you edit.') }}
- </div>
- </gl-dropdown-item>
- <gl-dropdown-item
- is-check-item
- :is-checked="markdownEditorSelected"
- @click="$emit('input', 'markdown')"
- ><div class="gl-font-weight-bold">{{ __('Markdown') }}</div>
- <div class="gl-text-secondary">
- {{ __('View and edit markdown, with the option to preview the formatted output.') }}
- </div></gl-dropdown-item
- >
- </gl-dropdown>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
new file mode 100644
index 00000000000..645975ca565
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
@@ -0,0 +1,32 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ },
+ props: {
+ value: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ markdownEditorSelected() {
+ return this.value === 'markdown';
+ },
+ text() {
+ return this.markdownEditorSelected ? __('Switch to rich text') : __('Switch to Markdown');
+ },
+ },
+};
+</script>
+<template>
+ <gl-button
+ class="btn btn-default btn-sm gl-button btn-default-tertiary"
+ data-qa-selector="editing_mode_switcher"
+ @click="$emit('input')"
+ >{{ text }}</gl-button
+ >
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index cc153747765..3c4070105d1 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import $ from 'jquery';
import { debounce, unescape } from 'lodash';
import { createAlert } from '~/alert';
@@ -27,6 +27,7 @@ export default {
},
directives: {
SafeHtml,
+ GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
@@ -245,7 +246,7 @@ export default {
immediate: true,
handler(newVal) {
if (!newVal) {
- this.showWriteTab();
+ this.hidePreview();
}
},
},
@@ -277,7 +278,7 @@ export default {
}
},
methods: {
- showPreviewTab() {
+ showPreview() {
if (this.previewMarkdown) return;
this.previewMarkdown = true;
@@ -297,7 +298,7 @@ export default {
this.renderMarkdown();
}
},
- showWriteTab() {
+ hidePreview() {
this.markdownPreview = '';
this.previewMarkdown = false;
},
@@ -365,9 +366,11 @@ export default {
:drawio-enabled="drawioEnabled"
data-testid="markdownHeader"
:restricted-tool-bar-items="restrictedToolBarItems"
- @preview-markdown="showPreviewTab"
- @write-markdown="showWriteTab"
+ :show-content-editor-switcher="showContentEditorSwitcher"
+ @showPreview="showPreview"
+ @hidePreview="hidePreview"
@handleSuggestDismissed="() => $emit('handleSuggestDismissed')"
+ @enableContentEditor="$emit('enableContentEditor')"
/>
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
@@ -384,8 +387,6 @@ export default {
:quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
:show-comment-tool-bar="showCommentToolBar"
- :show-content-editor-switcher="showContentEditorSwitcher"
- @enableContentEditor="$emit('enableContentEditor')"
/>
</div>
</div>
@@ -393,7 +394,7 @@ export default {
<div
v-show="previewMarkdown"
ref="markdown-preview"
- class="js-vue-md-preview md-preview-holder"
+ class="js-vue-md-preview md-preview-holder gl-px-5"
>
<suggestions
v-if="hasSuggestion"
@@ -410,13 +411,13 @@ export default {
v-show="previewMarkdown"
ref="markdown-preview"
v-safe-html:[$options.safeHtmlConfig]="markdownPreview"
- class="js-vue-md-preview md md-preview-holder"
+ class="js-vue-md-preview md md-preview-holder gl-px-5"
></div>
</template>
<div
v-if="referencedCommands && previewMarkdown && !markdownPreviewLoading"
v-safe-html:[$options.safeHtmlConfig]="referencedCommands"
- class="referenced-commands gl-mx-n5"
+ class="referenced-commands gl-mx-2 gl-mb-2 gl-px-4 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
data-testid="referenced-commands"
></div>
<div v-if="shouldShowReferencedUsers" class="referenced-users">
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 53a3913ebda..17d9a2daf0b 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,5 +1,5 @@
<script>
-import { GlPopover, GlButton, GlTooltipDirective, GlTabs, GlTab } from '@gitlab/ui';
+import { GlPopover, GlButton, GlTooltipDirective } from '@gitlab/ui';
import $ from 'jquery';
import {
keysFor,
@@ -19,17 +19,17 @@ import { updateText } from '~/lib/utils/text_markdown';
import ToolbarButton from './toolbar_button.vue';
import DrawioToolbarButton from './drawio_toolbar_button.vue';
import CommentTemplatesDropdown from './comment_templates_dropdown.vue';
+import EditorModeSwitcher from './editor_mode_switcher.vue';
export default {
components: {
ToolbarButton,
GlPopover,
GlButton,
- GlTabs,
- GlTab,
DrawioToolbarButton,
CommentTemplatesDropdown,
AiActionsDropdown: () => import('ee_component/ai/components/ai_actions_dropdown.vue'),
+ EditorModeSwitcher,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -91,6 +91,11 @@ export default {
required: false,
default: false,
},
+ showContentEditorSwitcher: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -121,6 +126,9 @@ export default {
const expandText = s__('MarkdownEditor|Click to expand');
return [`<details><summary>${expandText}</summary>`, `{text}`, '</details>'].join('\n');
},
+ showEditorModeSwitcher() {
+ return this.showContentEditorSwitcher && !this.previewMarkdown;
+ },
},
watch: {
showSuggestPopover() {
@@ -128,14 +136,14 @@ export default {
},
},
mounted() {
- $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
- $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
+ $(document).on('markdown-preview:show.vue', this.showMarkdownPreview);
+ $(document).on('markdown-preview:hide.vue', this.hideMarkdownPreview);
this.updateSuggestPopoverVisibility();
},
beforeDestroy() {
- $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
- $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
+ $(document).off('markdown-preview:show.vue', this.showMarkdownPreview);
+ $(document).off('markdown-preview:hide.vue', this.hideMarkdownPreview);
},
methods: {
async updateSuggestPopoverVisibility() {
@@ -149,19 +157,15 @@ export default {
(form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0])
);
},
-
- previewMarkdownTab(event, form) {
- if (event.target.blur) event.target.blur();
+ showMarkdownPreview(_, form) {
if (!this.isValid(form)) return;
- this.$emit('preview-markdown');
+ this.$emit('showPreview');
},
-
- writeMarkdownTab(event, form) {
- if (event.target.blur) event.target.blur();
+ hideMarkdownPreview(_, form) {
if (!this.isValid(form)) return;
- this.$emit('write-markdown');
+ this.$emit('hidePreview');
},
handleSuggestDismissed() {
this.$emit('handleSuggestDismissed');
@@ -204,6 +208,16 @@ export default {
});
}
},
+ handleEditorModeChanged() {
+ this.$emit('enableContentEditor');
+ },
+ switchPreview() {
+ if (this.previewMarkdown) {
+ this.hideMarkdownPreview();
+ } else {
+ this.showMarkdownPreview();
+ }
+ },
},
shortcuts: {
bold: keysFor(BOLD_TEXT),
@@ -214,225 +228,240 @@ export default {
outdent: keysFor(OUTDENT_LINE),
},
i18n: {
- writeTabTitle: __('Write'),
- previewTabTitle: __('Preview'),
+ preview: __('Preview'),
+ hidePreview: __('Continue editing'),
},
};
</script>
<template>
- <div class="md-header">
- <gl-tabs content-class="gl-display-none">
- <gl-tab
- title-link-class="gl-py-4 gl-px-3 js-md-write-button"
- :title="$options.i18n.writeTabTitle"
- :active="!previewMarkdown"
- data-testid="write-tab"
- @click="writeMarkdownTab($event)"
- />
- <gl-tab
- v-if="enablePreview"
- title-link-class="gl-py-4 gl-px-3 js-md-preview-button"
- :title="$options.i18n.previewTabTitle"
- :active="previewMarkdown"
- data-testid="preview-tab"
- @click="previewMarkdownTab($event)"
- />
-
- <template #tabs-end>
- <div
- data-testid="md-header-toolbar"
- :class="{ 'gl-display-none!': previewMarkdown }"
- class="md-header-toolbar gl-ml-auto gl-py-2 gl-justify-content-center"
- >
- <template v-if="canSuggest">
- <toolbar-button
- ref="suggestButton"
- :tag="mdSuggestion"
- :prepend="true"
- :button-title="__('Insert suggestion')"
- :cursor-offset="4"
- :tag-content="lineContent"
- icon="doc-code"
- data-qa-selector="suggestion_button"
- class="js-suggestion-btn"
- @click="handleSuggestDismissed"
- />
- <gl-popover
- v-if="suggestPopoverVisible"
- :target="$refs.suggestButton.$el"
- :css-classes="['diff-suggest-popover']"
- placement="bottom"
- :show="suggestPopoverVisible"
- >
- <strong>{{ __('New! Suggest changes directly') }}</strong>
- <p class="mb-2">
- {{
- __(
- 'Suggest code changes which can be immediately applied in one click. Try it out!',
- )
- }}
- </p>
- <gl-button
- variant="confirm"
- category="primary"
- size="small"
- data-qa-selector="dismiss_suggestion_popover_button"
- @click="handleSuggestDismissed"
- >
- {{ __('Got it') }}
- </gl-button>
- </gl-popover>
- </template>
- <ai-actions-dropdown
- v-if="editorAiActions.length"
- :actions="editorAiActions"
- @input="insertIntoTextarea"
- />
- <toolbar-button
- tag="**"
- :button-title="
- /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
- sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
- modifierKey,
- }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
- "
- :shortcuts="$options.shortcuts.bold"
- icon="bold"
- />
- <toolbar-button
- tag="_"
- :button-title="
- /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
- sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
- modifierKey,
- }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
- "
- :shortcuts="$options.shortcuts.italic"
- icon="italic"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('strikethrough')"
- tag="~~"
- :button-title="
- /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
- sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
- modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
- })
- "
- :shortcuts="$options.shortcuts.strikethrough"
- icon="strikethrough"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('quote')"
- :prepend="true"
- :tag="tag"
- :button-title="__('Insert a quote')"
- icon="quote"
- @click="handleQuote"
- />
- <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
- <toolbar-button
- tag="[{text}](url)"
- tag-select="url"
- :button-title="
- /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
- sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), {
- modifierKey,
- }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
- "
- :shortcuts="$options.shortcuts.link"
- icon="link"
- />
+ <div class="md-header gl-bg-gray-50 gl-px-2 gl-rounded-base gl-mx-2 gl-mt-2">
+ <div
+ class="gl-display-flex gl-align-items-center gl-flex-wrap"
+ :class="{
+ 'gl-justify-content-end': previewMarkdown,
+ 'gl-justify-content-space-between': !previewMarkdown,
+ }"
+ >
+ <div
+ data-testid="md-header-toolbar"
+ class="md-header-toolbar gl-display-flex gl-py-2 gl-flex-wrap"
+ :class="{ 'gl-display-none!': previewMarkdown }"
+ >
+ <template v-if="canSuggest">
<toolbar-button
- v-if="!restrictedToolBarItems.includes('bullet-list')"
+ ref="suggestButton"
+ :tag="mdSuggestion"
:prepend="true"
- tag="- "
- :button-title="__('Add a bullet list')"
- icon="list-bulleted"
+ :button-title="__('Insert suggestion')"
+ :cursor-offset="4"
+ :tag-content="lineContent"
+ icon="doc-code"
+ data-qa-selector="suggestion_button"
+ class="js-suggestion-btn"
+ @click="handleSuggestDismissed"
/>
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('numbered-list')"
- :prepend="true"
- tag="1. "
- :button-title="__('Add a numbered list')"
- icon="list-numbered"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('task-list')"
- :prepend="true"
- tag="- [ ] "
- :button-title="__('Add a checklist')"
- icon="list-task"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('indent')"
- class="gl-display-none"
- :button-title="
- /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
- sprintf(s__('MarkdownEditor|Indent line (%{modifierKey}])'), {
- modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
- })
- "
- :shortcuts="$options.shortcuts.indent"
- command="indentLines"
- icon="list-indent"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('outdent')"
- class="gl-display-none"
- :button-title="
- /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
- sprintf(s__('MarkdownEditor|Outdent line (%{modifierKey}[)'), {
- modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
- })
- "
- :shortcuts="$options.shortcuts.outdent"
- command="outdentLines"
- icon="list-outdent"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('collapsible-section')"
- :tag="mdCollapsibleSection"
- :prepend="true"
- tag-select="Click to expand"
- :button-title="__('Add a collapsible section')"
- icon="details-block"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('table')"
- :tag="mdTable"
- :prepend="true"
- :button-title="__('Add a table')"
- icon="table"
- />
- <gl-button
- v-if="!restrictedToolBarItems.includes('attach-file')"
- v-gl-tooltip
- :title="__('Attach a file or image')"
- data-testid="button-attach-file"
- category="tertiary"
- icon="paperclip"
- @click="handleAttachFile"
- />
- <drawio-toolbar-button
- v-if="drawioEnabled"
- :uploads-path="uploadsPath"
- :markdown-preview-path="markdownPreviewPath"
- />
- <comment-templates-dropdown
- v-if="newCommentTemplatePath && glFeatures.savedReplies"
- :new-comment-template-path="newCommentTemplatePath"
- />
- <toolbar-button
- v-if="!restrictedToolBarItems.includes('full-screen')"
- class="js-zen-enter"
- :prepend="true"
- :button-title="__('Go full screen')"
- icon="maximize"
- />
- </div>
- </template>
- </gl-tabs>
+ <gl-popover
+ v-if="suggestPopoverVisible"
+ :target="$refs.suggestButton.$el"
+ :css-classes="['diff-suggest-popover']"
+ placement="bottom"
+ :show="suggestPopoverVisible"
+ >
+ <strong>{{ __('New! Suggest changes directly') }}</strong>
+ <p class="mb-2">
+ {{
+ __(
+ 'Suggest code changes which can be immediately applied in one click. Try it out!',
+ )
+ }}
+ </p>
+ <gl-button
+ variant="confirm"
+ category="primary"
+ size="small"
+ data-qa-selector="dismiss_suggestion_popover_button"
+ @click="handleSuggestDismissed"
+ >
+ {{ __('Got it') }}
+ </gl-button>
+ </gl-popover>
+ </template>
+ <ai-actions-dropdown
+ v-if="editorAiActions.length"
+ :actions="editorAiActions"
+ @input="insertIntoTextarea"
+ />
+ <toolbar-button
+ tag="**"
+ :button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
+ modifierKey,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
+ "
+ :shortcuts="$options.shortcuts.bold"
+ icon="bold"
+ />
+ <toolbar-button
+ tag="_"
+ :button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
+ modifierKey,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
+ "
+ :shortcuts="$options.shortcuts.italic"
+ icon="italic"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('strikethrough')"
+ tag="~~"
+ :button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
+ modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
+ })
+ "
+ :shortcuts="$options.shortcuts.strikethrough"
+ icon="strikethrough"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('quote')"
+ :prepend="true"
+ :tag="tag"
+ :button-title="__('Insert a quote')"
+ icon="quote"
+ @click="handleQuote"
+ />
+ <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
+ <toolbar-button
+ tag="[{text}](url)"
+ tag-select="url"
+ :button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), {
+ modifierKey,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
+ "
+ :shortcuts="$options.shortcuts.link"
+ icon="link"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('bullet-list')"
+ :prepend="true"
+ tag="- "
+ :button-title="__('Add a bullet list')"
+ icon="list-bulleted"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('numbered-list')"
+ :prepend="true"
+ tag="1. "
+ :button-title="__('Add a numbered list')"
+ icon="list-numbered"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('task-list')"
+ :prepend="true"
+ tag="- [ ] "
+ :button-title="__('Add a checklist')"
+ icon="list-task"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('indent')"
+ class="gl-display-none"
+ :button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Indent line (%{modifierKey}])'), {
+ modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
+ })
+ "
+ :shortcuts="$options.shortcuts.indent"
+ command="indentLines"
+ icon="list-indent"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('outdent')"
+ class="gl-display-none"
+ :button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Outdent line (%{modifierKey}[)'), {
+ modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
+ })
+ "
+ :shortcuts="$options.shortcuts.outdent"
+ command="outdentLines"
+ icon="list-outdent"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('collapsible-section')"
+ :tag="mdCollapsibleSection"
+ :prepend="true"
+ tag-select="Click to expand"
+ :button-title="__('Add a collapsible section')"
+ icon="details-block"
+ />
+ <toolbar-button
+ v-if="!restrictedToolBarItems.includes('table')"
+ :tag="mdTable"
+ :prepend="true"
+ :button-title="__('Add a table')"
+ icon="table"
+ />
+ <gl-button
+ v-if="!restrictedToolBarItems.includes('attach-file')"
+ v-gl-tooltip
+ :title="__('Attach a file or image')"
+ class="gl-mr-2"
+ data-testid="button-attach-file"
+ category="tertiary"
+ icon="paperclip"
+ size="small"
+ @click="handleAttachFile"
+ />
+ <drawio-toolbar-button
+ v-if="drawioEnabled"
+ :uploads-path="uploadsPath"
+ :markdown-preview-path="markdownPreviewPath"
+ />
+ <comment-templates-dropdown
+ v-if="newCommentTemplatePath && glFeatures.savedReplies"
+ :new-comment-template-path="newCommentTemplatePath"
+ />
+ </div>
+ <div class="switch-preview gl-py-2 gl-display-flex gl-align-items-center gl-ml-auto">
+ <editor-mode-switcher
+ v-if="showEditorModeSwitcher"
+ size="small"
+ class="gl-mr-2"
+ value="markdown"
+ @input="handleEditorModeChanged"
+ />
+ <gl-button
+ v-if="enablePreview"
+ data-testid="preview-toggle"
+ value="preview"
+ :label="$options.i18n.previewTabTitle"
+ class="js-md-preview-button gl-flex-direction-row-reverse gl-align-items-center gl-font-weight-normal!"
+ size="small"
+ category="tertiary"
+ @click="switchPreview"
+ >{{ previewMarkdown ? $options.i18n.hidePreview : $options.i18n.preview }}</gl-button
+ >
+ <gl-button
+ v-if="!restrictedToolBarItems.includes('full-screen')"
+ v-gl-tooltip
+ :class="{ 'gl-display-none!': previewMarkdown }"
+ class="js-zen-enter gl-ml-2"
+ category="tertiary"
+ icon="maximize"
+ size="small"
+ :title="__('Go full screen')"
+ :prepend="true"
+ :button-title="__('Go full screen')"
+ />
+ </div>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index e8be242f660..4733afb7504 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton, GlLink, GlLoadingIcon, GlSprintf, GlIcon } from '@gitlab/ui';
-import EditorModeDropdown from './editor_mode_dropdown.vue';
export default {
components: {
@@ -9,7 +8,6 @@ export default {
GlLoadingIcon,
GlSprintf,
GlIcon,
- EditorModeDropdown,
},
props: {
markdownDocsPath: {
@@ -31,30 +29,21 @@ export default {
required: false,
default: true,
},
- showContentEditorSwitcher: {
- type: Boolean,
- required: false,
- default: false,
- },
},
computed: {
hasQuickActionsDocsPath() {
return this.quickActionsDocsPath !== '';
},
},
- methods: {
- handleEditorModeChanged(mode) {
- if (mode === 'richText') {
- this.$emit('enableContentEditor');
- }
- },
- },
};
</script>
<template>
- <div v-if="showCommentToolBar" class="comment-toolbar clearfix">
- <div class="toolbar-text">
+ <div
+ v-if="showCommentToolBar"
+ class="comment-toolbar gl-mx-2 gl-mb-2 gl-px-4 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base clearfix"
+ >
+ <div class="toolbar-text gl-font-sm">
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
<gl-sprintf
:message="
@@ -62,7 +51,9 @@ export default {
"
>
<template #markdownDocsLink="{ content }">
- <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" class="gl-font-sm">{{
+ content
+ }}</gl-link>
</template>
</gl-sprintf>
</template>
@@ -75,18 +66,22 @@ export default {
"
>
<template #markdownDocsLink="{ content }">
- <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" class="gl-font-sm">{{
+ content
+ }}</gl-link>
</template>
<template #keyboard="{ content }">
<kbd>{{ content }}</kbd>
</template>
<template #quickActionsDocsLink="{ content }">
- <gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="quickActionsDocsPath" target="_blank" class="gl-font-sm">{{
+ content
+ }}</gl-link>
</template>
</gl-sprintf>
</template>
</div>
- <span v-if="canAttachFile" class="uploading-container gl-line-height-32">
+ <span v-if="canAttachFile" class="uploading-container gl-font-sm gl-line-height-32">
<span class="uploading-progress-container hide">
<gl-icon name="paperclip" />
<span class="attaching-file-message"></span>
@@ -111,7 +106,7 @@ export default {
<gl-button
variant="link"
category="primary"
- class="retry-uploading-link gl-vertical-align-baseline"
+ class="retry-uploading-link gl-vertical-align-baseline gl-font-sm!"
>
{{ content }}
</gl-button>
@@ -120,7 +115,7 @@ export default {
<gl-button
variant="link"
category="primary"
- class="markdown-selector attach-new-file gl-vertical-align-baseline"
+ class="markdown-selector attach-new-file gl-vertical-align-baseline gl-font-sm!"
>
{{ content }}
</gl-button>
@@ -130,17 +125,10 @@ export default {
<gl-button
variant="link"
category="primary"
- class="button-cancel-uploading-files gl-vertical-align-baseline hide"
+ class="button-cancel-uploading-files gl-vertical-align-baseline hide gl-font-sm!"
>
{{ __('Cancel') }}
</gl-button>
</span>
- <editor-mode-dropdown
- v-if="showContentEditorSwitcher"
- size="small"
- class="gl-float-right gl-line-height-28 gl-display-block"
- value="markdown"
- @input="handleEditorModeChanged"
- />
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index 5ca21522d33..636c89c99d4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -92,7 +92,8 @@ export default {
:icon="icon"
type="button"
category="tertiary"
- class="js-md"
+ size="small"
+ class="js-md gl-mr-3"
data-container="body"
@click="$emit('click', $event)"
/>
diff --git a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue
index cb682e8b944..32ac5daf5de 100644
--- a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue
+++ b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue
@@ -145,7 +145,7 @@ export default {
return this.isNewDiscussion
? 'timeline-entry note-form'
: // eslint-disable-next-line @gitlab/require-i18n-strings
- 'note note-wrapper note-comment discussion-reply-holder gl-border-t-0! clearfix';
+ 'note note-wrapper note-comment discussion-reply-holder gl-border-t-0! clearfix gl-bg-white! gl-pt-0!';
},
},
watch: {
diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss
index 44429b439d9..a0cbf4fcd43 100644
--- a/app/assets/stylesheets/components/content_editor.scss
+++ b/app/assets/stylesheets/components/content_editor.scss
@@ -1,5 +1,6 @@
.ProseMirror {
- min-height: 128px;
+ padding-top: $gl-spacing-scale-4;
+ min-height: 140px;
max-height: 55vh;
overflow-y: auto;
@@ -116,14 +117,8 @@
}
}
-.content-editor-dropdown .dropdown-menu {
- width: auto !important;
-
- @include gl-min-w-0;
-
- button {
- @include gl-white-space-nowrap;
- }
+.content-editor-switcher {
+ min-height: 32px;
}
diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss
index 9ec87b4f304..54a4769f66d 100644
--- a/app/assets/stylesheets/framework/diffs.scss
+++ b/app/assets/stylesheets/framework/diffs.scss
@@ -946,7 +946,7 @@ table.code {
&.popover {
width: 250px;
min-width: 250px;
- z-index: 210;
+ z-index: 610;
}
.popover-header {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index e4025eb8b8d..503e22742ba 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -4,7 +4,6 @@
*/
.file-holder {
border: 1px solid $border-color;
- border-top: 0;
border-radius: $border-radius-default;
&.file-holder-top-border {
@@ -21,10 +20,6 @@
border: 0;
}
- &.file-holder-bottom-radius {
- border-radius: 0 0 $border-radius-small $border-radius-small;
- }
-
&.readme-holder {
margin: $gl-padding 0;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 48aacc9606e..e57dad9e4cb 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -44,46 +44,6 @@
}
}
-.md-header {
- .nav-links {
- a {
- width: 100%;
- padding-top: 0;
- line-height: 19px;
-
- &.btn.btn-sm {
- padding: 2px 5px;
- }
-
- &:focus {
- margin-top: -10px;
- padding-top: 10px;
- }
- }
- }
-
- .gl-tabs-nav {
- @include media-breakpoint-down(xs) {
- .nav-item {
- flex: 1;
- }
-
- .gl-tab-nav-item {
- padding-top: $gl-padding-4;
- padding-bottom: $gl-padding-8;
- }
-
- .md-header-toolbar {
- width: 100%;
- display: flex;
- flex-wrap: wrap;
- padding-top: $gl-padding-8;
- border-top: 1px solid $border-color;
- }
- }
- }
-}
-
.md-header-tab {
@include media-breakpoint-down(xs) {
flex: 1;
@@ -131,7 +91,7 @@
}
.md-preview-holder {
- min-height: 172px;
+ min-height: 176px;
padding: 10px 0;
overflow-x: auto;
}
diff --git a/app/assets/stylesheets/framework/source_editor.scss b/app/assets/stylesheets/framework/source_editor.scss
index f1ee4c94942..a09ab7ed64c 100644
--- a/app/assets/stylesheets/framework/source_editor.scss
+++ b/app/assets/stylesheets/framework/source_editor.scss
@@ -37,6 +37,7 @@
.gl-source-editor {
@include gl-order-n1;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
}
}
diff --git a/app/assets/stylesheets/page_bundles/editor.scss b/app/assets/stylesheets/page_bundles/editor.scss
index 36da979ba1f..9e9723d2e5a 100644
--- a/app/assets/stylesheets/page_bundles/editor.scss
+++ b/app/assets/stylesheets/page_bundles/editor.scss
@@ -88,6 +88,10 @@
}
}
}
+
+ .overflow-guard {
+ border-radius: 0 0 $border-radius-default $border-radius-default;
+ }
}
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 00ef659dcf4..d3ebc06a1dd 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -1027,7 +1027,7 @@ $tabs-holder-z-index: 250;
}
.md-preview-holder {
- max-height: 172px;
+ max-height: 182px;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index adeab227670..d029aa01e37 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -31,7 +31,7 @@
.note-textarea {
display: block;
- padding: 10px 1px;
+ padding: 10px 16px;
color: $gl-text-color;
font-family: $regular-font;
border: 0;
@@ -48,9 +48,8 @@
.common-note-form {
.md-area {
- padding: 0 $gl-padding;
border: 1px solid $border-color;
- border-radius: $border-radius-base;
+ border-radius: $border-radius-large;
transition: border-color ease-in-out 0.15s,
box-shadow ease-in-out 0.15s;
background-color: $white;
@@ -81,6 +80,10 @@
@include gl-focus;
}
+.md-header {
+ min-height: 32px;
+}
+
.md-header .nav-links {
display: flex;
flex-flow: row wrap;
@@ -92,6 +95,11 @@
}
}
+
+.md-header .gl-tabs-nav {
+ border-bottom: 0;
+}
+
.issuable-note-warning {
color: $orange-600;
background-color: $orange-50;
@@ -305,7 +313,6 @@ table {
.comment-toolbar {
color: $gl-text-color-secondary;
- border-top: 1px solid $border-color;
}
.toolbar-button {
@@ -394,6 +401,10 @@ table {
float: left;
margin-top: 5px;
}
+
+ button {
+ font-size: $gl-font-size-sm !important;
+ }
}
.uploading-error-icon,
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index bec6cccb977..91fce6d6820 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -192,7 +192,7 @@ module MarkupHelper
def markdown_toolbar_button(options = {})
data = options[:data].merge({ container: 'body' })
- css_classes = %w[gl-button btn btn-default-tertiary btn-icon js-md has-tooltip] << options[:css_class].to_s
+ css_classes = %w[gl-button btn btn-default-tertiary btn-icon btn-sm js-md has-tooltip] << options[:css_class].to_s
content_tag :button,
type: 'button',
class: css_classes.join(' '),
diff --git a/app/models/resource_events/issue_assignment_event.rb b/app/models/resource_events/issue_assignment_event.rb
index b24f181bc48..393e2aa8942 100644
--- a/app/models/resource_events/issue_assignment_event.rb
+++ b/app/models/resource_events/issue_assignment_event.rb
@@ -10,5 +10,9 @@ module ResourceEvents
validates :issue, presence: true
enum action: { add: 1, remove: 2 }
+
+ def self.issuable_id_column
+ :issue_id
+ end
end
end
diff --git a/app/models/resource_events/merge_request_assignment_event.rb b/app/models/resource_events/merge_request_assignment_event.rb
index 898594b7008..778b9101858 100644
--- a/app/models/resource_events/merge_request_assignment_event.rb
+++ b/app/models/resource_events/merge_request_assignment_event.rb
@@ -10,5 +10,9 @@ module ResourceEvents
validates :merge_request, presence: true
enum action: { add: 1, remove: 2 }
+
+ def self.issuable_id_column
+ :merge_request_id
+ end
end
end
diff --git a/app/services/google_cloud/generate_pipeline_service.rb b/app/services/google_cloud/generate_pipeline_service.rb
index b6438d6f501..791be69f4d4 100644
--- a/app/services/google_cloud/generate_pipeline_service.rb
+++ b/app/services/google_cloud/generate_pipeline_service.rb
@@ -61,7 +61,7 @@ module GoogleCloud
end
def pipeline_content(include_path)
- gitlab_ci_yml = Gitlab::Config::Loader::Yaml.new(default_branch_gitlab_ci_yml || '{}').load!
+ gitlab_ci_yml = ::Gitlab::Ci::Config::Yaml.load!(default_branch_gitlab_ci_yml || '{}')
append_remote_include(gitlab_ci_yml, "https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/library/-/raw/main/#{include_path}")
end
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 3942c427cc4..f65b2f4915c 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -6,4 +6,4 @@
- page_title runner_name
- add_to_breadcrumbs _('Runners'), admin_runners_path
-#js-admin-runner-show{ data: {runner_id: @runner.id, runners_path: admin_runners_path} }
+#js-admin-runner-show{ data: {runner_id: @runner.id, runners_path: admin_runners_path, empty_state_image: image_path('illustrations/pipelines_empty.svg')} }
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 9962e03995b..19943aa68a3 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,4 +1,4 @@
-.form-actions.gl-display-flex
+.gl-display-flex.gl-mt-7
- submit_button_options = { type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { qa_selector: 'commit_button' } } }
= render Pajamas::ButtonComponent.new(**submit_button_options) do
= _('Commit changes')
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index ff95e9a1088..621cd251bdf 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -2,8 +2,8 @@
- file_name = params[:id].split("/").last ||= ""
- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
-.file-holder-bottom-radius.file-holder.file.gl-mb-3
- .js-file-title.file-title.gl-display-flex.gl-align-items-center.clearfix{ data: { current_action: action } }
+.file-holder.file.gl-mb-3
+ .js-file-title.file-title.gl-display-flex.gl-align-items-center.gl-rounded-top-base{ data: { current_action: action } }
.editor-ref.block-truncated.has-tooltip{ title: ref }
= sprite_icon('branch', size: 12)
= ref
@@ -29,7 +29,10 @@
- unless Feature.enabled?(:source_editor_toolbar, current_user)
.file-buttons.gl-display-flex.gl-align-items-center.gl-justify-content-end
- if is_markdown
- = render 'shared/blob/markdown_buttons', show_fullscreen_button: false, supports_file_upload: false
+ .md-header.gl-display-flex.gl-px-2.gl-rounded-base.gl-mx-2.gl-mt-2
+ .gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-justify-content-space-between
+ .md-header-toolbar.gl-display-flex.gl-py-2.gl-flex-wrap{ class: "gl-m-0!" }
+ = render 'shared/blob/markdown_buttons', supports_file_upload: false
%span.soft-wrap-toggle
= render Pajamas::ButtonComponent.new(icon: 'soft-unwrap', button_options: { class: 'no-wrap' }) do
= _("No wrap")
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 59b2536c5d0..fd74ffef425 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -4,5 +4,4 @@
%h1.page-title.gl-font-size-h-display
= _("New Snippet")
-%hr
= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 904854c3fb7..2b55d35cf1f 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -1,19 +1,18 @@
-.form-group.row.commit_message-group
+.form-group.commit_message-group.gl-mt-5
- nonce = SecureRandom.hex
- descriptions = local_assigns.slice(:message_with_description, :message_without_description)
- = label_tag "commit_message-#{nonce}", class: 'col-form-label col-sm-2' do
+ = label_tag "commit_message-#{nonce}" do
#{ _('Commit message') }
- .col-sm-10
- .commit-message-container
- .max-width-marker
- = text_area_tag 'commit_message',
- (params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]),
- class: 'form-control gl-form-input js-commit-message',
- placeholder: local_assigns[:placeholder],
- data: descriptions,
- 'data-qa-selector': 'commit_message_field',
- required: true, rows: (local_assigns[:rows] || 3),
- id: "commit_message-#{nonce}"
+ .commit-message-container
+ .max-width-marker
+ = text_area_tag 'commit_message',
+ (params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]),
+ class: 'form-control gl-form-input js-commit-message',
+ placeholder: local_assigns[:placeholder],
+ data: descriptions,
+ 'data-qa-selector': 'commit_message_field',
+ required: true, rows: (local_assigns[:rows] || 3),
+ id: "commit_message-#{nonce}"
- if local_assigns[:hint]
%p.hint
= _('Try to keep the first line under 52 characters and the others under 72.')
diff --git a/app/views/shared/_md_preview.html.haml b/app/views/shared/_md_preview.html.haml
index 2fff70cdc74..dd3a31f5a59 100644
--- a/app/views/shared/_md_preview.html.haml
+++ b/app/views/shared/_md_preview.html.haml
@@ -8,21 +8,18 @@
= _('Only project members can comment.')
.md-area.position-relative
- .md-header
- = gl_tabs_nav({ class: 'clearfix nav-links'}) do
- %li.md-header-tab.active
- %button.js-md-write-button{ class: 'gl-py-3!' }
- = _("Write")
- %li.md-header-tab
- %button.js-md-preview-button{ class: 'gl-py-3!' }
- = _("Preview")
-
- %li.md-header-toolbar.active.gl-py-2
- = render 'shared/blob/markdown_buttons', show_fullscreen_button: true
+ .md-header.gl-bg-gray-50.gl-px-2.gl-rounded-base.gl-mx-2.gl-mt-2
+ .gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-justify-content-space-between
+ .md-header-toolbar.gl-display-flex.gl-py-2.gl-flex-wrap
+ = render 'shared/blob/markdown_buttons'
+ .switch-preview.gl-py-2.gl-display-flex.gl-align-items-center.gl-ml-auto
+ = render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, button_options: { class: 'js-md-preview-button', value: 'preview' }) do
+ = _('Preview')
+ = render Pajamas::ButtonComponent.new(icon: 'maximize', category: :tertiary, size: :small, button_options: { 'tabindex': -1, 'aria-label': _("Go full screen"), class: 'has-tooltip js-zen-enter gl-ml-2', data: { container: 'body' } })
.md-write-holder
= yield
- .md.md-preview-holder.js-md-preview.hide{ data: { url: url } }
+ .md.md-preview-holder.gl-px-5.js-md-preview.hide{ data: { url: url } }
.referenced-commands.hide
- if referenced_users
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 14ea96f9669..bdc7156242d 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -8,13 +8,12 @@
= hidden_field_tag 'branch_name', ref, class: 'js-branch-name'
- else
- if can?(current_user, :push_code, @project)
- .form-group.row.branch
- = label_tag 'branch_name', _('Target Branch'), class: 'col-form-label col-sm-2'
- .col-sm-10
- = text_field_tag 'branch_name', branch_name, required: true, class: "form-control gl-form-input js-branch-name ref-name"
+ .form-group.branch
+ = label_tag 'branch_name', _('Target Branch')
+ = text_field_tag 'branch_name', branch_name, required: true, class: "form-control gl-form-input js-branch-name ref-name"
- .js-create-merge-request-container
- = render 'shared/new_merge_request_checkbox'
+ .js-create-merge-request-container
+ = render 'shared/new_merge_request_checkbox'
- elsif project.can_current_user_push_to_branch?(branch_name)
= hidden_field_tag 'branch_name', branch_name
- else
diff --git a/app/views/shared/_zen.html.haml b/app/views/shared/_zen.html.haml
index 5a4efe7fe7f..05bee9e4d42 100644
--- a/app/views/shared/_zen.html.haml
+++ b/app/views/shared/_zen.html.haml
@@ -4,6 +4,7 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
- qa_selector = local_assigns.fetch(:qa_selector, '')
- autofocus = local_assigns.fetch(:autofocus, false)
+
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f
diff --git a/app/views/shared/blob/_markdown_buttons.html.haml b/app/views/shared/blob/_markdown_buttons.html.haml
index db53d78dadb..a3d3c1c8231 100644
--- a/app/views/shared/blob/_markdown_buttons.html.haml
+++ b/app/views/shared/blob/_markdown_buttons.html.haml
@@ -1,42 +1,44 @@
- modifier_key = client_js_flags[:isMac] ? '⌘' : s_('KeyboardKey|Ctrl+')
- supports_file_upload = local_assigns.fetch(:supports_file_upload, true)
-.md-header-toolbar.active
- = markdown_toolbar_button({ icon: "bold",
- data: { "md-tag" => "**", "md-shortcuts": '["mod+b"]' },
- title: sprintf(s_("MarkdownEditor|Add bold text (%{modifier_key}B)") % { modifier_key: modifier_key }) })
+= markdown_toolbar_button({ icon: "bold",
+ css_class: 'gl-mr-3',
+ data: { "md-tag" => "**", "md-shortcuts": '["mod+b"]' },
+ title: sprintf(s_("MarkdownEditor|Add bold text (%{modifier_key}B)") % { modifier_key: modifier_key }) })
- = markdown_toolbar_button({ icon: "italic",
- data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' },
- title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) })
+= markdown_toolbar_button({ icon: "italic",
+ css_class: 'gl-mr-3',
+ data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' },
+ title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) })
- = markdown_toolbar_button({ icon: "strikethrough",
- data: { "md-tag" => "~~", "md-shortcuts": '["mod+shift+x"]' },
- title: sprintf(s_("MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)") % { modifier_key: modifier_key }) })
+= markdown_toolbar_button({ icon: "strikethrough",
+ css_class: 'gl-mr-3',
+ data: { "md-tag" => "~~", "md-shortcuts": '["mod+shift+x"]' },
+ title: sprintf(s_("MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)") % { modifier_key: modifier_key }) })
- = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
- = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
+= markdown_toolbar_button({ icon: "quote", css_class: 'gl-mr-3', data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
+= markdown_toolbar_button({ icon: "code", css_class: 'gl-mr-3', data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
- = markdown_toolbar_button({ icon: "link",
- data: { "md-tag" => "[{text}](url)", "md-select" => "url", "md-shortcuts": '["mod+k"]' },
- title: sprintf(s_("MarkdownEditor|Add a link (%{modifier_key}K)") % { modifier_key: modifier_key }) })
+= markdown_toolbar_button({ icon: "link",
+ css_class: 'gl-mr-3',
+ data: { "md-tag" => "[{text}](url)", "md-select" => "url", "md-shortcuts": '["mod+k"]' },
+ title: sprintf(s_("MarkdownEditor|Add a link (%{modifier_key}K)") % { modifier_key: modifier_key }) })
- = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "- ", "md-prepend" => true }, title: _("Add a bullet list") })
- = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") })
- = markdown_toolbar_button({ icon: "list-task", data: { "md-tag" => "- [ ] ", "md-prepend" => true }, title: _("Add a checklist") })
- = markdown_toolbar_button({ icon: "list-indent",
- data: { "md-command" => 'indentLines', "md-shortcuts": '["mod+]"]' },
- css_class: 'gl-display-none',
- title: sprintf(s_("MarkdownEditor|Indent line (%{modifier_key}])") % { modifier_key: modifier_key }) })
- = markdown_toolbar_button({ icon: "list-outdent",
- data: { "md-command" => 'outdentLines', "md-shortcuts": '["mod+["]' },
- css_class: 'gl-display-none',
- title: sprintf(s_("MarkdownEditor|Outdent line (%{modifier_key}[)") % { modifier_key: modifier_key }) })
- = markdown_toolbar_button({ icon: "details-block",
- data: { "md-tag" => "<details><summary>Click to expand</summary>\n{text}\n</details>", "md-prepend" => true, "md-select" => "Click to expand" },
- title: _("Add a collapsible section") })
- = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| | |\n| | |", "md-prepend" => true }, title: _("Add a table") })
- - if supports_file_upload
- = render Pajamas::ButtonComponent.new(icon: 'paperclip', category: :tertiary, button_options: { 'aria-label': _("Attach a file or image"), class: 'has-tooltip js-attach-file-button', data: { testid: 'button-attach-file', container: 'body' } })
- - if show_fullscreen_button
- = render Pajamas::ButtonComponent.new(icon: 'maximize', category: :tertiary, button_options: { 'tabindex': -1, 'aria-label': _("Go full screen"), class: 'has-tooltip js-zen-enter', data: { container: 'body' } })
+= markdown_toolbar_button({ icon: "list-bulleted", css_class: 'gl-mr-3', data: { "md-tag" => "- ", "md-prepend" => true }, title: _("Add a bullet list") })
+= markdown_toolbar_button({ icon: "list-numbered", css_class: 'gl-mr-3', data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") })
+= markdown_toolbar_button({ icon: "list-task", css_class: 'gl-mr-3', data: { "md-tag" => "- [ ] ", "md-prepend" => true }, title: _("Add a checklist") })
+= markdown_toolbar_button({ icon: "list-indent",
+ css_class: 'gl-display-none gl-mr-3',
+ data: { "md-command" => 'indentLines', "md-shortcuts": '["mod+]"]' },
+ title: sprintf(s_("MarkdownEditor|Indent line (%{modifier_key}])") % { modifier_key: modifier_key }) })
+= markdown_toolbar_button({ icon: "list-outdent",
+ css_class: 'gl-display-none gl-mr-3',
+ data: { "md-command" => 'outdentLines', "md-shortcuts": '["mod+["]' },
+ title: sprintf(s_("MarkdownEditor|Outdent line (%{modifier_key}[)") % { modifier_key: modifier_key }) })
+= markdown_toolbar_button({ icon: "details-block",
+ css_class: 'gl-mr-3',
+ data: { "md-tag" => "<details><summary>Click to expand</summary>\n{text}\n</details>", "md-prepend" => true, "md-select" => "Click to expand" },
+ title: _("Add a collapsible section") })
+= markdown_toolbar_button({ icon: "table", css_class: 'gl-mr-3', data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| | |\n| | |", "md-prepend" => true }, title: _("Add a table") })
+- if supports_file_upload
+ = render Pajamas::ButtonComponent.new(icon: 'paperclip', category: :tertiary, size: :small, button_options: { 'aria-label': _("Attach a file or image"), class: 'has-tooltip js-attach-file-button gl-mr-3', data: { testid: 'button-attach-file', container: 'body' } })
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index fb000b9aab1..d7d6e477ab1 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -1,7 +1,7 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
- supports_file_upload = local_assigns.fetch(:supports_file_upload, true)
-.comment-toolbar.clearfix
- .toolbar-text
+.comment-toolbar.gl-mx-2.gl-mb-2.gl-px-4.gl-bg-gray-10.gl-rounded-bottom-left-base.gl-rounded-bottom-right-base.clearfix
+ .toolbar-text.gl-font-sm
- markdownLinkStart = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/markdown') }
- quickActionsLinkStart = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/quick_actions') }
- if supports_quick_actions
@@ -9,7 +9,7 @@
- else
= html_escape(s_('MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}')) % { markdownDocsLinkStart: markdownLinkStart, markdownDocsLinkEnd: '</a>'.html_safe }
- if supports_file_upload
- %span.uploading-container.gl-line-height-32
+ %span.uploading-container.gl-line-height-32.gl-font-sm
%span.uploading-progress-container.hide
= sprite_icon('paperclip', css_class: 'gl-icon gl-vertical-align-text-bottom')
%span.attaching-file-message
diff --git a/config/metrics/counts_all/20210216175520_ci_runners.yml b/config/metrics/counts_all/20210216175520_ci_runners.yml
index 68d8a3ee190..f701a312446 100644
--- a/config/metrics/counts_all/20210216175520_ci_runners.yml
+++ b/config/metrics/counts_all/20210216175520_ci_runners.yml
@@ -18,3 +18,4 @@ tier:
- ultimate
performance_indicator_type: []
milestone: "<13.9"
+instrumentation_class: CountCiRunnersMetric
diff --git a/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml b/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml
index fac597f7a9f..ef969f31496 100644
--- a/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml
+++ b/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml
@@ -12,6 +12,7 @@ milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58197
time_frame: all
data_source: database
+instrumentation_class: CountCiRunnersInstanceTypeActiveMetric
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml b/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml
index ddbeeaf9ce1..bc5f69f80b7 100644
--- a/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml
+++ b/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml
@@ -12,6 +12,7 @@ milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58197
time_frame: all
data_source: database
+instrumentation_class: CountCiRunnersGroupTypeActiveMetric
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml b/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml
index fe3a070d689..054e3f9e524 100644
--- a/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml
+++ b/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml
@@ -12,6 +12,7 @@ milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58197
time_frame: all
data_source: database
+instrumentation_class: CountCiRunnersProjectTypeActiveMetric
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml b/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml
index 82cf9083fe2..653a4a3917a 100644
--- a/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml
+++ b/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml
@@ -12,6 +12,7 @@ milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58197
time_frame: all
data_source: database
+instrumentation_class: CountCiRunnersInstanceTypeActiveOnlineMetric
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml b/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml
index 2f954dab596..53c2c2a8650 100644
--- a/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml
+++ b/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml
@@ -12,6 +12,7 @@ milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58197
time_frame: all
data_source: database
+instrumentation_class: CountCiRunnersGroupTypeActiveOnlineMetric
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml b/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml
index de771a4a264..556492214f1 100644
--- a/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml
+++ b/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml
@@ -12,6 +12,7 @@ milestone: "13.12"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58197
time_frame: all
data_source: database
+instrumentation_class: CountCiRunnersProjectTypeActiveOnlineMetric
distribution:
- ce
- ee
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 6edfd5add48..e257bddb900 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -1340,6 +1340,12 @@ Example:
expect(metrics.merged_at).to be_like_time(time)
```
+Example for `be_within`:
+
+```ruby
+expect(violation.reload.merged_at).to be_within(0.00001.seconds).of(merge_request.merged_at)
+```
+
#### `have_gitlab_http_status`
Prefer `have_gitlab_http_status` over `have_http_status` and
diff --git a/doc/development/value_stream_analytics.md b/doc/development/value_stream_analytics.md
index 43db2187cdd..afbd1a76a1b 100644
--- a/doc/development/value_stream_analytics.md
+++ b/doc/development/value_stream_analytics.md
@@ -160,6 +160,7 @@ graph LR;
IssueCreated --> IssueLastEdited;
IssueCreated --> IssueLabelAdded;
IssueCreated --> IssueLabelRemoved;
+ IssueCreated --> IssueFirstAssignedAt;
MergeRequestCreated --> MergeRequestMerged;
MergeRequestCreated --> MergeRequestClosed;
MergeRequestCreated --> MergeRequestFirstDeployedToProduction;
@@ -168,6 +169,13 @@ graph LR;
MergeRequestCreated --> MergeRequestLastEdited;
MergeRequestCreated --> MergeRequestLabelAdded;
MergeRequestCreated --> MergeRequestLabelRemoved;
+ MergeRequestCreated --> MergeRequestFirstAssignedAt;
+ MergeRequestFirstAssignedAt --> MergeRequestClosed;
+ MergeRequestFirstAssignedAt --> MergeRequestLastBuildStarted;
+ MergeRequestFirstAssignedAt --> MergeRequestLastEdited;
+ MergeRequestFirstAssignedAt --> MergeRequestMerged;
+ MergeRequestFirstAssignedAt --> MergeRequestLabelAdded;
+ MergeRequestFirstAssignedAt --> MergeRequestLabelRemoved;
MergeRequestLastBuildStarted --> MergeRequestLastBuildFinished;
MergeRequestLastBuildStarted --> MergeRequestClosed;
MergeRequestLastBuildStarted --> MergeRequestFirstDeployedToProduction;
@@ -184,19 +192,30 @@ graph LR;
IssueLabelAdded --> IssueLabelAdded;
IssueLabelAdded --> IssueLabelRemoved;
IssueLabelAdded --> IssueClosed;
+ IssueLabelAdded --> IssueFirstAssignedAt;
IssueLabelRemoved --> IssueClosed;
+ IssueLabelRemoved --> IssueFirstAssignedAt;
IssueFirstAddedToBoard --> IssueClosed;
IssueFirstAddedToBoard --> IssueFirstAssociatedWithMilestone;
IssueFirstAddedToBoard --> IssueFirstMentionedInCommit;
IssueFirstAddedToBoard --> IssueLastEdited;
IssueFirstAddedToBoard --> IssueLabelAdded;
IssueFirstAddedToBoard --> IssueLabelRemoved;
+ IssueFirstAddedToBoard --> IssueFirstAssignedAt;
+ IssueFirstAssignedAt --> IssueClosed;
+ IssueFirstAssignedAt --> IssueFirstAddedToBoard;
+ IssueFirstAssignedAt --> IssueFirstAssociatedWithMilestone;
+ IssueFirstAssignedAt --> IssueFirstMentionedInCommit;
+ IssueFirstAssignedAt --> IssueLastEdited;
+ IssueFirstAssignedAt --> IssueLabelAdded;
+ IssueFirstAssignedAt --> IssueLabelRemoved;
IssueFirstAssociatedWithMilestone --> IssueClosed;
IssueFirstAssociatedWithMilestone --> IssueFirstAddedToBoard;
IssueFirstAssociatedWithMilestone --> IssueFirstMentionedInCommit;
IssueFirstAssociatedWithMilestone --> IssueLastEdited;
IssueFirstAssociatedWithMilestone --> IssueLabelAdded;
IssueFirstAssociatedWithMilestone --> IssueLabelRemoved;
+ IssueFirstAssociatedWithMilestone --> IssueFirstAssignedAt;
IssueFirstMentionedInCommit --> IssueClosed;
IssueFirstMentionedInCommit --> IssueFirstAssociatedWithMilestone;
IssueFirstMentionedInCommit --> IssueFirstAddedToBoard;
@@ -222,8 +241,10 @@ graph LR;
MergeRequestLabelAdded --> MergeRequestLabelAdded;
MergeRequestLabelAdded --> MergeRequestLabelRemoved;
MergeRequestLabelAdded --> MergeRequestMerged;
+ MergeRequestLabelAdded --> MergeRequestFirstAssignedAt;
MergeRequestLabelRemoved --> MergeRequestLabelAdded;
MergeRequestLabelRemoved --> MergeRequestLabelRemoved;
+ MergeRequestLabelRemoved --> MergeRequestFirstAssignedAt;
```
## Default stages
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index e4a3f60834f..418c01cd851 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -103,6 +103,8 @@ The following table lists project permissions available for each role:
| [Issues](project/issues/index.md):<br>View [Design Management](project/issues/design_management.md) pages | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Issues](project/issues/index.md):<br>View [related issues](project/issues/related_issues.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Issues](project/issues/index.md):<br>Set [weight](project/issues/issue_weight.md) | ✓ (15) | ✓ | ✓ | ✓ | ✓ |
+| [Issues](project/issues/index.md):<br>Set metadata such as labels, milestones, or assignees when creating an issue | ✓ (15) | ✓ | ✓ | ✓ | ✓ |
+| [Issues](project/issues/index.md):<br>Edit metadata such labels, milestones, or assignees for an existing issue | (15) | ✓ | ✓ | ✓ | ✓ |
| [Issues](project/issues/index.md):<br>Set [parent epic](group/epics/manage_epics.md#add-an-existing-issue-to-an-epic) | | ✓ | ✓ | ✓ | ✓ |
| [Issues](project/issues/index.md):<br>View [confidential issues](project/issues/confidential_issues.md) | (2) | ✓ | ✓ | ✓ | ✓ |
| [Issues](project/issues/index.md):<br>Close / reopen (18) | | ✓ | ✓ | ✓ | ✓ |
diff --git a/lib/gitlab/config/loader/yaml.rb b/lib/gitlab/config/loader/yaml.rb
index 38f8eca3c3c..7138663811e 100644
--- a/lib/gitlab/config/loader/yaml.rb
+++ b/lib/gitlab/config/loader/yaml.rb
@@ -1,5 +1,9 @@
# frozen_string_literal: true
+# NOTE: DO NOT use this class for loading GitLab CI configuration files.
+# Instead, use `Gitlab::Ci::Config::Yaml.load!`, which will properly handle
+# CI configuration headers.
+
module Gitlab
module Config
module Loader
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric.rb
new file mode 100644
index 00000000000..fbf4e0f904b
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersGroupTypeActiveMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Runner.group_type.active }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric.rb
new file mode 100644
index 00000000000..acb6de53d14
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersGroupTypeActiveOnlineMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Runner.group_type.active.online }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric.rb
new file mode 100644
index 00000000000..d9a785679d7
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersInstanceTypeActiveMetric < DatabaseMetric
+ operation :count
+
+ relation do
+ ::Ci::Runner.instance_type.active
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric.rb
new file mode 100644
index 00000000000..05a9c47c016
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersInstanceTypeActiveOnlineMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Runner.instance_type.active.online }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric.rb
new file mode 100644
index 00000000000..8be4955e28d
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Runner }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric.rb
new file mode 100644
index 00000000000..e713e85b270
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersProjectTypeActiveMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Runner.project_type.active }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric.rb
new file mode 100644
index 00000000000..91e7c6063b8
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class CountCiRunnersProjectTypeActiveOnlineMetric < DatabaseMetric
+ operation :count
+
+ relation { ::Ci::Runner.project_type.active.online }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index ad2a8130cca..8403c933076 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -145,7 +145,6 @@ module Gitlab
merge_requests: count(MergeRequest),
notes: count(Note)
}.merge(
- runners_usage,
integrations_usage,
user_preferences_usage,
service_desk_counts
@@ -156,18 +155,6 @@ module Gitlab
end
# rubocop: enable Metrics/AbcSize
- def runners_usage
- {
- ci_runners: count(::Ci::Runner),
- ci_runners_instance_type_active: count(::Ci::Runner.instance_type.active),
- ci_runners_group_type_active: count(::Ci::Runner.group_type.active),
- ci_runners_project_type_active: count(::Ci::Runner.project_type.active),
- ci_runners_instance_type_active_online: count(::Ci::Runner.instance_type.active.online),
- ci_runners_group_type_active_online: count(::Ci::Runner.group_type.active.online),
- ci_runners_project_type_active_online: count(::Ci::Runner.project_type.active.online)
- }
- end
-
def system_usage_data_monthly
{
counts_monthly: {
diff --git a/lib/gitlab/usage_data_metrics.rb b/lib/gitlab/usage_data_metrics.rb
index 48f695d5db1..8948d621d3c 100644
--- a/lib/gitlab/usage_data_metrics.rb
+++ b/lib/gitlab/usage_data_metrics.rb
@@ -8,10 +8,6 @@ module Gitlab
build_payload(:with_value)
end
- def suggested_names
- build_payload(:with_suggested_name)
- end
-
private
def build_payload(method_symbol)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 04594cc02c3..4dd83d4e416 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13216,6 +13216,9 @@ msgstr ""
msgid "CycleAnalyticsEvent|Issue first added to a board"
msgstr ""
+msgid "CycleAnalyticsEvent|Issue first assigned"
+msgstr ""
+
msgid "CycleAnalyticsEvent|Issue first associated with a milestone"
msgstr ""
@@ -13240,6 +13243,9 @@ msgstr ""
msgid "CycleAnalyticsEvent|Merge request created"
msgstr ""
+msgid "CycleAnalyticsEvent|Merge request first assigned"
+msgstr ""
+
msgid "CycleAnalyticsEvent|Merge request first commit time"
msgstr ""
@@ -16245,12 +16251,6 @@ msgstr ""
msgid "Editing"
msgstr ""
-msgid "Editing markdown"
-msgstr ""
-
-msgid "Editing rich text"
-msgstr ""
-
msgid "Edits"
msgstr ""
@@ -27038,9 +27038,6 @@ msgstr ""
msgid "Mark to do as done"
msgstr ""
-msgid "Markdown"
-msgstr ""
-
msgid "Markdown Help"
msgstr ""
@@ -38327,9 +38324,6 @@ msgstr ""
msgid "Revoked personal access token %{personal_access_token_name}!"
msgstr ""
-msgid "Rich text"
-msgstr ""
-
msgid "RightSidebar|Copy email address"
msgstr ""
@@ -38730,6 +38724,9 @@ msgstr ""
msgid "Runners|Maintenance note"
msgstr ""
+msgid "Runners|Make sure the runner is online and available to run jobs (not paused). Jobs display here when the runner picks them up."
+msgstr ""
+
msgid "Runners|Manually verify that the runner is available to pick up jobs."
msgstr ""
@@ -39106,6 +39103,9 @@ msgstr ""
msgid "Runners|This registration process is only supported in GitLab Runner 15.10 or later"
msgstr ""
+msgid "Runners|This runner has not run any jobs"
+msgstr ""
+
msgid "Runners|This runner has not run any jobs."
msgstr ""
@@ -42243,19 +42243,19 @@ msgstr ""
msgid "Snippets|Delete file"
msgstr ""
-msgid "Snippets|Description (optional)"
+msgid "Snippets|Describe what your snippet does or how to use it…"
msgstr ""
-msgid "Snippets|Error with Akismet. Please check the logs for more info."
+msgid "Snippets|Description (optional)"
msgstr ""
-msgid "Snippets|Files"
+msgid "Snippets|Error with Akismet. Please check the logs for more info."
msgstr ""
-msgid "Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+msgid "Snippets|File name (e.g. test.rb)"
msgstr ""
-msgid "Snippets|Optionally add a description about what your snippet does or how to use it…"
+msgid "Snippets|Files"
msgstr ""
msgid "Snippets|Snippets can't contain empty files. Ensure all files have content, or delete them."
@@ -43865,6 +43865,12 @@ msgstr ""
msgid "Switch to GitLab Next"
msgstr ""
+msgid "Switch to Markdown"
+msgstr ""
+
+msgid "Switch to rich text"
+msgstr ""
+
msgid "Switch to the source to copy the file contents"
msgstr ""
@@ -49084,9 +49090,6 @@ msgstr ""
msgid "View all projects"
msgstr ""
-msgid "View and edit markdown, with the option to preview the formatted output."
-msgstr ""
-
msgid "View blame"
msgstr ""
@@ -49216,9 +49219,6 @@ msgstr ""
msgid "View the documentation"
msgstr ""
-msgid "View the formatted output in real-time as you edit."
-msgstr ""
-
msgid "View the latest successful deployment to this environment"
msgstr ""
diff --git a/package.json b/package.json
index ec986934347..c5bc3767779 100644
--- a/package.json
+++ b/package.json
@@ -122,7 +122,7 @@
"clipboard": "^2.0.8",
"compression-webpack-plugin": "^5.0.2",
"copy-webpack-plugin": "^6.4.1",
- "core-js": "^3.29.1",
+ "core-js": "^3.30.1",
"cron-validator": "^1.1.1",
"cronstrue": "^1.122.0",
"cropper": "^2.3.0",
@@ -207,7 +207,7 @@
"vue-loader": "15.10.1",
"vue-observe-visibility": "^1.0.0",
"vue-resize": "^1.0.1",
- "vue-router": "3.4.9",
+ "vue-router": "3.6.5",
"vue-router-vue3": "npm:vue-router@4.1.6",
"vue-template-compiler": "2.7.14",
"vue-virtual-scroll-list": "^1.4.7",
diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb
index b1d83a6e2d0..05d59acd8e8 100644
--- a/qa/qa/page/component/snippet.rb
+++ b/qa/qa/page/component/snippet.rb
@@ -156,8 +156,14 @@ module QA
end
end
- def has_embed_dropdown?
- has_element?(:snippet_embed_dropdown)
+ RSpec::Matchers.define :have_embed_dropdown do
+ match do |page|
+ page.has_element?(:snippet_embed_dropdown)
+ end
+
+ match_when_negated do |page|
+ page.has_no_element?(:snippet_embed_dropdown)
+ end
end
def click_edit_button
diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb
index 9143a25d9ab..335790c5b27 100644
--- a/qa/qa/page/component/wiki_page_form.rb
+++ b/qa/qa/page/component/wiki_page_form.rb
@@ -19,7 +19,7 @@ module QA
element :markdown_editor_form_field
end
- base.view 'app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue' do
+ base.view 'app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue' do
element :editing_mode_switcher
end
@@ -59,9 +59,6 @@ module QA
def use_new_editor
click_element(:editing_mode_switcher)
- within_element(:editing_mode_switcher) do
- find('button', text: 'Rich text').click
- end
wait_until(reload: false) do
has_element?(:content_editor_container)
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index 98c1f9baf12..82b7379b67c 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -115,7 +115,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
before do
visit project_issue_path(project, issue)
- click_button 'More actions'
+ find('.more-actions-toggle button').click
end
it_behaves_like 'reports the user with an abuse category'
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index a70a1e2e70b..376e1e6063f 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -25,17 +25,17 @@ RSpec.describe 'Group milestones', feature_category: :subgroups do
description.native.send_keys('')
- click_button('Preview')
+ click_button("Preview")
preview = find('.js-md-preview')
expect(preview).to have_content('Nothing to preview.')
- click_button('Write')
+ click_button("Continue editing")
description.native.send_keys(':+1: Nice')
- click_button('Preview')
+ click_button("Preview")
expect(preview).to have_css('gl-emoji')
expect(find('#milestone_description', visible: false)).not_to be_visible
diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb
index 52464c6be8b..887bc7d0c87 100644
--- a/spec/features/issuables/markdown_references/jira_spec.rb
+++ b/spec/features/issuables/markdown_references/jira_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe "Jira", :js, feature_category: :team_planning do
end
it "creates a link to the referenced issue on the preview" do
- find(".js-md-preview-button").click
+ click_button("Preview")
wait_for_requests
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 3b1716230cd..56c395091d9 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -61,22 +61,22 @@ RSpec.describe "User creates issue", feature_category: :team_planning do
textarea = first(".gfm-form textarea")
page.within(form) do
- click_link("Preview")
+ click_button("Preview")
preview = find(".js-vue-md-preview") # this element is findable only when the "Preview" link is clicked.
expect(preview).to have_content("Nothing to preview.")
- click_link("Write")
+ click_button("Continue editing")
fill_in("Description", with: "Bug fixed :smile:")
- click_link("Preview")
+ click_button("Preview")
expect(preview).to have_css("gl-emoji")
expect(textarea).not_to be_visible
- click_link("Write")
+ click_button("Continue editing")
fill_in("Description", with: "/confidential")
- click_link("Preview")
+ click_button("Preview")
expect(form).to have_content('Makes this issue confidential.')
end
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index 4ef58918a2b..c1cf8fada26 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -39,9 +39,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin
click_button("Preview")
end
- expect(form).to have_button("Write")
-
- click_button("Write")
+ click_button("Continue editing")
fill_in("Description", with: "/confidential")
click_button("Preview")
@@ -121,8 +119,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin
expect(issuable_form).to have_selector(markdown_field_focused_selector)
page.within issuable_form do
- click_on _('Editing markdown')
- click_on _('Rich text')
+ click_button("Switch to rich text")
end
expect(issuable_form).not_to have_selector(content_editor_focused_selector)
@@ -134,8 +131,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin
expect(issuable_form).to have_selector(content_editor_focused_selector)
page.within issuable_form do
- click_on _('Editing rich text')
- click_on _('Markdown')
+ click_button("Switch to Markdown")
end
expect(issuable_form).not_to have_selector(markdown_field_focused_selector)
diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb
index ddbcb04fa80..6e98812753a 100644
--- a/spec/features/merge_request/batch_comments_spec.rb
+++ b/spec/features/merge_request/batch_comments_spec.rb
@@ -52,6 +52,8 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
find('.js-note-delete').click
+ wait_for_requests
+
page.within('.modal') do
click_button('Delete comment', match: :first)
end
@@ -66,6 +68,8 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
find('.js-note-edit').click
+ wait_for_requests
+
# make sure comment form is in view
execute_script("window.scrollBy(0, 200)")
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 9ab53a00903..35e2fa2f89c 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -248,7 +248,7 @@ RSpec.describe 'User comments on a diff', :js, feature_category: :code_review_wo
page.within('.diff-file:nth-of-type(1) .discussion .note') do
find('.more-actions').click
- find('.more-actions .dropdown-menu li', match: :first)
+ find('.more-actions li', match: :first)
find('.js-note-delete').click
end
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index a74a8b1cd5a..f13c68a60ee 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js, feature_category: :
should_allow_commenting(find_by_scrolling('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
accept_gl_confirm(button_text: 'Delete comment') do
- first('button.more-actions-toggle').click
+ first('.more-actions-toggle button').click
first('.js-note-delete').click
end
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index f167ab8fe8a..03b01ef4b7a 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe 'Merge request > User posts notes', :js, feature_category: :code_
before do
page.within('.js-main-target-form') do
fill_in 'note[note]', with: 'This is awesome!'
- find('.js-md-preview-button').click
+ click_button("Preview")
click_button 'Comment'
end
end
@@ -138,7 +138,7 @@ RSpec.describe 'Merge request > User posts notes', :js, feature_category: :code_
it 'hides the toolbar buttons when previewing a note' do
wait_for_requests
- find('.js-md-preview-button').click
+ click_button("Preview")
page.within('.js-main-target-form') do
expect(page).not_to have_css('.md-header-toolbar')
end
diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb
index 6118f59df3c..1a9d40ae926 100644
--- a/spec/features/merge_request/user_views_open_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe 'User views an open merge request', feature_category: :code_revie
fill_in(:merge_request_description, with: '')
page.within('.js-vue-markdown-field') do
- click_link('Preview')
+ click_button("Preview")
expect(find('.js-vue-md-preview')).to have_content('Nothing to preview.')
end
@@ -69,12 +69,12 @@ RSpec.describe 'User views an open merge request', feature_category: :code_revie
fill_in(:merge_request_description, with: ':+1: Nice')
page.within('.js-vue-markdown-field') do
- click_link('Preview')
+ click_button("Preview")
expect(find('.js-vue-md-preview')).to have_css('gl-emoji')
end
- expect(find('.js-vue-markdown-field')).to have_css('.js-vue-md-preview').and have_link('Write')
+ expect(find('.js-vue-markdown-field')).to have_css('.js-md-preview-button')
expect(find('#merge_request_description', visible: false)).not_to be_visible
end
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 2b6b09ccc10..6e335871ed1 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -142,7 +142,7 @@ RSpec.describe 'Editing file blob', :js, feature_category: :projects do
it 'renders content with CommonMark' do
visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
fill_editor(content: '1. one\\n - sublist\\n')
- click_link 'Preview'
+ click_on "Preview"
wait_for_requests
# the above generates two separate lists (not embedded) in CommonMark
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 91b838116e9..b0cb57f158d 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe "User adds a comment on a commit", :js, feature_category: :source
expect(page).not_to have_css(".js-note-text")
# Check on the `Write` tab
- click_button("Write")
+ click_button("Continue editing")
expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji}")
@@ -107,7 +107,7 @@ RSpec.describe "User adds a comment on a commit", :js, feature_category: :source
# Test UI elements, then submit.
page.within("form[data-line-code='#{sample_commit.line_code}']") do
expect(find(".js-note-text", visible: false).text).to eq("")
- expect(page).to have_css('.js-md-write-button')
+ expect(page).to have_css('.js-md-preview')
click_button("Comment")
end
diff --git a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
index e23eb1cada8..e265756f930 100644
--- a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
+++ b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe "User deletes comments on a commit", :js, feature_category: :sour
note.hover
find(".more-actions").click
- find(".more-actions .dropdown-menu li", match: :first)
+ find(".more-actions li", match: :first)
find(".js-note-delete").click
end
diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb
index c4019b4d123..709914434e7 100644
--- a/spec/features/projects/commit/user_comments_on_commit_spec.rb
+++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe "User comments on commit", :js, feature_category: :source_code_ma
expect(page).not_to have_css(".js-note-text")
# Check on `Write` tab
- click_button("Write")
+ click_button("Continue editing")
expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji_code}")
@@ -109,7 +109,7 @@ RSpec.describe "User comments on commit", :js, feature_category: :source_code_ma
note.hover
find(".more-actions").click
- find(".more-actions .dropdown-menu li", match: :first)
+ find(".more-actions li", match: :first)
find(".js-note-delete").click
end
diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb
index c282067f3ad..ffc319c8453 100644
--- a/spec/features/projects/releases/user_creates_release_spec.rb
+++ b/spec/features/projects/releases/user_creates_release_spec.rb
@@ -108,7 +108,7 @@ RSpec.describe 'User creates release', :js, feature_category: :continuous_delive
fill_release_notes('**some** _markdown_ [content](https://example.com)')
- click_on 'Preview'
+ click_button("Preview")
wait_for_all_requests
end
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 5aac27a71e4..5c1ee729346 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -81,6 +81,7 @@ RSpec.describe 'Comments on personal snippets', :js, feature_category: :source_c
it 'previews a note' do
fill_in 'note[note]', with: 'This is **awesome**!'
+
find('.js-md-preview-button').click
page.within('.new-note .md-preview-holder') do
diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js
index 4ceed885e6e..7306e6eef17 100644
--- a/spec/frontend/api/projects_api_spec.js
+++ b/spec/frontend/api/projects_api_spec.js
@@ -146,4 +146,29 @@ describe('~/api/projects_api.js', () => {
});
});
});
+
+ describe('getProjectShareLocations', () => {
+ it('requests share locations for a project', async () => {
+ const expectedUrl = `/api/v7/projects/1/share_locations`;
+ const params = { search: 'foo' };
+
+ const response = [
+ {
+ id: 27,
+ web_url: 'http://127.0.0.1:3000/groups/Commit451',
+ name: 'Commit451',
+ avatar_url: null,
+ full_name: 'Commit451',
+ full_path: 'Commit451',
+ },
+ ];
+
+ mock.onGet(expectedUrl).replyOnce(HTTP_STATUS_OK, response);
+
+ await expect(projectsApi.getProjectShareLocations(projectId, params)).resolves.toMatchObject({
+ data: response,
+ });
+ expect(mock.history.get[0].params).toEqual({ ...params, per_page: DEFAULT_PER_PAGE });
+ });
+ });
});
diff --git a/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap
index a5690844053..1733c4d4bb4 100644
--- a/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap
+++ b/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap
@@ -10,7 +10,7 @@ exports[`Blob Header Editing rendering matches the snapshot 1`] = `
<gl-form-input-stub
class="form-control js-snippet-file-name"
name="snippet_file_name"
- placeholder="Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+ placeholder="File name (e.g. test.rb)"
type="text"
value="foo.md"
/>
diff --git a/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js b/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js
new file mode 100644
index 00000000000..e64308f49d1
--- /dev/null
+++ b/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js
@@ -0,0 +1,37 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState } from '@gitlab/ui';
+import RunnerJobsEmptyState from '~/ci/runner/components/runner_jobs_empty_state.vue';
+
+const DEFAULT_PROPS = {
+ emptyTitle: 'This runner has not run any jobs',
+ emptyDescription:
+ 'Make sure the runner is online and available to run jobs (not paused). Jobs display here when the runner picks them up.',
+};
+
+describe('RunnerJobsEmptyStateComponent', () => {
+ let wrapper;
+
+ const mountComponent = () => {
+ wrapper = shallowMount(RunnerJobsEmptyState, {
+ provide: {
+ emptyStateImage: 'emptyStateImage',
+ },
+ });
+ };
+
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ describe('empty', () => {
+ it('should show an empty state if it is empty', () => {
+ const emptyState = findEmptyState();
+
+ expect(emptyState.props('svgPath')).toBe('emptyStateImage');
+ expect(emptyState.props('title')).toBe(DEFAULT_PROPS.emptyTitle);
+ expect(emptyState.text()).toContain(DEFAULT_PROPS.emptyDescription);
+ });
+ });
+});
diff --git a/spec/frontend/ci/runner/components/runner_jobs_spec.js b/spec/frontend/ci/runner/components/runner_jobs_spec.js
index 367b9ce395d..179b37cfa21 100644
--- a/spec/frontend/ci/runner/components/runner_jobs_spec.js
+++ b/spec/frontend/ci/runner/components/runner_jobs_spec.js
@@ -8,8 +8,9 @@ import { createAlert } from '~/alert';
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
import RunnerJobsTable from '~/ci/runner/components/runner_jobs_table.vue';
import RunnerPagination from '~/ci/runner/components/runner_pagination.vue';
+import RunnerJobsEmptyState from '~/ci/runner/components/runner_jobs_empty_state.vue';
import { captureException } from '~/ci/runner/sentry_utils';
-import { I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/ci/runner/constants';
+import { RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/ci/runner/constants';
import runnerJobsQuery from '~/ci/runner/graphql/show/runner_jobs.query.graphql';
@@ -31,7 +32,7 @@ describe('RunnerJobs', () => {
const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader);
const findRunnerJobsTable = () => wrapper.findComponent(RunnerJobsTable);
const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
-
+ const findEmptyState = () => wrapper.findComponent(RunnerJobsEmptyState);
const createComponent = ({ mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RunnerJobs, {
apolloProvider: createMockApollo([[runnerJobsQuery, mockRunnerJobsQuery]]),
@@ -127,8 +128,8 @@ describe('RunnerJobs', () => {
await waitForPromises();
});
- it('Shows a "None" label', () => {
- expect(wrapper.text()).toBe(I18N_NO_JOBS_FOUND);
+ it('should render empty state', () => {
+ expect(findEmptyState().exists()).toBe(true);
});
});
diff --git a/spec/frontend/ci/runner/mock_data.js b/spec/frontend/ci/runner/mock_data.js
index 196005c9882..d47a95f5c07 100644
--- a/spec/frontend/ci/runner/mock_data.js
+++ b/spec/frontend/ci/runner/mock_data.js
@@ -1,9 +1,9 @@
// Fixtures generated by: spec/frontend/fixtures/runner.rb
// List queries
+import allRunnersWithCreatorData from 'test_fixtures/graphql/ci/runner/list/all_runners.query.graphql.with_creator.json';
import allRunnersData from 'test_fixtures/graphql/ci/runner/list/all_runners.query.graphql.json';
import allRunnersDataPaginated from 'test_fixtures/graphql/ci/runner/list/all_runners.query.graphql.paginated.json';
-import allRunnersWithCreatorData from 'test_fixtures/graphql/ci/runner/list/all_runners.query.graphql.with_creator.json';
import runnersCountData from 'test_fixtures/graphql/ci/runner/list/all_runners_count.query.graphql.json';
import groupRunnersData from 'test_fixtures/graphql/ci/runner/list/group_runners.query.graphql.json';
import groupRunnersDataPaginated from 'test_fixtures/graphql/ci/runner/list/group_runners.query.graphql.paginated.json';
diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
index b8e6bcbc3c4..a328f79e4e7 100644
--- a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
+++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`content_editor/components/toolbar_button displays tertiary, medium button with a provided label and icon 1`] = `
-"<b-button-stub size=\\"md\\" tag=\\"button\\" type=\\"button\\" variant=\\"default\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-button btn-default-tertiary btn-icon\\">
+"<b-button-stub size=\\"sm\\" tag=\\"button\\" type=\\"button\\" variant=\\"default\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-mr-3 gl-button btn-default-tertiary btn-icon\\">
<!---->
<gl-icon-stub name=\\"bold\\" size=\\"16\\" class=\\"gl-button-icon\\"></gl-icon-stub>
<!---->
diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js
index b642ac9c46b..8bbd79a61af 100644
--- a/spec/frontend/content_editor/components/content_editor_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_spec.js
@@ -2,7 +2,6 @@ import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { EditorContent, Editor } from '@tiptap/vue-2';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
import ContentEditor from '~/content_editor/components/content_editor.vue';
import ContentEditorAlert from '~/content_editor/components/content_editor_alert.vue';
import ContentEditorProvider from '~/content_editor/components/content_editor_provider.vue';
@@ -44,7 +43,6 @@ describe('ContentEditor', () => {
ContentEditorAlert,
GlLink,
GlSprintf,
- EditorModeDropdown,
},
});
};
@@ -107,12 +105,6 @@ describe('ContentEditor', () => {
expect(findEditorElement().text()).not.toContain('For quick actions, type /');
});
- it('renders an editor mode dropdown', () => {
- createWrapper();
-
- expect(wrapper.findComponent(EditorModeDropdown).exists()).toBe(true);
- });
-
describe('when setting initial content', () => {
it('displays loading indicator', async () => {
createWrapper();
diff --git a/spec/frontend/content_editor/components/formatting_toolbar_spec.js b/spec/frontend/content_editor/components/formatting_toolbar_spec.js
index 5d2a9e493e5..2fc7e5e2e1b 100644
--- a/spec/frontend/content_editor/components/formatting_toolbar_spec.js
+++ b/spec/frontend/content_editor/components/formatting_toolbar_spec.js
@@ -6,6 +6,7 @@ import {
TOOLBAR_CONTROL_TRACKING_ACTION,
CONTENT_EDITOR_TRACKING_LABEL,
} from '~/content_editor/constants';
+import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue';
describe('content_editor/components/formatting_toolbar', () => {
let wrapper;
@@ -16,6 +17,7 @@ describe('content_editor/components/formatting_toolbar', () => {
stubs: {
GlTabs,
GlTab,
+ EditorModeSwitcher,
},
});
};
@@ -64,4 +66,10 @@ describe('content_editor/components/formatting_toolbar', () => {
});
});
});
+
+ it('renders an editor mode dropdown', () => {
+ buildWrapper();
+
+ expect(wrapper.findComponent(EditorModeSwitcher).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
index 71ffbd3f93c..0d56280d630 100644
--- a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
@@ -3,6 +3,7 @@ import { NodeViewWrapper } from '@tiptap/vue-2';
import { selectedRect as getSelectedRect } from '@tiptap/pm/tables';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { stubComponent } from 'helpers/stub_component';
import TableCellBaseWrapper from '~/content_editor/components/wrappers/table_cell_base.vue';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../../test_utils';
@@ -20,6 +21,13 @@ describe('content/components/wrappers/table_cell_base', () => {
node,
...propsData,
},
+ stubs: {
+ GlDropdown: stubComponent(GlDropdown, {
+ methods: {
+ hide: jest.fn(),
+ },
+ }),
+ },
});
};
@@ -38,14 +46,6 @@ describe('content/components/wrappers/table_cell_base', () => {
jest.spyOn($cursor, 'node').mockReturnValue(node);
};
- const mockDropdownHide = () => {
- /*
- * TODO: Replace this method with using the scoped hide function
- * provided by BootstrapVue https://bootstrap-vue.org/docs/components/dropdown.
- * GitLab UI is not exposing it in the default scope
- */
- findDropdown().vm.hide = jest.fn();
- };
beforeEach(() => {
node = {};
@@ -96,8 +96,6 @@ describe('content/components/wrappers/table_cell_base', () => {
createWrapper();
await nextTick();
-
- mockDropdownHide();
});
it.each`
diff --git a/spec/frontend/feature_flags/components/feature_flags_table_spec.js b/spec/frontend/feature_flags/components/feature_flags_table_spec.js
index f23bca54b55..02a8e38dc2a 100644
--- a/spec/frontend/feature_flags/components/feature_flags_table_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_table_spec.js
@@ -1,5 +1,6 @@
-import { GlToggle } from '@gitlab/ui';
+import { GlIcon, GlToggle } from '@gitlab/ui';
import { nextTick } from 'vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import { mockTracking } from 'helpers/tracking_helper';
@@ -46,6 +47,13 @@ const getDefaultProps = () => ({
},
],
},
+ {
+ id: 2,
+ iid: 2,
+ active: true,
+ name: 'flag without description',
+ description: '',
+ },
],
});
@@ -61,6 +69,9 @@ describe('Feature flag table', () => {
csrfToken: 'fakeToken',
},
...opts,
+ directives: {
+ GlTooltip: createMockDirective('gl-tooltip'),
+ },
});
};
@@ -105,10 +116,6 @@ describe('Feature flag table', () => {
it('Should render a feature flag column', () => {
expect(wrapper.find('.js-feature-flag-title').exists()).toBe(true);
expect(trimText(wrapper.find('.feature-flag-name').text())).toEqual('flag name');
-
- expect(trimText(wrapper.find('.feature-flag-description').text())).toEqual(
- 'flag description',
- );
});
it('should render an environments specs label', () => {
@@ -125,6 +132,37 @@ describe('Feature flag table', () => {
});
});
+ describe.each(getDefaultProps().featureFlags)('description tooltip', (featureFlag) => {
+ beforeEach(() => {
+ createWrapper(props);
+ });
+
+ const haveInfoIcon = Boolean(featureFlag.description);
+
+ it(`${haveInfoIcon ? 'displays' : "doesn't display"} an information icon`, () => {
+ expect(
+ wrapper
+ .findByTestId(featureFlag.id)
+ .find('.feature-flag-description')
+ .findComponent(GlIcon)
+ .exists(),
+ ).toBe(haveInfoIcon);
+ });
+
+ if (haveInfoIcon) {
+ it('includes a tooltip', () => {
+ const icon = wrapper
+ .findByTestId(featureFlag.id)
+ .find('.feature-flag-description')
+ .findComponent(GlIcon);
+ const tooltip = getBinding(icon.element, 'gl-tooltip');
+
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value).toBe(featureFlag.description);
+ });
+ }
+ });
+
describe('when active and with an update toggle', () => {
let toggle;
let spy;
diff --git a/spec/frontend/invite_members/components/group_select_spec.js b/spec/frontend/invite_members/components/group_select_spec.js
index a1ca9a69926..e7011f896b6 100644
--- a/spec/frontend/invite_members/components/group_select_spec.js
+++ b/spec/frontend/invite_members/components/group_select_spec.js
@@ -2,28 +2,29 @@ import { GlAvatarLabeled, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import * as groupsApi from '~/api/groups_api';
+import * as projectsApi from '~/api/projects_api';
import GroupSelect from '~/invite_members/components/group_select.vue';
const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' };
const group2 = { id: 2, full_name: 'Group Two', avatar_url: 'test' };
const allGroups = [group1, group2];
-const createComponent = (props = {}) => {
- return mount(GroupSelect, {
- propsData: {
- invalidGroups: [],
- ...props,
- },
- });
-};
-
describe('GroupSelect', () => {
let wrapper;
+ const createComponent = (props = {}) => {
+ wrapper = mount(GroupSelect, {
+ propsData: {
+ invalidGroups: [],
+ sourceId: '1',
+ isProject: false,
+ ...props,
+ },
+ });
+ };
+
beforeEach(() => {
jest.spyOn(groupsApi, 'getGroups').mockResolvedValue(allGroups);
-
- wrapper = createComponent();
});
const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
@@ -35,48 +36,93 @@ describe('GroupSelect', () => {
.wrappers.find((dropdownItemWrapper) => dropdownItemWrapper.props('label') === text);
it('renders GlSearchBoxByType with default attributes', () => {
+ createComponent();
+
expect(findSearchBoxByType().exists()).toBe(true);
expect(findSearchBoxByType().vm.$attrs).toMatchObject({
placeholder: 'Search groups',
});
});
- describe('when user types in the search input', () => {
- let resolveApiRequest;
+ describe('when `isProject` prop is `false`', () => {
+ describe('when user types in the search input', () => {
+ let resolveApiRequest;
- beforeEach(() => {
- jest.spyOn(groupsApi, 'getGroups').mockImplementation(
- () =>
- new Promise((resolve) => {
- resolveApiRequest = resolve;
- }),
- );
+ beforeEach(() => {
+ jest.spyOn(groupsApi, 'getGroups').mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ resolveApiRequest = resolve;
+ }),
+ );
- findSearchBoxByType().vm.$emit('input', group1.name);
- });
+ createComponent();
- it('calls the API', () => {
- resolveApiRequest({ data: allGroups });
+ findSearchBoxByType().vm.$emit('input', group1.name);
+ });
+
+ it('calls the API', () => {
+ resolveApiRequest(allGroups);
- expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, {
- exclude_internal: true,
- active: true,
- order_by: 'similarity',
+ expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, {
+ exclude_internal: true,
+ active: true,
+ order_by: 'similarity',
+ });
+ });
+
+ it('displays loading icon while waiting for API call to resolve', async () => {
+ expect(findSearchBoxByType().props('isLoading')).toBe(true);
+
+ resolveApiRequest(allGroups);
+ await waitForPromises();
+
+ expect(findSearchBoxByType().props('isLoading')).toBe(false);
});
});
+ });
- it('displays loading icon while waiting for API call to resolve', async () => {
- expect(findSearchBoxByType().props('isLoading')).toBe(true);
+ describe('when `isProject` prop is `true`', () => {
+ describe('when user types in the search input', () => {
+ let resolveApiRequest;
- resolveApiRequest({ data: allGroups });
- await waitForPromises();
+ beforeEach(() => {
+ jest.spyOn(projectsApi, 'getProjectShareLocations').mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ resolveApiRequest = resolve;
+ }),
+ );
+
+ createComponent({ isProject: true });
+
+ findSearchBoxByType().vm.$emit('input', group1.name);
+ });
+
+ it('calls the API', () => {
+ resolveApiRequest({ data: allGroups });
+
+ expect(projectsApi.getProjectShareLocations).toHaveBeenCalledWith('1', {
+ search: group1.name,
+ });
+ });
- expect(findSearchBoxByType().props('isLoading')).toBe(false);
+ it('displays loading icon while waiting for API call to resolve', async () => {
+ expect(findSearchBoxByType().props('isLoading')).toBe(true);
+
+ resolveApiRequest({ data: allGroups });
+ await waitForPromises();
+
+ expect(findSearchBoxByType().props('isLoading')).toBe(false);
+ });
});
});
describe('avatar label', () => {
- it('includes the correct attributes with name and avatar_url', () => {
+ it('includes the correct attributes with name and avatar_url', async () => {
+ createComponent();
+ await waitForPromises();
+
expect(findAvatarByLabel(group1.full_name).attributes()).toMatchObject({
src: group1.avatar_url,
'entity-id': `${group1.id}`,
@@ -87,7 +133,7 @@ describe('GroupSelect', () => {
describe('when filtering out the group from results', () => {
beforeEach(() => {
- wrapper = createComponent({ invalidGroups: [group1.id] });
+ createComponent({ invalidGroups: [group1.id] });
});
it('does not find an invalid group', () => {
@@ -101,7 +147,10 @@ describe('GroupSelect', () => {
});
describe('when group is selected from the dropdown', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+
findAvatarByLabel(group1.full_name).trigger('click');
});
diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
index 4f082145562..21e215764e5 100644
--- a/spec/frontend/invite_members/components/invite_groups_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
@@ -242,4 +242,16 @@ describe('InviteGroupsModal', () => {
});
});
});
+
+ it('renders `GroupSelect` component and passes correct props', () => {
+ createComponent();
+
+ expect(findGroupSelect().props()).toEqual({
+ groupsFilter: 'all',
+ sourceId: '1',
+ parentGroupId: null,
+ invalidGroups: [],
+ isProject: false,
+ });
+ });
});
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index fb64551c76b..70f25afc5ba 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -264,13 +264,13 @@ describe('issue_comment_form component', () => {
it('hides content editor switcher if feature flag content_editor_on_issues is off', () => {
mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: false } });
- expect(wrapper.text()).not.toContain('Rich text');
+ expect(wrapper.text()).not.toContain('Switch to rich text');
});
it('shows content editor switcher if feature flag content_editor_on_issues is on', () => {
mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: true } });
- expect(wrapper.text()).toContain('Rich text');
+ expect(wrapper.text()).toContain('Switch to rich text');
});
describe('textarea', () => {
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 63286927d53..879bada4aee 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -1,9 +1,10 @@
-import { mount, createWrapper } from '@vue/test-utils';
+import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
+import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
-import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import noteActions from '~/notes/components/note_actions.vue';
import { NOTEABLE_TYPE_MAPPING } from '~/notes/constants';
import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue';
@@ -19,6 +20,8 @@ describe('noteActions', () => {
let actions;
let axiosMock;
+ const mockCloseDropdown = jest.fn();
+
const findUserAccessRoleBadge = (idx) => wrapper.findAllComponents(UserAccessRoleBadge).at(idx);
const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim();
const findTimelineButton = () => wrapper.findComponent(TimelineEventButton);
@@ -45,6 +48,14 @@ describe('noteActions', () => {
store,
propsData,
computed,
+ stubs: {
+ GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
+ methods: {
+ close: mockCloseDropdown,
+ },
+ }),
+ GlDisclosureDropdownItem,
+ },
});
};
@@ -144,17 +155,6 @@ describe('noteActions', () => {
expect(wrapper.find('.js-note-delete').exists()).toBe(true);
});
- it('closes tooltip when dropdown opens', async () => {
- wrapper.find('.more-actions-toggle').trigger('click');
-
- const rootWrapper = createWrapper(wrapper.vm.$root);
-
- await nextTick();
- const emitted = Object.keys(rootWrapper.emitted());
-
- expect(emitted).toEqual([BV_HIDE_TOOLTIP]);
- });
-
it('should not be possible to assign or unassign the comment author in a merge request', () => {
const assignUserButton = wrapper.find('[data-testid="assign-user"]');
expect(assignUserButton.exists()).toBe(false);
@@ -175,6 +175,11 @@ describe('noteActions', () => {
const { resolveButton } = wrapper.vm.$refs;
expect(resolveButton.$el.getAttribute('title')).toBe(`Resolved by ${complexUnescapedName}`);
});
+
+ it('closes the dropdown', () => {
+ findReportAbuseButton().vm.$emit('action');
+ expect(mockCloseDropdown).toHaveBeenCalled();
+ });
});
});
@@ -401,13 +406,13 @@ describe('noteActions', () => {
});
it('opens the drawer when report abuse button is clicked', async () => {
- await findReportAbuseButton().trigger('click');
+ await findReportAbuseButton().vm.$emit('action');
expect(findAbuseCategorySelector().props('showDrawer')).toEqual(true);
});
it('closes the drawer', async () => {
- await findReportAbuseButton().trigger('click');
+ await findReportAbuseButton().vm.$emit('action');
findAbuseCategorySelector().vm.$emit('close-drawer');
await nextTick();
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index 9423af4f058..b5b33607282 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -66,13 +66,13 @@ describe('issue_note_form component', () => {
it('hides content editor switcher if feature flag content_editor_on_issues is off', () => {
createComponentWrapper({}, { contentEditorOnIssues: false });
- expect(wrapper.text()).not.toContain('Rich text');
+ expect(wrapper.text()).not.toContain('Switch to rich text');
});
it('shows content editor switcher if feature flag content_editor_on_issues is on', () => {
createComponentWrapper({}, { contentEditorOnIssues: true });
- expect(wrapper.text()).toContain('Rich text');
+ expect(wrapper.text()).toContain('Switch to rich text');
});
describe('conflicts editing', () => {
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
index 52615ac3c65..e72de11d921 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/shortcuts_spec.js
@@ -1,11 +1,10 @@
import $ from 'jquery';
-import htmlSnippetsShow from 'test_fixtures/snippets/show.html';
import { flatten } from 'lodash';
+import htmlSnippetsShow from 'test_fixtures/snippets/show.html';
import { Mousetrap } from '~/lib/mousetrap';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import Shortcuts, { LOCAL_MOUSETRAP_DATA_KEY } from '~/behaviors/shortcuts/shortcuts';
-
-jest.mock('mousetrap/plugins/pause/mousetrap-pause', () => {});
+import MarkdownPreview from '~/behaviors/preview_markdown';
describe('Shortcuts', () => {
const createEvent = (type, target) =>
@@ -21,6 +20,9 @@ describe('Shortcuts', () => {
beforeEach(() => {
setHTMLFixture(htmlSnippetsShow);
+ new Shortcuts(); // eslint-disable-line no-new
+ new MarkdownPreview(); // eslint-disable-line no-new
+
jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus');
jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus');
jest.spyOn(document.querySelector('#search'), 'focus');
diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
index 7eb0468c5be..c8d972b19a3 100644
--- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
+++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
@@ -19,7 +19,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
<gl-form-input-stub
class="form-control"
data-qa-selector="description_placeholder"
- placeholder="Optionally add a description about what your snippet does or how to use it…"
+ placeholder="Describe what your snippet does or how to use it…"
/>
</div>
@@ -90,7 +90,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
</div>
<div
- class="js-vue-md-preview md md-preview-holder"
+ class="js-vue-md-preview md md-preview-holder gl-px-5"
style="display: none;"
/>
diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js
index 1f236616e77..d2984254dee 100644
--- a/spec/frontend/super_sidebar/utils_spec.js
+++ b/spec/frontend/super_sidebar/utils_spec.js
@@ -13,8 +13,8 @@ describe('Super sidebar utils spec', () => {
describe('getTopFrequentItems', () => {
const maxItems = 3;
- it('returns empty array if no items provided', () => {
- const result = getTopFrequentItems();
+ it.each([undefined, null])('returns empty array if `items` is %s', (items) => {
+ const result = getTopFrequentItems(items);
expect(result.length).toBe(0);
});
diff --git a/spec/frontend/vue_shared/components/form/__snapshots__/form_footer_actions_spec.js.snap b/spec/frontend/vue_shared/components/form/__snapshots__/form_footer_actions_spec.js.snap
index 2189d6ac3cc..6f98a74a82f 100644
--- a/spec/frontend/vue_shared/components/form/__snapshots__/form_footer_actions_spec.js.snap
+++ b/spec/frontend/vue_shared/components/form/__snapshots__/form_footer_actions_spec.js.snap
@@ -2,18 +2,8 @@
exports[`Form Footer Actions renders content properly 1`] = `
<footer
- class="form-actions d-flex justify-content-between"
+ class="gl-mt-5 footer-block"
>
- <div>
- Bar
- </div>
-
- <div>
- Foo
- </div>
-
- <div>
- Abrakadabra
- </div>
+ Bar Foo Abrakadabra
</footer>
`;
diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
deleted file mode 100644
index fd8493e0911..00000000000
--- a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
-
-describe('vue_shared/component/markdown/editor_mode_dropdown', () => {
- let wrapper;
-
- const createComponent = ({ value, size } = {}) => {
- wrapper = shallowMount(EditorModeDropdown, {
- propsData: {
- value,
- size,
- },
- });
- };
-
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownItem = (text) =>
- wrapper
- .findAllComponents(GlDropdownItem)
- .filter((item) => item.text().startsWith(text))
- .at(0);
-
- describe.each`
- modeText | value | dropdownText | otherMode
- ${'Rich text'} | ${'richText'} | ${'Editing rich text'} | ${'Markdown'}
- ${'Markdown'} | ${'markdown'} | ${'Editing markdown'} | ${'Rich text'}
- `('$modeText', ({ modeText, value, dropdownText, otherMode }) => {
- beforeEach(() => {
- createComponent({ value });
- });
-
- it('shows correct dropdown label', () => {
- expect(findDropdown().props('text')).toEqual(dropdownText);
- });
-
- it('checks correct checked dropdown item', () => {
- expect(findDropdownItem(modeText).props().isChecked).toBe(true);
- expect(findDropdownItem(otherMode).props().isChecked).toBe(false);
- });
-
- it('emits event on click', () => {
- findDropdownItem(modeText).vm.$emit('click');
-
- expect(wrapper.emitted().input).toEqual([[value]]);
- });
- });
-
- it('passes size to dropdown', () => {
- createComponent({ size: 'small', value: 'markdown' });
-
- expect(findDropdown().props('size')).toEqual('small');
- });
-});
diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js
new file mode 100644
index 00000000000..693353ed604
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js
@@ -0,0 +1,37 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue';
+
+describe('vue_shared/component/markdown/editor_mode_switcher', () => {
+ let wrapper;
+
+ const createComponent = ({ value } = {}) => {
+ wrapper = shallowMount(EditorModeSwitcher, {
+ propsData: {
+ value,
+ },
+ });
+ };
+
+ const findSwitcherButton = () => wrapper.findComponent(GlButton);
+
+ describe.each`
+ modeText | value | buttonText
+ ${'Rich text'} | ${'richText'} | ${'Switch to Markdown'}
+ ${'Markdown'} | ${'markdown'} | ${'Switch to rich text'}
+ `('when $modeText', ({ modeText, value, buttonText }) => {
+ beforeEach(() => {
+ createComponent({ value });
+ });
+
+ it('shows correct button label', () => {
+ expect(findSwitcherButton().text()).toEqual(buttonText);
+ });
+
+ it('emits event on click', () => {
+ findSwitcherButton(modeText).vm.$emit('click');
+
+ expect(wrapper.emitted().input).toEqual([[]]);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index 68ce07f86b9..b29f0d58d77 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -18,12 +18,6 @@ const textareaValue = 'testing\n123';
const uploadsPath = 'test/uploads';
const restrictedToolBarItems = ['quote'];
-function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) {
- expect(writeLink.element.children[0].classList.contains('active')).toBe(isWrite);
- expect(previewLink.element.children[0].classList.contains('active')).toBe(!isWrite);
- expect(wrapper.find('.md-preview-holder').element.style.display).toBe(isWrite ? 'none' : '');
-}
-
describe('Markdown field component', () => {
let axiosMock;
let subject;
@@ -92,8 +86,7 @@ describe('Markdown field component', () => {
});
}
- const getPreviewLink = () => subject.findByTestId('preview-tab');
- const getWriteLink = () => subject.findByTestId('write-tab');
+ const getPreviewToggle = () => subject.findByTestId('preview-toggle');
const getMarkdownButton = () => subject.find('.js-md');
const getListBulletedButton = () => subject.findAll('.js-md[title="Add a bullet list"]');
const getVideo = () => subject.find('video');
@@ -109,8 +102,7 @@ describe('Markdown field component', () => {
<p>markdown preview</p>
<video src="${FIXTURES_PATH}/static/mock-video.mp4"></video>
`;
- let previewLink;
- let writeLink;
+ let previewToggle;
let dropzoneSpy;
beforeEach(() => {
@@ -140,8 +132,8 @@ describe('Markdown field component', () => {
.onPost(markdownPreviewPath)
.reply(HTTP_STATUS_OK, { references: { users: [], commands: 'test command' } });
- previewLink = getPreviewLink();
- previewLink.vm.$emit('click', { target: {} });
+ previewToggle = getPreviewToggle();
+ previewToggle.vm.$emit('click', true);
await axios.waitFor(markdownPreviewPath);
const referencedCommands = subject.find('[data-testid="referenced-commands"]');
@@ -155,26 +147,29 @@ describe('Markdown field component', () => {
axiosMock.onPost(markdownPreviewPath).reply(HTTP_STATUS_OK, { body: previewHTML });
});
- it('sets preview link as active', async () => {
- previewLink = getPreviewLink();
- previewLink.vm.$emit('click', { target: {} });
+ it('sets preview toggle as active', async () => {
+ previewToggle = getPreviewToggle();
+
+ expect(previewToggle.text()).toBe('Preview');
+
+ previewToggle.vm.$emit('click', true);
await nextTick();
- expect(previewLink.element.children[0].classList.contains('active')).toBe(true);
+ expect(previewToggle.text()).toBe('Continue editing');
});
it('shows preview loading text', async () => {
- previewLink = getPreviewLink();
- previewLink.vm.$emit('click', { target: {} });
+ previewToggle = getPreviewToggle();
+ previewToggle.vm.$emit('click', true);
await nextTick();
expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain('Loading…');
});
it('renders markdown preview and GFM', async () => {
- previewLink = getPreviewLink();
+ previewToggle = getPreviewToggle();
- previewLink.vm.$emit('click', { target: {} });
+ previewToggle.vm.$emit('click', true);
await axios.waitFor(markdownPreviewPath);
expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
@@ -182,8 +177,8 @@ describe('Markdown field component', () => {
});
it('calls video.pause() on comment input when isSubmitting is changed to true', async () => {
- previewLink = getPreviewLink();
- previewLink.vm.$emit('click', { target: {} });
+ previewToggle = getPreviewToggle();
+ previewToggle.vm.$emit('click', true);
await axios.waitFor(markdownPreviewPath);
const video = getVideo();
@@ -195,34 +190,27 @@ describe('Markdown field component', () => {
expect(callPause).toHaveBeenCalled();
});
- it('clicking already active write or preview link does nothing', async () => {
- writeLink = getWriteLink();
- previewLink = getPreviewLink();
-
- writeLink.vm.$emit('click', { target: {} });
- await nextTick();
-
- assertMarkdownTabs(true, writeLink, previewLink, subject);
- writeLink.vm.$emit('click', { target: {} });
- await nextTick();
+ it('switches between preview/write on toggle', async () => {
+ previewToggle = getPreviewToggle();
- assertMarkdownTabs(true, writeLink, previewLink, subject);
- previewLink.vm.$emit('click', { target: {} });
+ previewToggle.vm.$emit('click', true);
await nextTick();
+ expect(subject.find('.md-preview-holder').element.style.display).toBe(''); // visible
- assertMarkdownTabs(false, writeLink, previewLink, subject);
- previewLink.vm.$emit('click', { target: {} });
+ previewToggle.vm.$emit('click', false);
await nextTick();
-
- assertMarkdownTabs(false, writeLink, previewLink, subject);
+ expect(subject.find('.md-preview-holder').element.style.display).toBe('none');
});
- it('passes correct props to MarkdownToolbar', () => {
+ it('passes correct props to MarkdownHeader and MarkdownToolbar', () => {
expect(findMarkdownToolbar().props()).toEqual({
canAttachFile: true,
markdownDocsPath,
quickActionsDocsPath: '',
showCommentToolBar: true,
+ });
+
+ expect(findMarkdownHeader().props()).toMatchObject({
showContentEditorSwitcher: false,
});
});
@@ -380,13 +368,13 @@ describe('Markdown field component', () => {
it('defaults to false', () => {
createSubject();
- expect(findMarkdownToolbar().props('showContentEditorSwitcher')).toBe(false);
+ expect(findMarkdownHeader().props('showContentEditorSwitcher')).toBe(false);
});
it('passes showContentEditorSwitcher', () => {
createSubject({ showContentEditorSwitcher: true });
- expect(findMarkdownToolbar().props('showContentEditorSwitcher')).toBe(true);
+ expect(findMarkdownHeader().props('showContentEditorSwitcher')).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index 68f05e5119d..48fe5452e74 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -1,10 +1,11 @@
import $ from 'jquery';
import { nextTick } from 'vue';
-import { GlTabs } from '@gitlab/ui';
+import { GlToggle } from '@gitlab/ui';
import HeaderComponent from '~/vue_shared/components/markdown/header.vue';
import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
import DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue';
describe('Markdown field header component', () => {
let wrapper;
@@ -15,12 +16,11 @@ describe('Markdown field header component', () => {
previewMarkdown: false,
...props,
},
- stubs: { GlTabs },
+ stubs: { GlToggle },
});
};
- const findWriteTab = () => wrapper.findByTestId('write-tab');
- const findPreviewTab = () => wrapper.findByTestId('preview-tab');
+ const findPreviewToggle = () => wrapper.findByTestId('preview-toggle');
const findToolbar = () => wrapper.findByTestId('md-header-toolbar');
const findToolbarButtons = () => wrapper.findAllComponents(ToolbarButton);
const findToolbarButtonByProp = (prop, value) =>
@@ -87,16 +87,14 @@ describe('Markdown field header component', () => {
});
});
- it('activates `write` tab when previewMarkdown is false', () => {
- expect(findWriteTab().attributes('active')).toBe('true');
- expect(findPreviewTab().attributes('active')).toBeUndefined();
+ it('hides markdown preview when previewMarkdown is false', () => {
+ expect(findPreviewToggle().text()).toBe('Preview');
});
- it('activates `preview` tab when previewMarkdown is true', () => {
+ it('shows markdown preview when previewMarkdown is true', () => {
createWrapper({ previewMarkdown: true });
- expect(findWriteTab().attributes('active')).toBeUndefined();
- expect(findPreviewTab().attributes('active')).toBe('true');
+ expect(findPreviewToggle().text()).toBe('Continue editing');
});
it('hides toolbar in preview mode', () => {
@@ -105,17 +103,16 @@ describe('Markdown field header component', () => {
expect(findToolbar().classes().includes('gl-display-none!')).toBe(true);
});
- it('emits toggle markdown event when clicking preview tab', async () => {
- const eventData = { target: {} };
- findPreviewTab().vm.$emit('click', eventData);
+ it('emits toggle markdown event when clicking preview toggle', async () => {
+ findPreviewToggle().vm.$emit('click', true);
await nextTick();
- expect(wrapper.emitted('preview-markdown').length).toEqual(1);
+ expect(wrapper.emitted('showPreview').length).toEqual(1);
- findWriteTab().vm.$emit('click', eventData);
+ findPreviewToggle().vm.$emit('click', false);
await nextTick();
- expect(wrapper.emitted('write-markdown').length).toEqual(1);
+ expect(wrapper.emitted('showPreview').length).toEqual(2);
});
it('does not emit toggle markdown event when triggered from another form', () => {
@@ -125,15 +122,8 @@ describe('Markdown field header component', () => {
),
]);
- expect(wrapper.emitted('preview-markdown')).toBeUndefined();
- expect(wrapper.emitted('write-markdown')).toBeUndefined();
- });
-
- it('blurs preview link after click', () => {
- const target = { blur: jest.fn() };
- findPreviewTab().vm.$emit('click', { target });
-
- expect(target.blur).toHaveBeenCalled();
+ expect(wrapper.emitted('showPreview')).toBeUndefined();
+ expect(wrapper.emitted('hidePreview')).toBeUndefined();
});
it('renders markdown table template', () => {
@@ -166,12 +156,12 @@ describe('Markdown field header component', () => {
expect(wrapper.find('.js-suggestion-btn').exists()).toBe(false);
});
- it('hides preview tab when previewMarkdown property is false', () => {
+ it('hides markdown preview when previewMarkdown property is false', () => {
createWrapper({
enablePreview: false,
});
- expect(wrapper.findByTestId('preview-tab').exists()).toBe(false);
+ expect(wrapper.findByTestId('preview-toggle').exists()).toBe(false);
});
describe('restricted tool bar items', () => {
@@ -215,4 +205,18 @@ describe('Markdown field header component', () => {
});
});
});
+
+ describe('with content editor switcher', () => {
+ beforeEach(() => {
+ createWrapper({
+ showContentEditorSwitcher: true,
+ });
+ });
+
+ it('re-emits event from switcher', () => {
+ wrapper.findComponent(EditorModeSwitcher).vm.$emit('input', 'richText');
+
+ expect(wrapper.emitted('enableContentEditor')).toEqual([[]]);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
index 63a689088c7..dec2327db0f 100644
--- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
@@ -122,13 +122,13 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
it('enables content editor switcher when contentEditorEnabled prop is true', () => {
buildWrapper({ propsData: { enableContentEditor: true } });
- expect(findMarkdownField().text()).toContain('Rich text');
+ expect(findMarkdownField().text()).toContain('Switch to rich text');
});
it('hides content editor switcher when contentEditorEnabled prop is false', () => {
buildWrapper({ propsData: { enableContentEditor: false } });
- expect(findMarkdownField().text()).not.toContain('Rich text');
+ expect(findMarkdownField().text()).not.toContain('Switch to rich text');
});
it('passes down any additional props to markdown field component', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
index fea14f80496..2489421b697 100644
--- a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
@@ -1,6 +1,5 @@
import { mount } from '@vue/test-utils';
import Toolbar from '~/vue_shared/components/markdown/toolbar.vue';
-import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
describe('toolbar', () => {
let wrapper;
@@ -44,18 +43,4 @@ describe('toolbar', () => {
expect(wrapper.find('.comment-toolbar').exists()).toBe(true);
});
});
-
- describe('with content editor switcher', () => {
- beforeEach(() => {
- createMountedWrapper({
- showContentEditorSwitcher: true,
- });
- });
-
- it('re-emits event from switcher', () => {
- wrapper.findComponent(EditorModeDropdown).vm.$emit('input', 'richText');
-
- expect(wrapper.emitted('enableContentEditor')).toEqual([[]]);
- });
- });
});
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric_spec.rb
new file mode 100644
index 00000000000..33605783671
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_metric_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersGroupTypeActiveMetric, feature_category: :runner do
+ let_it_be(:group) { create(:group) }
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner,
+ :group,
+ groups: [group]
+ )
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric_spec.rb
new file mode 100644
index 00000000000..24d6ea6f1e9
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_group_type_active_online_metric_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersGroupTypeActiveOnlineMetric, feature_category: :runner do
+ let(:group) { create(:group) }
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner,
+ :group,
+ groups: [group],
+ contacted_at: 1.second.ago
+ )
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric_spec.rb
new file mode 100644
index 00000000000..ae4829cceef
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_metric_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersInstanceTypeActiveMetric, feature_category: :runner do
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner)
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric_spec.rb
new file mode 100644
index 00000000000..b1b9a5a6cea
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_instance_type_active_online_metric_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersInstanceTypeActiveOnlineMetric, feature_category: :runner do
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner, contacted_at: 1.second.ago)
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric_spec.rb
new file mode 100644
index 00000000000..6a3a8e6dd58
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_metric_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersMetric, feature_category: :runner do
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner)
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric_spec.rb
new file mode 100644
index 00000000000..eeb699c1377
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_metric_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersProjectTypeActiveMetric, feature_category: :runner do
+ let(:project) { build(:project) }
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner,
+ :project,
+ projects: [project]
+ )
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric_spec.rb
new file mode 100644
index 00000000000..c3ed752ae04
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_runners_project_type_active_online_metric_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiRunnersProjectTypeActiveOnlineMetric, feature_category: :runner do
+ let(:project) { build(:project) }
+ let(:expected_value) { 1 }
+
+ before do
+ create(:ci_runner,
+ :project,
+ projects: [project],
+ contacted_at: 1.second.ago
+ )
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/spec/lib/gitlab/usage_data_metrics_spec.rb b/spec/lib/gitlab/usage_data_metrics_spec.rb
index 6391b003096..1f52819fd9e 100644
--- a/spec/lib/gitlab/usage_data_metrics_spec.rb
+++ b/spec/lib/gitlab/usage_data_metrics_spec.rb
@@ -85,16 +85,4 @@ RSpec.describe Gitlab::UsageDataMetrics, :with_license, feature_category: :servi
end
end
end
-
- describe '.suggested_names' do
- subject { described_class.suggested_names }
-
- let(:suggested_names) do
- ::Gitlab::Usage::Metric.all.map(&:with_suggested_name).reduce({}, :deep_merge)
- end
-
- it 'includes Service Ping suggested names' do
- expect(subject).to match_array(suggested_names)
- end
- end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 7bf25af598c..e0640f10a03 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -665,29 +665,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
end
end
- describe '.runners_usage' do
- before do
- project = build(:project)
- create_list(:ci_runner, 2, :instance_type, :online)
- create(:ci_runner, :group, :online)
- create(:ci_runner, :group, :inactive)
- create_list(:ci_runner, 3, :project_type, :online, projects: [project])
- end
-
- subject { described_class.runners_usage }
-
- it 'gathers runner usage counts correctly' do
- expect(subject[:ci_runners]).to eq(7)
- expect(subject[:ci_runners_instance_type_active]).to eq(2)
- expect(subject[:ci_runners_group_type_active]).to eq(1)
- expect(subject[:ci_runners_project_type_active]).to eq(3)
-
- expect(subject[:ci_runners_instance_type_active_online]).to eq(2)
- expect(subject[:ci_runners_group_type_active_online]).to eq(1)
- expect(subject[:ci_runners_project_type_active_online]).to eq(3)
- end
- end
-
describe '.license_usage_data' do
subject { described_class.license_usage_data }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index db981c832ac..60583bc351d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -5321,13 +5321,13 @@ RSpec.describe User, feature_category: :user_profile do
end
describe '#source_groups_of_two_factor_authentication_requirement' do
- let_it_be(:group_not_requiring_2FA) { create :group }
+ let_it_be(:group_not_requiring_2fa) { create :group }
let(:user) { create :user }
before do
group.add_member(user, GroupMember::OWNER)
- group_not_requiring_2FA.add_member(user, GroupMember::OWNER)
+ group_not_requiring_2fa.add_member(user, GroupMember::OWNER)
end
context 'when user is direct member of group requiring 2FA' do
diff --git a/spec/support/helpers/content_editor_helpers.rb b/spec/support/helpers/content_editor_helpers.rb
index 1bbc05cc05a..f19af0c9af8 100644
--- a/spec/support/helpers/content_editor_helpers.rb
+++ b/spec/support/helpers/content_editor_helpers.rb
@@ -2,8 +2,7 @@
module ContentEditorHelpers
def switch_to_content_editor
- click_button _('Editing markdown')
- click_button _('Rich text')
+ click_button("Switch to rich text")
end
def type_in_content_editor(keys)
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 78774b515df..7973d541f9c 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -41,7 +41,7 @@ module Features
wait_for_requests
filled_text.send_keys(:escape)
- click_on('Preview')
+ click_button("Preview")
yield if block_given?
end
diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb
index fa2705a64fa..40f1f6fe6f3 100644
--- a/spec/support/helpers/note_interaction_helpers.rb
+++ b/spec/support/helpers/note_interaction_helpers.rb
@@ -7,6 +7,6 @@ module NoteInteractionHelpers
note_element = find_by_scrolling("#note_#{note.id}")
note_element.find('.more-actions-toggle').click
- note_element.find('.more-actions .dropdown-menu li', match: :first)
+ note_element.find('.more-actions li', match: :first)
end
end
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index beee663fbc6..24249438faf 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -7,7 +7,6 @@ module UsageDataHelpers
ci_external_pipelines
ci_pipeline_config_auto_devops
ci_pipeline_config_repository
- ci_runners
ci_triggers
ci_pipeline_schedules
auto_devops_enabled
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index d2dfb468485..2bcbd5e5190 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -104,8 +104,8 @@ RSpec.shared_examples 'an editable merge request' do
fill_in 'merge_request_description', with: long_description
height = get_textarea_height
- find('.js-md-preview-button').click
- find('.js-md-write-button').click
+ click_button("Preview")
+ click_button("Continue editing")
new_height = get_textarea_height
expect(height).to eq(new_height)
diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb
index 45ad4d5cf71..133da230bed 100644
--- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb
+++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb
@@ -48,6 +48,6 @@ RSpec.shared_examples 'reportable note' do |type|
restore_window_size
dropdown.find('.more-actions-toggle').click
- dropdown.find('.dropdown-menu li', match: :first)
+ dropdown.find('.more-actions li', match: :first)
end
end
diff --git a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
index 7a3b94ad81d..6451c531aec 100644
--- a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
@@ -62,7 +62,7 @@ RSpec.shared_examples 'wiki file attachments' do
attach_with_dropzone(true)
wait_for_requests
- find('.js-md-preview-button').click
+ click_button("Preview")
file_path = page.find('input[name="files[]"]', visible: :hidden).value
link = page.find('a.no-attachment-icon')['href']
img_link = page.find('a.no-attachment-icon img')['src']
diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
index 3e285bb8ad7..ca68df9a89b 100644
--- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
@@ -78,7 +78,7 @@ RSpec.shared_examples 'User previews wiki changes' do
it_behaves_like 'relative links' do
before do
- click_on 'Preview'
+ click_button("Preview")
end
let(:element) { preview }
@@ -88,7 +88,7 @@ RSpec.shared_examples 'User previews wiki changes' do
# using two `\n` ensures we're sublist to it's own line due
# to list auto-continue
fill_in :wiki_content, with: "1. one\n\n - sublist\n"
- click_on "Preview"
+ click_button("Preview")
# the above generates two separate lists (not embedded) in CommonMark
expect(preview).to have_content("sublist")
@@ -102,7 +102,7 @@ RSpec.shared_examples 'User previews wiki changes' do
[[also_do_not_linkify]]
```
HEREDOC
- click_on "Preview"
+ click_button("Preview")
expect(preview).to have_content("do_not_linkify")
expect(preview).to have_content('[[do_not_linkify]]')
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index bce889b454d..5740adb3f0e 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -68,3 +68,64 @@ RSpec.shared_examples_for 'LEFT JOIN-able value stream analytics event' do
end
end
end
+
+RSpec.shared_examples_for 'value stream analytics first assignment event methods' do
+ let_it_be(:model1) { create(model_factory) } # rubocop: disable Rails/SaveBang
+ let_it_be(:model2) { create(model_factory) } # rubocop: disable Rails/SaveBang
+
+ let_it_be(:assignment_event1) do
+ create(event_factory, action: :add, created_at: 3.years.ago, model_factory => model1)
+ end
+
+ let_it_be(:assignment_event2) do
+ create(event_factory, action: :add, created_at: 2.years.ago, model_factory => model1)
+ end
+
+ let_it_be(:unassignment_event1) do
+ create(event_factory, action: :remove, created_at: 1.year.ago, model_factory => model1)
+ end
+
+ let(:query) { model1.class.where(id: [model1.id, model2.id]) }
+ let(:event) { described_class.new({}) }
+
+ describe '#apply_query_customization' do
+ subject(:records) { event.apply_query_customization(query).pluck(:id, *event.column_list).to_a }
+
+ it 'looks up the first assignment event timestamp' do
+ expect(records).to match_array([[model1.id, be_within(1.second).of(assignment_event1.created_at)]])
+ end
+ end
+
+ describe '#apply_negated_query_customization' do
+ subject(:records) { event.apply_negated_query_customization(query).pluck(:id).to_a }
+
+ it 'returns records where the event has not happened yet' do
+ expect(records).to eq([model2.id])
+ end
+ end
+
+ describe '#include_in' do
+ subject(:records) { event.include_in(query).pluck(:id, *event.column_list).to_a }
+
+ it 'returns both records' do
+ expect(records).to match_array([
+ [model1.id, be_within(1.second).of(assignment_event1.created_at)],
+ [model2.id, nil]
+ ])
+ end
+
+ context 'when invoked multiple times' do
+ subject(:records) do
+ scope = event.include_in(query)
+ event.include_in(scope).pluck(:id, *event.column_list).to_a
+ end
+
+ it 'returns both records' do
+ expect(records).to match_array([
+ [model1.id, be_within(1.second).of(assignment_event1.created_at)],
+ [model2.id, nil]
+ ])
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 1a0b2612033..b3a8deae813 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4192,10 +4192,10 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
-core-js@^3.29.1, core-js@^3.6.5:
- version "3.29.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6"
- integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==
+core-js@^3.29.1, core-js@^3.30.1, core-js@^3.6.5:
+ version "3.30.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.1.tgz#fc9c5adcc541d8e9fa3e381179433cbf795628ba"
+ integrity sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==
core-util-is@~1.0.0:
version "1.0.3"
@@ -12677,10 +12677,10 @@ vue-resize@^1.0.1:
dependencies:
"@vue/devtools-api" "^6.4.5"
-vue-router@3.4.9:
- version "3.4.9"
- resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.9.tgz#c016f42030ae2932f14e4748b39a1d9a0e250e66"
- integrity sha512-CGAKWN44RqXW06oC+u4mPgHLQQi2t6vLD/JbGRDAXm0YpMv0bgpKuU5bBd7AvMgfTz9kXVRIWKHqRwGEb8xFkA==
+vue-router@3.6.5:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.6.5.tgz#95847d52b9a7e3f1361cb605c8e6441f202afad8"
+ integrity sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==
vue-runtime-helpers@^1.1.2:
version "1.1.2"