From ec3e75cfeadc943e3fc63ac5e6b5504ad43ea512 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 15 Jun 2022 21:10:04 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../Service Ping reporting and monitoring.md | 9 +- .../extensions/code_block_highlight.js | 3 +- .../components/work_item_description.vue | 214 ++++++++++++++++- .../components/work_item_detail.vue | 12 +- .../update_work_item_widgets.mutation.graphql | 10 + .../work_items/pages/work_item_root.vue | 4 + app/helpers/markup_helper.rb | 28 ++- app/policies/project_policy.rb | 6 +- app/views/groups/runners/_settings.html.haml | 20 +- .../_enforcement_checkbox.html.haml | 3 +- .../cascading_settings/_lock_icon.html.haml | 4 +- ...ccess.yml => markup_rendering_timeout.yml} | 12 +- ...1_create_confidential_notes_index_on_id.rb | 19 ++ db/schema_migrations/20220613095911 | 1 + db/structure.sql | 4 +- doc/ci/yaml/index.md | 55 +++-- doc/user/group/index.md | 9 +- lib/banzai/filter/syntax_highlight_filter.rb | 1 + .../diff/rendered/notebook/diff_file.rb | 7 +- lib/gitlab/highlight.rb | 9 +- lib/gitlab/render_timeout.rb | 14 ++ locale/gitlab.pot | 57 +++-- package.json | 4 +- qa/qa/tools/delete_projects.rb | 8 +- qa/qa/tools/delete_subgroups.rb | 8 +- qa/qa/tools/test_resources_handler.rb | 2 +- spec/fixtures/glfm/example_snapshots/html.yml | 183 ++++++++------- .../markdown_golden_master_examples.yml | 24 +- .../components/work_item_description_spec.js | 222 ++++++++++++++++++ spec/frontend/work_items/mock_data.js | 41 ++++ .../work_items/pages/work_item_root_spec.js | 1 + spec/helpers/markup_helper_spec.rb | 27 +++ .../filter/syntax_highlight_filter_spec.rb | 10 +- spec/lib/gitlab/asciidoc_spec.rb | 6 +- .../diff/rendered/notebook/diff_file_spec.rb | 2 +- spec/lib/gitlab/highlight_spec.rb | 15 +- spec/lib/gitlab/render_timeout_spec.rb | 25 ++ spec/policies/project_policy_spec.rb | 82 +++---- .../build_service_spec.rb | 118 ++++------ spec/support/helpers/gitaly_setup.rb | 1 + .../cascading_settings_shared_examples.rb | 3 +- yarn.lock | 68 +++--- 42 files changed, 961 insertions(+), 390 deletions(-) create mode 100644 app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql rename config/feature_flags/development/{faster_owner_access.yml => markup_rendering_timeout.yml} (55%) create mode 100644 db/post_migrate/20220613095911_create_confidential_notes_index_on_id.rb create mode 100644 db/schema_migrations/20220613095911 create mode 100644 lib/gitlab/render_timeout.rb create mode 100644 spec/frontend/work_items/components/work_item_description_spec.js create mode 100644 spec/lib/gitlab/render_timeout_spec.rb diff --git a/.gitlab/issue_templates/Service Ping reporting and monitoring.md b/.gitlab/issue_templates/Service Ping reporting and monitoring.md index 045f13c1c40..1c0d221318b 100644 --- a/.gitlab/issue_templates/Service Ping reporting and monitoring.md +++ b/.gitlab/issue_templates/Service Ping reporting and monitoring.md @@ -18,7 +18,7 @@ Broken metrics issues are marked with the ~"broken metric" label. ## Prerequisites -1. Make sure the SSH key is added to the local SSH agent: `ssh-add`. +1. Add your SSH key to the local SSH agent: `ssh-add`. Your SSH key is required to connect to a Rails console from the bastion host. ## Triggering @@ -97,6 +97,13 @@ Trigger some events from the User Interface. Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: 'event_name', start_date: 28.days.ago, end_date: Date.current) ``` +# Troubleshooting + +## Connecting to a Rails console host fails with `Permission denied (publickey).`. + +Make sure you add the SSH key to the local SSH agent with: `ssh-add`. If you don't add your SSH key, your key won't be forwarded +when you run `ssh -A`, and you will not be able to connect to a Rails console host. + # What to do if you get mentioned In this issue, we keep the track of new metrics added to the Service Ping, and the metrics that are timing out. diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index cc4ba84a29d..61f6a233694 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -4,7 +4,8 @@ import { VueNodeViewRenderer } from '@tiptap/vue-2'; import languageLoader from '../services/code_block_language_loader'; import CodeBlockWrapper from '../components/wrappers/code_block.vue'; -const extractLanguage = (element) => element.getAttribute('lang'); +const extractLanguage = (element) => element.dataset.canonicalLang ?? element.getAttribute('lang'); + export const backtickInputRegex = /^```([a-z]+)?[\s\n]$/; export const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/; diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue index a4118cc48c4..5a85fcdd7ac 100644 --- a/app/assets/javascripts/work_items/components/work_item_description.vue +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -1,42 +1,234 @@ diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index afcf28416e9..d9e92ead86c 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -86,12 +86,12 @@ export default { canDelete() { return this.workItem?.userPermissions?.deleteWorkItem; }, - workItemDescription() { - return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_DESCRIPTION); - }, workItemsMvc2Enabled() { return this.glFeatures.workItemsMvc2; }, + hasDescriptionWidget() { + return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_DESCRIPTION); + }, workItemAssignees() { return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_ASSIGNEE); }, @@ -146,11 +146,9 @@ export default { @error="error = $event" /> diff --git a/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql new file mode 100644 index 00000000000..148b340b439 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql @@ -0,0 +1,10 @@ +#import "./work_item.fragment.graphql" + +mutation workItemUpdateWidgets($input: WorkItemUpdateWidgetsInput!) { + workItemUpdateWidgets(input: $input) { + workItem { + ...WorkItem + } + errors + } +} diff --git a/app/assets/javascripts/work_items/pages/work_item_root.vue b/app/assets/javascripts/work_items/pages/work_item_root.vue index 6dc3dc3b3c9..e9840889bdb 100644 --- a/app/assets/javascripts/work_items/pages/work_item_root.vue +++ b/app/assets/javascripts/work_items/pages/work_item_root.vue @@ -4,6 +4,7 @@ import { TYPE_WORK_ITEM } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { visitUrl } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; +import ZenMode from '~/zen_mode'; import WorkItemDetail from '../components/work_item_detail.vue'; import deleteWorkItemMutation from '../graphql/delete_work_item.mutation.graphql'; @@ -29,6 +30,9 @@ export default { return convertToGraphQLId(TYPE_WORK_ITEM, this.id); }, }, + mounted() { + this.ZenMode = new ZenMode(); + }, methods: { deleteWorkItem() { this.$apollo diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index fd67465575b..777d485797f 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -6,6 +6,12 @@ module MarkupHelper include ActionView::Helpers::TextHelper include ActionView::Context + # Let's increase the render timeout + # For a smaller one, a test that renders the blob content statically fails + # We can consider removing this custom timeout when refactor_blob_viewer FF is removed: + # https://gitlab.com/gitlab-org/gitlab/-/issues/324351 + RENDER_TIMEOUT = 5.seconds + def plain?(filename) Gitlab::MarkupHelper.plain?(filename) end @@ -139,14 +145,22 @@ module MarkupHelper def markup_unsafe(file_name, text, context = {}) return '' unless text.present? - if gitlab_markdown?(file_name) - markdown_unsafe(text, context) - elsif asciidoc?(file_name) - asciidoc_unsafe(text, context) - elsif plain?(file_name) - plain_unsafe(text) + markup = proc do + if gitlab_markdown?(file_name) + markdown_unsafe(text, context) + elsif asciidoc?(file_name) + asciidoc_unsafe(text, context) + elsif plain?(file_name) + plain_unsafe(text) + else + other_markup_unsafe(file_name, text, context) + end + end + + if Feature.enabled?(:markup_rendering_timeout, @project) + Gitlab::RenderTimeout.timeout(foreground: RENDER_TIMEOUT, &markup) else - other_markup_unsafe(file_name, text, context) + markup.call end rescue StandardError => e Gitlab::ErrorTracking.track_exception(e, project_id: @project&.id, file_name: file_name) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index dd97915010f..fc4ff02934d 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -29,11 +29,7 @@ class ProjectPolicy < BasePolicy owner_of_personal_namespace = project.owner.present? && project.owner == @user unless owner_of_personal_namespace - group_or_project_owner = if Feature.enabled?(:faster_owner_access) - team_access_level >= Gitlab::Access::OWNER - else - project.group&.has_owner?(@user) - end + group_or_project_owner = team_access_level >= Gitlab::Access::OWNER end owner_of_personal_namespace || group_or_project_owner diff --git a/app/views/groups/runners/_settings.html.haml b/app/views/groups/runners/_settings.html.haml index b805b7404a0..3e5ec3c26e2 100644 --- a/app/views/groups/runners/_settings.html.haml +++ b/app/views/groups/runners/_settings.html.haml @@ -3,16 +3,10 @@ - if @group.licensed_feature_available?(:stale_runner_cleanup_for_namespace) .gl-mb-5 #stale-runner-cleanup-form{ data: { group_full_path: @group.full_path, stale_timeout_secs: ::Ci::Runner::STALE_TIMEOUT.to_i } } -= render Pajamas::CardComponent.new(card_options: { class: 'gl-px-8 gl-py-6 gl-line-height-20' }, - body_options: { class: 'gl-display-flex gl-p-0!'}) do |c| - = c.body do - .gl-banner-illustration - = image_tag('illustrations/rocket-launch-md.svg', alt: s_('Runners|Rocket launch illustration')) - .gl-banner-content - %h1.gl-banner-title - = s_('Runners|New group runners view') - %p - = s_('Runners|The new view gives you more space and better visibility into your fleet of runners.') - %a.btn.btn-confirm.btn-md.gl-button{ :href => group_runners_path(@group) } - %span.gl-button-text - = s_('Runners|Take me there!') += render Pajamas::BannerComponent.new(button_text: s_('Runners|Take me there!'), + button_link: group_runners_path(@group), + svg_path: 'illustrations/rocket-launch-md.svg', + close_options: { class: 'gl-display-none' }) do |c| + - c.title do + = s_('Runners|New group runners view') + %p= s_('Runners|The new view gives you more space and better visibility into your fleet of runners.') diff --git a/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml b/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml index d167ffb5582..68a4d010872 100644 --- a/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml +++ b/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml @@ -3,6 +3,7 @@ - form = local_assigns.fetch(:form, nil) - setting_locked = local_assigns.fetch(:setting_locked, false) - help_text = local_assigns.fetch(:help_text, s_('CascadingSettings|Subgroups cannot change this setting.')) +- label = local_assigns.fetch(:label, s_('CascadingSettings|Enforce for all subgroups')) - return unless attribute && group && form - return if setting_locked @@ -10,6 +11,6 @@ - lock_attribute = "lock_#{attribute}" = form.gitlab_ui_checkbox_component lock_attribute, - s_('CascadingSettings|Enforce for all subgroups'), + label, help_text: help_text, checkbox_options: { checked: group.namespace_settings.public_send(lock_attribute), data: { testid: 'enforce-for-all-subgroups-checkbox' } } diff --git a/app/views/shared/namespaces/cascading_settings/_lock_icon.html.haml b/app/views/shared/namespaces/cascading_settings/_lock_icon.html.haml index 8431a38a69b..ed835af6524 100644 --- a/app/views/shared/namespaces/cascading_settings/_lock_icon.html.haml +++ b/app/views/shared/namespaces/cascading_settings/_lock_icon.html.haml @@ -1 +1,3 @@ -= render Pajamas::ButtonComponent.new(category: 'tertiary', icon: 'lock', button_options: { class: 'position-absolute gl-top-3 gl-right-0 gl-translate-y-n50 gl-p-1! gl-bg-transparent! gl-cursor-default! js-cascading-settings-lock-popover-target', data: cascading_namespace_settings_popover_data(attribute, group, settings_path_helper) }) +- class_list = local_assigns.fetch(:class_list, '') + += render Pajamas::ButtonComponent.new(category: 'tertiary', icon: 'lock', button_options: { class: "gl-absolute gl-top-3 gl-right-0 gl-translate-y-n50 gl-p-1! gl-bg-transparent! gl-cursor-default! js-cascading-settings-lock-popover-target #{class_list}", data: cascading_namespace_settings_popover_data(attribute, group, settings_path_helper) }) diff --git a/config/feature_flags/development/faster_owner_access.yml b/config/feature_flags/development/markup_rendering_timeout.yml similarity index 55% rename from config/feature_flags/development/faster_owner_access.yml rename to config/feature_flags/development/markup_rendering_timeout.yml index 29c15a7b68c..6c579ebe28a 100644 --- a/config/feature_flags/development/faster_owner_access.yml +++ b/config/feature_flags/development/markup_rendering_timeout.yml @@ -1,8 +1,8 @@ --- -name: faster_owner_access -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82177 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362328 -milestone: '15.0' +name: markup_rendering_timeout +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89509 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365358 +milestone: '15.1' type: development -group: group::workspace -default_enabled: true +group: group::source code +default_enabled: false diff --git a/db/post_migrate/20220613095911_create_confidential_notes_index_on_id.rb b/db/post_migrate/20220613095911_create_confidential_notes_index_on_id.rb new file mode 100644 index 00000000000..80c26c3ea8a --- /dev/null +++ b/db/post_migrate/20220613095911_create_confidential_notes_index_on_id.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class CreateConfidentialNotesIndexOnId < Gitlab::Database::Migration[2.0] + OLD_INDEX_NAME = 'index_notes_on_confidential' + INDEX_NAME = 'index_notes_on_id_where_confidential' + + disable_ddl_transaction! + + def up + remove_concurrent_index_by_name :notes, name: OLD_INDEX_NAME + add_concurrent_index :notes, :id, where: 'confidential = true', name: INDEX_NAME + end + + def down + # we don't have to re-create OLD_INDEX_NAME index + # because it wasn't used yet, also its creation might be expensive + remove_concurrent_index_by_name :notes, name: INDEX_NAME + end +end diff --git a/db/schema_migrations/20220613095911 b/db/schema_migrations/20220613095911 new file mode 100644 index 00000000000..c2f60fafd11 --- /dev/null +++ b/db/schema_migrations/20220613095911 @@ -0,0 +1 @@ +1d2dc45d6fae911d75eaf5970afbae6d2f31d2efd1c27b75fce5feacbcc319d3 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 0b4eb6d55a1..fb9bbd72aa8 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -28643,12 +28643,12 @@ CREATE INDEX index_notes_on_author_id_and_created_at_and_id ON notes USING btree CREATE INDEX index_notes_on_commit_id ON notes USING btree (commit_id); -CREATE INDEX index_notes_on_confidential ON notes USING btree (confidential) WHERE (confidential = true); - CREATE INDEX index_notes_on_created_at ON notes USING btree (created_at); CREATE INDEX index_notes_on_discussion_id ON notes USING btree (discussion_id); +CREATE INDEX index_notes_on_id_where_confidential ON notes USING btree (id) WHERE (confidential = true); + CREATE INDEX index_notes_on_line_code ON notes USING btree (line_code); CREATE INDEX index_notes_on_noteable_id_and_noteable_type_and_system ON notes USING btree (noteable_id, noteable_type, system); diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 1e343801120..d4159984542 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -2336,43 +2336,58 @@ can use that variable in `needs:pipeline` to download artifacts from the parent To need a job that sometimes does not exist in the pipeline, add `optional: true` to the `needs` configuration. If not defined, `optional: false` is the default. -Jobs that use [`rules`](#rules), [`only`, or `except`](#only--except), might -not always exist in a pipeline. When the pipeline is created, GitLab checks the `needs` -relationships before starting it. Without `optional: true`, needs relationships that -point to a job that does not exist stops the pipeline from starting and causes a pipeline -error similar to: +Jobs that use [`rules`](#rules), [`only`, or `except`](#only--except) might not always +be added to a pipeline. GitLab checks the `needs` relationships before starting a +pipeline: -- `'job1' job needs 'job2' job, but it was not added to the pipeline` +- If the needs entry has `optional: true` and the needed job is present in the pipeline, + the job waits for it to complete before starting. +- If the needed job is not present, the job can start when all other needs requirements are met. +- If the `needs` section contains only optional jobs, and none are added to the pipeline, + the job starts immediately (the same as an empty `needs` entry: `needs: []`). +- If a needed job has `optional: false`, but it was not added to the pipeline, the + pipeline fails to start with an error similar to: `'job1' job needs 'job2' job, but it was not added to the pipeline`. **Keyword type**: Job keyword. You can use it only as part of a job. -**Possible inputs**: - -- `job`: The job to make optional. -- `true` or `false` (default). - **Example of `needs:optional`**: ```yaml -build: +build-job: stage: build + +test-job1: + stage: test + +test-job2: + stage: test rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -rspec: - stage: test +deploy-job: + stage: deploy needs: - - job: build + - job: test-job2 + optional: true + - job: test-job1 + +review-job: + stage: deploy + needs: + - job: test-job2 optional: true ``` In this example: -- When the branch is the default branch, the `build` job exists in the pipeline, and the `rspec` - job waits for it to complete before starting. -- When the branch is not the default branch, the `build` job does not exist in the pipeline. - The `rspec` job runs immediately (similar to `needs: []`) because its `needs` - relationship to the `build` job is optional. +- `build-job`, `test-job1`, and `test-job2` start in stage order. +- When the branch is the default branch, `test-job2` is added to the pipeline, so: + - `deploy-job` waits for both `test-job1` and `test-job2` to complete. + - `review-job` waits for `test-job2` to complete. +- When the branch is not the default branch, `test-job2` is not added to the pipeline, so: + - `deploy-job` waits for only `test-job1` to complete, and does not wait for the missing `test-job2`. + - `review-job` has no other needed jobs and starts immediately (at the same time as `build-job`), + like `needs: []`. #### `needs:pipeline` diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 528cf3b5d46..8c116c2de28 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -737,11 +737,12 @@ To disable group mentions: > - [Inheritance and enforcement added](https://gitlab.com/gitlab-org/gitlab/-/issues/321724) in GitLab 13.11. > - [Instance setting to enable by default added](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2. > - [Instance setting is inherited and enforced when disabled](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) in GitLab 15.1. +> - [User interface changed](https://gitlab.com/gitlab-org/gitlab/-/issues/352961) in GitLab 15.1. [Delayed project deletion](../project/settings/index.md#delayed-project-deletion) is locked and disabled unless the instance-level settings for [deletion protection](../admin_area/settings/visibility_and_access_controls.md#deletion-protection) is enabled for either groups only or groups and projects. When enabled on groups, projects in the group are deleted after a period of delay. During this period, projects are in a read-only state and can be restored. -The default period is seven days but [is configurable at the instance level](../admin_area/settings/visibility_and_access_controls.md#deletion-protection). +The default period is seven days but [is configurable at the instance level](../admin_area/settings/visibility_and_access_controls.md#retention-period). On self-managed GitLab, projects are deleted immediately by default. In GitLab 14.2 and later, an administrator can @@ -755,8 +756,10 @@ To enable delayed deletion of projects in a group: 1. Go to the group's **Settings > General** page. 1. Expand the **Permissions and group features** section. -1. Check **Enable delayed project deletion**. -1. Optional. To prevent subgroups from changing this setting, select **Enforce for all subgroups**. +1. Scroll to: + - (GitLab 15.1 and later) **Deletion protection** and select **Keep deleted projects**. + - (GitLab 15.0 and earlier) **Enable delayed project deletion** and tick the checkbox. +1. Optional. To prevent subgroups from changing this setting, select **Enforce for all subgroups**. Renamed to **Enforce deletion protection for all subgroups** in GitLab 15.1. 1. Select **Save changes**. NOTE: diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index bcd9f39d1dc..7175e99f1c7 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -60,6 +60,7 @@ module Banzai highlighted = %(
#{code}
) diff --git a/lib/gitlab/diff/rendered/notebook/diff_file.rb b/lib/gitlab/diff/rendered/notebook/diff_file.rb index 0795e0acebb..0a5b2ec3890 100644 --- a/lib/gitlab/diff/rendered/notebook/diff_file.rb +++ b/lib/gitlab/diff/rendered/notebook/diff_file.rb @@ -8,7 +8,6 @@ module Gitlab include Gitlab::Utils::StrongMemoize RENDERED_TIMEOUT_BACKGROUND = 10.seconds - RENDERED_TIMEOUT_FOREGROUND = 1.5.seconds BACKGROUND_EXECUTION = 'background' FOREGROUND_EXECUTION = 'foreground' LOG_IPYNBDIFF_GENERATED = 'IPYNB_DIFF_GENERATED' @@ -70,7 +69,7 @@ module Gitlab next end - Timeout.timeout(timeout_time) do + Gitlab::RenderTimeout.timeout(background: RENDERED_TIMEOUT_BACKGROUND) do IpynbDiff.diff(source_diff.old_blob&.data, source_diff.new_blob&.data, raise_if_invalid_nb: true, diffy_opts: { include_diff_info: true })&.tap do @@ -104,10 +103,6 @@ module Gitlab ) end - def timeout_time - Gitlab::Runtime.sidekiq? ? RENDERED_TIMEOUT_BACKGROUND : RENDERED_TIMEOUT_FOREGROUND - end - def log_event(message, error = nil) Gitlab::AppLogger.info({ message: message }) Gitlab::ErrorTracking.log_exception(error) if error diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 758a594036b..b71abe5c052 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -2,9 +2,6 @@ module Gitlab class Highlight - TIMEOUT_BACKGROUND = 30.seconds - TIMEOUT_FOREGROUND = 1.5.seconds - def self.highlight(blob_name, blob_content, language: nil, plain: false) new(blob_name, blob_content, language: language) .highlight(blob_content, continue: false, plain: plain) @@ -72,7 +69,7 @@ module Gitlab def highlight_rich(text, continue: true) tag = lexer.tag tokens = lexer.lex(text, continue: continue) - Timeout.timeout(timeout_time) { @formatter.format(tokens, **context, tag: tag).html_safe } + Gitlab::RenderTimeout.timeout { @formatter.format(tokens, **context, tag: tag).html_safe } rescue Timeout::Error => e Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) highlight_plain(text) @@ -80,10 +77,6 @@ module Gitlab highlight_plain(text) end - def timeout_time - Gitlab::Runtime.sidekiq? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND - end - def link_dependencies(text, highlighted_text) Gitlab::DependencyLinker.link(blob_name, text, highlighted_text) end diff --git a/lib/gitlab/render_timeout.rb b/lib/gitlab/render_timeout.rb new file mode 100644 index 00000000000..b3c2a5b4c2a --- /dev/null +++ b/lib/gitlab/render_timeout.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Gitlab + module RenderTimeout + BACKGROUND = 30.seconds + FOREGROUND = 1.5.seconds + + def self.timeout(background: BACKGROUND, foreground: FOREGROUND, &block) + period = Gitlab::Runtime.sidekiq? ? background : foreground + + Timeout.timeout(period, &block) + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9440a61c7a2..994dfe7ded3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2628,9 +2628,6 @@ msgstr "" msgid "AdminSettings|Delete project after" msgstr "" -msgid "AdminSettings|Deletion protection" -msgstr "" - msgid "AdminSettings|Disable Elasticsearch until indexing completes." msgstr "" @@ -2700,9 +2697,6 @@ msgstr "" msgid "AdminSettings|Inactive project deletion" msgstr "" -msgid "AdminSettings|Keep deleted" -msgstr "" - msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines" msgstr "" @@ -2748,9 +2742,6 @@ msgstr "" msgid "AdminSettings|No required pipeline" msgstr "" -msgid "AdminSettings|None, delete immediately" -msgstr "" - msgid "AdminSettings|Only enable search after installing the plugin, enabling indexing, and recreating the index." msgstr "" @@ -2781,9 +2772,6 @@ msgstr "" msgid "AdminSettings|Restrict group access by IP address. %{link_start}Learn more%{link_end}." msgstr "" -msgid "AdminSettings|Retention period that deleted groups and projects will remain restorable. Personal projects are always deleted immediately. Some groups can opt-out their projects." -msgstr "" - msgid "AdminSettings|Save %{name} limits" msgstr "" @@ -7263,6 +7251,9 @@ msgstr "" msgid "Card number:" msgstr "" +msgid "CascadingSettings|Enforce deletion protection for all subgroups" +msgstr "" + msgid "CascadingSettings|Enforce for all subgroups" msgstr "" @@ -12194,6 +12185,33 @@ msgstr "" msgid "Deletion pending. This project will be deleted on %{date}. Repository and other project resources are read-only." msgstr "" +msgid "DeletionSettings|All projects are deleted immediately." +msgstr "" + +msgid "DeletionSettings|Deletion protection" +msgstr "" + +msgid "DeletionSettings|Keep deleted" +msgstr "" + +msgid "DeletionSettings|Keep deleted projects for %{number} days" +msgstr "" + +msgid "DeletionSettings|Keep deleted projects for 1 day" +msgstr "" + +msgid "DeletionSettings|None, delete immediately" +msgstr "" + +msgid "DeletionSettings|Only administrators can delete projects." +msgstr "" + +msgid "DeletionSettings|Owners and administrators can delete projects." +msgstr "" + +msgid "DeletionSettings|Retention period that deleted groups and projects will remain restorable. Personal projects are always deleted immediately. Some groups can opt-out their projects." +msgstr "" + msgid "Denied" msgstr "" @@ -18279,9 +18297,6 @@ msgstr "" msgid "GroupSettings|Enable customer relations" msgstr "" -msgid "GroupSettings|Enable delayed project deletion" -msgstr "" - msgid "GroupSettings|Export group" msgstr "" @@ -18315,12 +18330,6 @@ msgstr "" msgid "GroupSettings|Prevents group members from being notified if the group is mentioned." msgstr "" -msgid "GroupSettings|Projects will be permanently deleted after a %{waiting_period}-day delay. Inherited by subgroups." -msgstr "" - -msgid "GroupSettings|Projects will be permanently deleted after a %{waiting_period}-day delay. This delay can be %{link_start}customized by an admin%{link_end} in instance settings. Inherited by subgroups." -msgstr "" - msgid "GroupSettings|Select a subgroup to use as the source for custom project templates for this group." msgstr "" @@ -33072,9 +33081,6 @@ msgstr "" msgid "Runners|Revision" msgstr "" -msgid "Runners|Rocket launch illustration" -msgstr "" - msgid "Runners|Runner" msgstr "" @@ -43223,6 +43229,9 @@ msgstr "" msgid "WorkItem|Add a child" msgstr "" +msgid "WorkItem|Are you sure you want to cancel editing?" +msgstr "" + msgid "WorkItem|Are you sure you want to delete the work item? This action cannot be reversed." msgstr "" diff --git a/package.json b/package.json index fc865cab9f9..17cf346e277 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "2.18.0", - "@gitlab/ui": "40.7.1", + "@gitlab/ui": "41.10.0", "@gitlab/visual-review-tools": "1.7.3", "@rails/actioncable": "6.1.4-7", "@rails/ujs": "6.1.4-7", @@ -193,7 +193,7 @@ "web-vitals": "^0.2.4", "webpack": "^4.46.0", "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.2", + "webpack-cli": "^4.10.0", "webpack-stats-plugin": "^0.3.1", "worker-loader": "^2.0.0", "xterm": "3.14.5", diff --git a/qa/qa/tools/delete_projects.rb b/qa/qa/tools/delete_projects.rb index 1287726760b..96ea5f8de7e 100644 --- a/qa/qa/tools/delete_projects.rb +++ b/qa/qa/tools/delete_projects.rb @@ -39,8 +39,12 @@ module QA def delete_projects(project_ids) $stdout.puts "Deleting #{project_ids.length} projects..." project_ids.each do |project_id| - delete_response = delete Runtime::API::Request.new(@api_client, "/projects/#{project_id}").url - dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF\e[0m" + request_url = Runtime::API::Request.new(@api_client, "/projects/#{project_id}").url + path = parse_body(get(request_url))[:path_with_namespace] + $stdout.puts "\nDeleting project #{path}..." + + delete_response = delete(request_url) + dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF - #{delete_response}\e[0m" print dot_or_f end end diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb index 823323810ff..bcbd308ae3f 100644 --- a/qa/qa/tools/delete_subgroups.rb +++ b/qa/qa/tools/delete_subgroups.rb @@ -32,8 +32,12 @@ module QA def delete_subgroups(sub_group_ids) $stdout.puts "Deleting #{sub_group_ids.length} subgroups..." sub_group_ids.each do |subgroup_id| - delete_response = delete Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url - dot_or_f = delete_response.code == 202 ? "\e[32m.\e[0m" : "\e[31mF\e[0m" + request_url = Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url + path = parse_body(get(request_url))[:full_path] + $stdout.puts "\nDeleting subgroup #{path}..." + + delete_response = delete(request_url) + dot_or_f = delete_response.code == 404 ? "\e[32m.\e[0m" : "\e[31mF - #{delete_response}\e[0m" print dot_or_f end end diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb index 0abb8efbf13..f968fb8b26c 100644 --- a/qa/qa/tools/test_resources_handler.rb +++ b/qa/qa/tools/test_resources_handler.rb @@ -162,7 +162,7 @@ module QA if delete_response.code == 202 || delete_response.code == 204 Runtime::Logger.info("Deleting #{resource_info}... SUCCESS") else - Runtime::Logger.info("Deleting #{resource_info}... FAILED") + Runtime::Logger.info("Deleting #{resource_info}... FAILED - #{delete_response}") failures << resource_info end end diff --git a/spec/fixtures/glfm/example_snapshots/html.yml b/spec/fixtures/glfm/example_snapshots/html.yml index 96c01340f8e..b9deadcb4cb 100644 --- a/spec/fixtures/glfm/example_snapshots/html.yml +++ b/spec/fixtures/glfm/example_snapshots/html.yml @@ -3,20 +3,23 @@ canonical: "
foo\tbaz\t\tbim\n
\n" static: "
\n
foo\tbaz\t\tbim
\n\n
" + lang=\"plaintext\" data-canonical-lang=\"\" v-pre=\"true\">foo\tbaz\t\tbim\n\n" wysiwyg: "
foo\tbaz\t\tbim
" 02_01__preliminaries__tabs__002: canonical: "
foo\tbaz\t\tbim\n
\n" static: "
\n
foo\tbaz\t\tbim
\n\n
" + lang=\"plaintext\" data-canonical-lang=\"\" v-pre=\"true\">foo\tbaz\t\tbim\n\n" wysiwyg: "
foo\tbaz\t\tbim
" 02_01__preliminaries__tabs__003: canonical: "
a\ta\nὐ\ta\n
\n" static: "
\n
a\ta\nὐ\ta
\n\n
" + lang=\"plaintext\" data-canonical-lang=\"\" v-pre=\"true\">a\ta\nὐ\ta\n\n" wysiwyg: "
a\ta\nὐ\ta
" 02_01__preliminaries__tabs__004: canonical: | @@ -49,7 +52,7 @@
  • foo

    -
      bar
    +
      bar
  • @@ -65,7 +68,7 @@ static: |-
    -
      foo
    +
      foo
    @@ -83,7 +86,7 @@
    • -
        foo
      +
        foo
    • @@ -97,7 +100,7 @@ static: |-
      -
      foo
      +    
      foo
           bar
      @@ -214,7 +217,7 @@ static: |-
      -
      ***
      +
      ***
      wysiwyg: |- @@ -440,7 +443,7 @@ static: |-
      -
      # foo
      +
      # foo
      wysiwyg: |- @@ -623,7 +626,7 @@
      static: |-
      -
      Foo
      +    
      Foo
           ---
           
           Foo
      @@ -806,7 +809,7 @@
      static: |-
      -
      foo
      +
      foo

      @@ -896,7 +899,7 @@
      static: |-
      -
      a simple
      +    
      a simple
             indented code block
      @@ -950,7 +953,7 @@ static: |-
      -
      <a/>
      +    
      <a/>
           *hi*
           
           - one
      @@ -973,7 +976,7 @@
      static: |-
      -
      chunk1
      +    
      chunk1
           
           chunk2
           
      @@ -994,7 +997,7 @@
         canonical: "
      chunk1\n  \n  chunk2\n
      \n" static: |-
      -
      chunk1
      +    
      chunk1
             
             chunk2
      @@ -1018,7 +1021,7 @@

      bar

      static: |-
      -
      foo
      +
      foo

      bar

      @@ -1037,13 +1040,13 @@

      Heading

      -
      foo
      +
      foo

      Heading

      -
      foo
      +
      foo

      @@ -1056,7 +1059,7 @@
      static: |-
      -
          foo
      +    
          foo
           bar
      @@ -1069,7 +1072,7 @@
      static: |-
      -
      foo
      +
      foo
      wysiwyg: |- @@ -1078,7 +1081,7 @@ canonical: "
      foo  \n
      \n" static: |-
      -
      foo  
      +
      foo  
      wysiwyg: |- @@ -1090,7 +1093,7 @@
      static: |-
      -
      <
      +    
      <
            >
      @@ -1104,7 +1107,7 @@ static: |-
      -
      <
      +    
      <
            >
      @@ -1125,7 +1128,7 @@ static: |-
      -
      aaa
      +    
      aaa
           ~~~
      @@ -1139,7 +1142,7 @@ static: |-
      -
      aaa
      +    
      aaa
           ```
      @@ -1153,7 +1156,7 @@ static: |-
      -
      aaa
      +    
      aaa
           ```
      @@ -1167,7 +1170,7 @@ static: |-
      -
      aaa
      +    
      aaa
           ~~~
      @@ -1179,7 +1182,7 @@
      static: |-
      -
      +
      wysiwyg: |- @@ -1192,7 +1195,7 @@
      static: |-
      -
      
      +    
      
           ```
           aaa
      @@ -1211,7 +1214,7 @@ static: |-
      -
      aaa
      +
      aaa
      @@ -1222,7 +1225,7 @@ canonical: "
      \n  \n
      \n" static: |-
      -
      
      +    
      
             
      @@ -1234,7 +1237,7 @@
      static: |-
      -
      +
      wysiwyg: |- @@ -1246,7 +1249,7 @@
      static: |-
      -
      aaa
      +    
      aaa
           aaa
      @@ -1261,7 +1264,7 @@ static: |-
      -
      aaa
      +    
      aaa
           aaa
           aaa
      @@ -1278,7 +1281,7 @@
      static: |-
      -
      aaa
      +    
      aaa
            aaa
           aaa
      @@ -1295,7 +1298,7 @@
      static: |-
      -
      ```
      +    
      ```
           aaa
           ```
      @@ -1310,7 +1313,7 @@
      static: |-
      -
      aaa
      +
      aaa
      wysiwyg: |- @@ -1321,7 +1324,7 @@
      static: |-
      -
      aaa
      +
      aaa
      wysiwyg: |- @@ -1333,7 +1336,7 @@ static: |-
      -
      aaa
      +    
      aaa
               ```
      @@ -1357,7 +1360,7 @@ static: |-
      -
      aaa
      +    
      aaa
           ~~~ ~~
      @@ -1373,7 +1376,7 @@ static: |-

      foo

      -
      bar
      +
      bar

      baz

      @@ -1389,7 +1392,7 @@

      foo

      -
      bar
      +
      bar

      @@ -1435,7 +1438,7 @@
      static: |-
      -
      +
      wysiwyg: |- @@ -1456,7 +1459,7 @@
      static: |-
      -
      foo
      +
      foo
      wysiwyg: |- @@ -1467,7 +1470,7 @@ static: |-
      -
      ``` aaa
      +
      ``` aaa
      wysiwyg: |- @@ -1730,7 +1733,7 @@

      okay

      static: |-
      -
      
      +    
      
           import Text.HTML.TagSoup
           
           main :: IO ()
      @@ -1932,8 +1935,8 @@
           
      static: " \n
      \n
      <!--
      -    foo -->
      \n\n
      " + lang=\"plaintext\" data-canonical-lang=\"\" v-pre=\"true\"><!-- foo -->
      \n\n
      " wysiwyg: |- Error - check implementation: Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. @@ -1945,7 +1948,7 @@ static: |2-
      -
      <div>
      +
      <div>
      @@ -2051,7 +2054,7 @@
      -
      <td>
      +    
      <td>
             Hi
           </td>
      @@ -2220,7 +2223,7 @@

      [foo]

      static: |-
      -
      [foo]: /url "title"
      +
      [foo]: /url "title"

      [foo]

      @@ -2233,7 +2236,7 @@

      [foo]

      static: |-
      -
      [foo]: /url
      +
      [foo]: /url

      [foo]

      @@ -2387,7 +2390,7 @@

      bbb

      static: |-
      -
      aaa
      +
      aaa

      bbb

      @@ -2737,7 +2740,7 @@
      static: |-
      -
      > # Foo
      +    
      > # Foo
           > bar
           > baz
      @@ -2825,12 +2828,12 @@ static: |-
      -
      foo
      +
      foo
      -
      bar
      +
      bar
      wysiwyg: |- @@ -2845,13 +2848,13 @@ static: |-
      -
      +

      foo

      -
      +
      wysiwyg: |- @@ -3074,7 +3077,7 @@ static: |-
      -
      code
      +
      code
      @@ -3096,7 +3099,7 @@

      A paragraph with two lines.

      -
      indented code
      +
      indented code
      @@ -3124,7 +3127,7 @@

      A paragraph with two lines.

      -
      indented code
      +
      indented code
      @@ -3177,7 +3180,7 @@
    • one
    • -
       two
      +
       two
      wysiwyg: |- @@ -3289,7 +3292,7 @@
    • foo

      -
      bar
      +
      bar

      baz

      @@ -3317,7 +3320,7 @@
    • Foo

      -
      bar
      +    
      bar
           
           
           baz
      @@ -3391,7 +3394,7 @@
    • foo

      -
      bar
      +
      bar
    • @@ -3412,7 +3415,7 @@
    • foo

      -
      bar
      +
      bar
    • @@ -3428,12 +3431,12 @@
      static: |-
      -
      indented code
      +
      indented code

      paragraph

      -
      more code
      +
      more code
      wysiwyg: |- @@ -3453,12 +3456,12 @@
      1. -
        indented code
        +
        indented code

        paragraph

        -
        more code
        +
        more code
      2. @@ -3480,12 +3483,12 @@
        1. -
           indented code
          +
           indented code

          paragraph

          -
          more code
          +
          more code
        2. @@ -3549,13 +3552,13 @@
        3. foo
        4. -
          bar
          +
          bar
        5. -
          baz
          +
          baz
        6. @@ -3680,7 +3683,7 @@

          A paragraph with two lines.

          -
          indented code
          +
          indented code
          @@ -3710,7 +3713,7 @@

          A paragraph with two lines.

          -
          indented code
          +
          indented code
          @@ -3740,7 +3743,7 @@

          A paragraph with two lines.

          -
          indented code
          +
          indented code
          @@ -3762,7 +3765,7 @@
    • static: |-
      -
      1.  A paragraph
      +    
      1.  A paragraph
               with two lines.
           
                   indented code
      @@ -3796,7 +3799,7 @@
           

      A paragraph with two lines.

      -
      indented code
      +
      indented code
      @@ -4245,7 +4248,7 @@
      -
      code
      +
      code
      wysiwyg: |- @@ -4343,7 +4346,7 @@
      -
      3. c
      +
      3. c
      wysiwyg: |- @@ -4472,7 +4475,7 @@
    • a
    • -
      b
      +    
      b
           
           
      @@ -4551,7 +4554,7 @@

      b

    • -
      c
      +
      c
      @@ -4602,7 +4605,7 @@
      1. -
        foo
        +
        foo

        bar

        @@ -4749,7 +4752,7 @@
      static: |-
      -
      \[\]
      +
      \[\]
      wysiwyg: |- @@ -4760,7 +4763,7 @@
      static: |-
      -
      \[\]
      +
      \[\]
      wysiwyg: |- @@ -4799,7 +4802,7 @@ static: |-
      -
      foo
      +
      foo
      wysiwyg: |- @@ -4888,7 +4891,7 @@ static: |-
      -
      foo
      +
      foo
      wysiwyg: |- @@ -4906,7 +4909,7 @@ static: |-
      -
      f&ouml;f&ouml;
      +
      f&ouml;f&ouml;
      wysiwyg: |- diff --git a/spec/fixtures/markdown/markdown_golden_master_examples.yml b/spec/fixtures/markdown/markdown_golden_master_examples.yml index e1fd246b2d5..a1ad88ef69c 100644 --- a/spec/fixtures/markdown/markdown_golden_master_examples.yml +++ b/spec/fixtures/markdown/markdown_golden_master_examples.yml @@ -290,7 +290,7 @@ -- name: code_block +- name: code_block_javascript markdown: |- ```javascript console.log('hello world') @@ -301,6 +301,28 @@
      +- name: code_block_plaintext + markdown: |- + ``` + plaintext + ``` + html: |- +
      +
        plaintext
      + +
      + +- name: code_block_unknown + markdown: |- + ```foobar + custom_language = >> this << + ``` + html: |- +
      +
        custom_language = >> this <<
      + +
      + - name: color_chips markdown: |- - `#F00` diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js new file mode 100644 index 00000000000..8017c46dea8 --- /dev/null +++ b/spec/frontend/work_items/components/work_item_description_spec.js @@ -0,0 +1,222 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { mockTracking } from 'helpers/tracking_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { updateDraft } from '~/lib/utils/autosave'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import WorkItemDescription from '~/work_items/components/work_item_description.vue'; +import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; +import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; +import updateWorkItemWidgetsMutation from '~/work_items/graphql/update_work_item_widgets.mutation.graphql'; +import { + updateWorkItemWidgetsResponse, + workItemResponseFactory, + workItemQueryResponse, +} from '../mock_data'; + +jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => { + return { + confirmAction: jest.fn(), + }; +}); +jest.mock('~/lib/utils/autosave'); + +const workItemId = workItemQueryResponse.data.workItem.id; + +describe('WorkItemDescription', () => { + let wrapper; + + Vue.use(VueApollo); + + const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemWidgetsResponse); + + const findEditButton = () => wrapper.find('[data-testid="edit-description"]'); + const findMarkdownField = () => wrapper.findComponent(MarkdownField); + + const editDescription = (newText) => wrapper.find('textarea').setValue(newText); + + const clickCancel = () => wrapper.find('[data-testid="cancel"]').vm.$emit('click'); + const clickSave = () => wrapper.find('[data-testid="save-description"]').vm.$emit('click', {}); + + const createComponent = async ({ + mutationHandler = mutationSuccessHandler, + canUpdate = true, + isEditing = false, + } = {}) => { + const workItemResponse = workItemResponseFactory({ canUpdate }); + const workItemResponseHandler = jest.fn().mockResolvedValue(workItemResponse); + + const { id } = workItemQueryResponse.data.workItem; + wrapper = shallowMount(WorkItemDescription, { + apolloProvider: createMockApollo([ + [workItemQuery, workItemResponseHandler], + [updateWorkItemWidgetsMutation, mutationHandler], + ]), + propsData: { + workItemId: id, + }, + provide: { + fullPath: '/group/project', + }, + stubs: { + MarkdownField, + }, + }); + + await waitForPromises(); + + if (isEditing) { + findEditButton().vm.$emit('click'); + + await nextTick(); + } + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Edit button', () => { + it('is not visible when canUpdate = false', async () => { + await createComponent({ + canUpdate: false, + }); + + expect(findEditButton().exists()).toBe(false); + }); + + it('toggles edit mode', async () => { + await createComponent({ + canUpdate: true, + }); + + findEditButton().vm.$emit('click'); + + await nextTick(); + + expect(findMarkdownField().exists()).toBe(true); + }); + }); + + describe('editing description', () => { + it('cancels when clicking cancel', async () => { + await createComponent({ + isEditing: true, + }); + + clickCancel(); + + await nextTick(); + + expect(confirmAction).not.toHaveBeenCalled(); + expect(findMarkdownField().exists()).toBe(false); + }); + + it('prompts for confirmation when clicking cancel after changes', async () => { + await createComponent({ + isEditing: true, + }); + + editDescription('updated desc'); + + clickCancel(); + + await nextTick(); + + expect(confirmAction).toHaveBeenCalled(); + }); + + it('calls update widgets mutation', async () => { + await createComponent({ + isEditing: true, + }); + + editDescription('updated desc'); + + clickSave(); + + await waitForPromises(); + + expect(mutationSuccessHandler).toHaveBeenCalledWith({ + input: { + id: workItemId, + descriptionWidget: { + description: 'updated desc', + }, + }, + }); + }); + + it('tracks editing description', async () => { + await createComponent({ + isEditing: true, + markdownPreviewPath: '/preview', + }); + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + clickSave(); + + await waitForPromises(); + + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', { + category: TRACKING_CATEGORY_SHOW, + label: 'item_description', + property: 'type_Task', + }); + }); + + it('emits error when mutation returns error', async () => { + const error = 'eror'; + + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockResolvedValue({ + data: { + workItemUpdateWidgets: { + workItem: {}, + errors: [error], + }, + }, + }), + }); + + editDescription('updated desc'); + + clickSave(); + + await waitForPromises(); + + expect(wrapper.emitted('error')).toEqual([[error]]); + }); + + it('emits error when mutation fails', async () => { + const error = 'eror'; + + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockRejectedValue(new Error(error)), + }); + + editDescription('updated desc'); + + clickSave(); + + await waitForPromises(); + + expect(wrapper.emitted('error')).toEqual([[error]]); + }); + + it('autosaves description', async () => { + await createComponent({ + isEditing: true, + }); + + editDescription('updated desc'); + + expect(updateDraft).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index acb6bf178dc..2bbbfa476a6 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -53,6 +53,47 @@ export const updateWorkItemMutationResponse = { }, }; +export const workItemResponseFactory = ({ canUpdate } = {}) => ({ + data: { + workItem: { + __typename: 'WorkItem', + id: 'gid://gitlab/WorkItem/1', + title: 'Updated title', + state: 'OPEN', + description: 'description', + workItemType: { + __typename: 'WorkItemType', + id: 'gid://gitlab/WorkItems::Type/5', + name: 'Task', + }, + userPermissions: { + deleteWorkItem: false, + updateWorkItem: canUpdate, + }, + widgets: [ + { + __typename: 'WorkItemWidgetDescription', + type: 'DESCRIPTION', + description: 'some **great** text', + descriptionHtml: + '

      some great text

      ', + }, + ], + }, + }, +}); + +export const updateWorkItemWidgetsResponse = { + data: { + workItemUpdateWidgets: { + workItem: { + id: 1234, + }, + errors: [], + }, + }, +}; + export const projectWorkItemTypesQueryResponse = { data: { workspace: { diff --git a/spec/frontend/work_items/pages/work_item_root_spec.js b/spec/frontend/work_items/pages/work_item_root_spec.js index 61af6f316da..3c5da94114e 100644 --- a/spec/frontend/work_items/pages/work_item_root_spec.js +++ b/spec/frontend/work_items/pages/work_item_root_spec.js @@ -11,6 +11,7 @@ import deleteWorkItem from '~/work_items/graphql/delete_work_item.mutation.graph import { deleteWorkItemResponse, deleteWorkItemFailureResponse } from '../mock_data'; jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), visitUrl: jest.fn(), })); diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index a7e657f2636..8a7a6d003f4 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -467,6 +467,33 @@ FooBar end end + context 'when rendering takes too long' do + before do + stub_const("MarkupHelper::RENDER_TIMEOUT", 0.1) + allow(Gitlab::OtherMarkup).to receive(:render) { sleep(0.2) } + end + + it 'times out' do + expect(Gitlab::RenderTimeout).to receive(:timeout).and_call_original + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(Timeout::Error), + project_id: project.id, file_name: file_name + ) + + subject + end + + context 'when markup_rendering_timeout is disabled' do + it 'waits until the execution completes' do + stub_feature_flags(markup_rendering_timeout: false) + + expect(Gitlab::RenderTimeout).not_to receive(:timeout) + + subject + end + end + end + context 'when file is a markdown file' do let(:file_name) { 'foo.md' } diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 16c958ec10b..33adca0ddfc 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as plaintext" do result = filter('
      def fun end
      ') - expect(result.to_html.delete("\n")).to eq('
      def fun end
      ') + expect(result.to_html.delete("\n")).to eq('
      def fun end
      ') end include_examples "XSS prevention", "" @@ -59,7 +59,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as plaintext" do result = filter('
      This is a test
      ') - expect(result.to_html.delete("\n")).to eq('
      This is a test
      ') + expect(result.to_html.delete("\n")).to eq('
      This is a test
      ') end include_examples "XSS prevention", "gnuplot" @@ -130,13 +130,13 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "includes it in the highlighted code block" do result = filter('
      This is a test
      ') - expect(result.to_html.delete("\n")).to eq('
      This is a test
      ') + expect(result.to_html.delete("\n")).to eq('
      This is a test
      ') end it "escape sourcepos metadata to prevent XSS" do result = filter('
      ') - expect(result.to_html.delete("\n")).to eq('
      ') + expect(result.to_html.delete("\n")).to eq('
      ') end end @@ -150,7 +150,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as plaintext" do result = filter('
      This is a test
      ') - expect(result.to_html.delete("\n")).to eq('
      This is a test
      ') + expect(result.to_html.delete("\n")).to eq('
      This is a test
      ') end include_examples "XSS prevention", "ruby" diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 66da5d86ed4..bfea1315d90 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -94,7 +94,7 @@ module Gitlab # Move this test back to the items hash when removing `use_cmark_renderer` feature flag. it "does not convert dangerous fenced code with inline script into HTML" do input = '```mypre">' - output = "
      \n
      \n
      \n
      \n\n
      \n
      \n
      " + output = "
      \n
      \n
      \n
      \n\n
      \n
      \n
      " expect(render(input, context)).to include(output) end @@ -360,7 +360,7 @@ module Gitlab
      -
      console.log('hello world')
      +
      console.log('hello world')
      @@ -390,7 +390,7 @@ module Gitlab
      class.cpp
      -
      #include <stdio.h>
      +            
      #include <stdio.h>
                   
                   for (int i = 0; i < 5; i++) {
                     std::cout<<"*"<<std::endl;
      diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
      index b617da6b157..c38684a6dc3 100644
      --- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
      +++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
      @@ -66,7 +66,7 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
       
           context 'timeout' do
             it 'utilizes timeout for web' do
      -        expect(Timeout).to receive(:timeout).with(described_class::RENDERED_TIMEOUT_FOREGROUND).and_call_original
      +        expect(Timeout).to receive(:timeout).with(Gitlab::RenderTimeout::FOREGROUND).and_call_original
       
               nb_file.diff
             end
      diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
      index 65d8c59fea7..537e59d91c3 100644
      --- a/spec/lib/gitlab/highlight_spec.rb
      +++ b/spec/lib/gitlab/highlight_spec.rb
      @@ -124,27 +124,14 @@ RSpec.describe Gitlab::Highlight do
           context 'timeout' do
             subject(:highlight) { described_class.new('file.rb', 'begin', language: 'ruby').highlight('Content') }
       
      -      it 'utilizes timeout for web' do
      -        expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_FOREGROUND).and_call_original
      -
      -        highlight
      -      end
      -
             it 'falls back to plaintext on timeout' do
               allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
      -        expect(Timeout).to receive(:timeout).and_raise(Timeout::Error)
      +        expect(Gitlab::RenderTimeout).to receive(:timeout).and_raise(Timeout::Error)
       
               expect(Rouge::Lexers::PlainText).to receive(:lex).and_call_original
       
               highlight
             end
      -
      -      it 'utilizes longer timeout for sidekiq' do
      -        allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
      -        expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_BACKGROUND).and_call_original
      -
      -        highlight
      -      end
           end
         end
       end
      diff --git a/spec/lib/gitlab/render_timeout_spec.rb b/spec/lib/gitlab/render_timeout_spec.rb
      new file mode 100644
      index 00000000000..f322d71867b
      --- /dev/null
      +++ b/spec/lib/gitlab/render_timeout_spec.rb
      @@ -0,0 +1,25 @@
      +# frozen_string_literal: true
      +
      +require 'spec_helper'
      +
      +RSpec.describe Gitlab::RenderTimeout do
      +  def expect_timeout(period)
      +    block = proc {}
      +
      +    expect(Timeout).to receive(:timeout).with(period) do |_, &block|
      +      expect(block).to eq(block)
      +    end
      +
      +    described_class.timeout(&block)
      +  end
      +
      +  it 'utilizes timeout for web' do
      +    expect_timeout(described_class::FOREGROUND)
      +  end
      +
      +  it 'utilizes longer timeout for sidekiq' do
      +    allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
      +
      +    expect_timeout(described_class::BACKGROUND)
      +  end
      +end
      diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
      index dfb625abc1b..3050cf6000e 100644
      --- a/spec/policies/project_policy_spec.rb
      +++ b/spec/policies/project_policy_spec.rb
      @@ -469,21 +469,42 @@ RSpec.describe ProjectPolicy do
           let!(:owner_of_different_thing) { create(:user) }
           let(:stranger) { create(:user) }
       
      -    shared_examples 'owner access for personal and group projects' do
      +    context 'personal project' do
      +      let!(:project) { create(:project) }
      +      let!(:project2) { create(:project) }
      +
             before do
      -        stub_feature_flags(faster_owner_access: faster_owner_access_enabled)
      +        project.add_guest(guest)
      +        project.add_reporter(reporter)
      +        project.add_developer(developer)
      +        project.add_maintainer(maintainer)
      +        project2.add_owner(owner_of_different_thing)
             end
       
      -      context 'personal project' do
      -        let!(:project) { create(:project) }
      -        let!(:project2) { create(:project) }
      +      it 'allows owner access', :aggregate_failures do
      +        expect(described_class.new(owner_of_different_thing, project)).to be_disallowed(:owner_access)
      +        expect(described_class.new(stranger, project)).to be_disallowed(:owner_access)
      +        expect(described_class.new(guest, project)).to be_disallowed(:owner_access)
      +        expect(described_class.new(reporter, project)).to be_disallowed(:owner_access)
      +        expect(described_class.new(developer, project)).to be_disallowed(:owner_access)
      +        expect(described_class.new(maintainer, project)).to be_disallowed(:owner_access)
      +        expect(described_class.new(project.owner, project)).to be_allowed(:owner_access)
      +      end
      +    end
       
      +    context 'group project' do
      +      let(:group) { create(:group) }
      +      let!(:group2) { create(:group) }
      +      let!(:project) { create(:project, group: group) }
      +
      +      context 'group members' do
               before do
      -          project.add_guest(guest)
      -          project.add_reporter(reporter)
      -          project.add_developer(developer)
      -          project.add_maintainer(maintainer)
      -          project2.add_owner(owner_of_different_thing)
      +          group.add_guest(guest)
      +          group.add_reporter(reporter)
      +          group.add_developer(developer)
      +          group.add_maintainer(maintainer)
      +          group.add_owner(owner_user)
      +          group2.add_owner(owner_of_different_thing)
               end
       
               it 'allows owner access', :aggregate_failures do
      @@ -493,48 +514,9 @@ RSpec.describe ProjectPolicy do
                 expect(described_class.new(reporter, project)).to be_disallowed(:owner_access)
                 expect(described_class.new(developer, project)).to be_disallowed(:owner_access)
                 expect(described_class.new(maintainer, project)).to be_disallowed(:owner_access)
      -          expect(described_class.new(project.owner, project)).to be_allowed(:owner_access)
      +          expect(described_class.new(owner_user, project)).to be_allowed(:owner_access)
               end
             end
      -
      -      context 'group project' do
      -        let(:group) { create(:group) }
      -        let!(:group2) { create(:group) }
      -        let!(:project) { create(:project, group: group) }
      -
      -        context 'group members' do
      -          before do
      -            group.add_guest(guest)
      -            group.add_reporter(reporter)
      -            group.add_developer(developer)
      -            group.add_maintainer(maintainer)
      -            group.add_owner(owner_user)
      -            group2.add_owner(owner_of_different_thing)
      -          end
      -
      -          it 'allows owner access', :aggregate_failures do
      -            expect(described_class.new(owner_of_different_thing, project)).to be_disallowed(:owner_access)
      -            expect(described_class.new(stranger, project)).to be_disallowed(:owner_access)
      -            expect(described_class.new(guest, project)).to be_disallowed(:owner_access)
      -            expect(described_class.new(reporter, project)).to be_disallowed(:owner_access)
      -            expect(described_class.new(developer, project)).to be_disallowed(:owner_access)
      -            expect(described_class.new(maintainer, project)).to be_disallowed(:owner_access)
      -            expect(described_class.new(owner_user, project)).to be_allowed(:owner_access)
      -          end
      -        end
      -      end
      -    end
      -
      -    context 'when faster_owner_access feature is enabled' do
      -      let(:faster_owner_access_enabled) { true }
      -
      -      it_behaves_like 'owner access for personal and group projects'
      -    end
      -
      -    context 'when faster_owner_access feature is not enabled' do
      -      let(:faster_owner_access_enabled) { false }
      -
      -      it_behaves_like 'owner access for personal and group projects'
           end
         end
       
      diff --git a/spec/services/notification_recipients/build_service_spec.rb b/spec/services/notification_recipients/build_service_spec.rb
      index b348d95ecfc..899d23ec641 100644
      --- a/spec/services/notification_recipients/build_service_spec.rb
      +++ b/spec/services/notification_recipients/build_service_spec.rb
      @@ -15,7 +15,7 @@ RSpec.describe NotificationRecipients::BuildService do
           shared_examples 'no N+1 queries' do
             it 'avoids N+1 queries', :request_store do
               # existing N+1 due to multiple users having to be looked up in the project_authorizations table
      -        threshold = Feature.enabled?(:faster_owner_access) && project.private? ? 1 : 0
      +        threshold = project.private? ? 1 : 0
       
               create_user
       
      @@ -31,42 +31,33 @@ RSpec.describe NotificationRecipients::BuildService do
             end
           end
       
      -    [true, false].each do |value|
      -      context "when faster_owner_access feature is #{value ? 'enabled' : 'not enabled'}" do
      +    context 'when there are multiple watchers' do
      +      def create_user
      +        watcher = create(:user)
      +        create(:notification_setting, source: project, user: watcher, level: :watch)
      +
      +        other_projects.each do |other_project|
      +          create(:notification_setting, source: other_project, user: watcher, level: :watch)
      +        end
      +      end
      +
      +      include_examples 'no N+1 queries'
      +    end
      +
      +    context 'when there are multiple subscribers' do
      +      def create_user
      +        subscriber = create(:user)
      +        issue.subscriptions.create!(user: subscriber, project: project, subscribed: true)
      +      end
      +
      +      include_examples 'no N+1 queries'
      +
      +      context 'when the project is private' do
               before do
      -          # test both feature flag values
      -          stub_feature_flags(faster_owner_access: value)
      +          project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
               end
       
      -        context 'when there are multiple watchers' do
      -          def create_user
      -            watcher = create(:user)
      -            create(:notification_setting, source: project, user: watcher, level: :watch)
      -
      -            other_projects.each do |other_project|
      -              create(:notification_setting, source: other_project, user: watcher, level: :watch)
      -            end
      -          end
      -
      -          include_examples 'no N+1 queries'
      -        end
      -
      -        context 'when there are multiple subscribers' do
      -          def create_user
      -            subscriber = create(:user)
      -            issue.subscriptions.create!(user: subscriber, project: project, subscribed: true)
      -          end
      -
      -          include_examples 'no N+1 queries'
      -
      -          context 'when the project is private' do
      -            before do
      -              project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
      -            end
      -
      -            include_examples 'no N+1 queries'
      -          end
      -        end
      +        include_examples 'no N+1 queries'
             end
           end
         end
      @@ -79,7 +70,7 @@ RSpec.describe NotificationRecipients::BuildService do
           shared_examples 'no N+1 queries' do
             it 'avoids N+1 queries', :request_store do
               # existing N+1 due to multiple users having to be looked up in the project_authorizations table
      -        threshold = Feature.enabled?(:faster_owner_access) && project.private? ? 1 : 0
      +        threshold = project.private? ? 1 : 0
       
               create_user
       
      @@ -95,42 +86,33 @@ RSpec.describe NotificationRecipients::BuildService do
             end
           end
       
      -    [true, false].each do |value|
      -      context "when faster_owner_access feature is #{value ? 'enabled' : 'not enabled'}" do
      +    context 'when there are multiple watchers' do
      +      def create_user
      +        watcher = create(:user)
      +        create(:notification_setting, source: project, user: watcher, level: :watch)
      +
      +        other_projects.each do |other_project|
      +          create(:notification_setting, source: other_project, user: watcher, level: :watch)
      +        end
      +      end
      +
      +      include_examples 'no N+1 queries'
      +    end
      +
      +    context 'when there are multiple subscribers' do
      +      def create_user
      +        subscriber = create(:user)
      +        merge_request.subscriptions.create!(user: subscriber, project: project, subscribed: true)
      +      end
      +
      +      include_examples 'no N+1 queries'
      +
      +      context 'when the project is private' do
               before do
      -          # test both feature flag values
      -          stub_feature_flags(faster_owner_access: value)
      +          project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
               end
       
      -        context 'when there are multiple watchers' do
      -          def create_user
      -            watcher = create(:user)
      -            create(:notification_setting, source: project, user: watcher, level: :watch)
      -
      -            other_projects.each do |other_project|
      -              create(:notification_setting, source: other_project, user: watcher, level: :watch)
      -            end
      -          end
      -
      -          include_examples 'no N+1 queries'
      -        end
      -
      -        context 'when there are multiple subscribers' do
      -          def create_user
      -            subscriber = create(:user)
      -            merge_request.subscriptions.create!(user: subscriber, project: project, subscribed: true)
      -          end
      -
      -          include_examples 'no N+1 queries'
      -
      -          context 'when the project is private' do
      -            before do
      -              project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
      -            end
      -
      -            include_examples 'no N+1 queries'
      -          end
      -        end
      +        include_examples 'no N+1 queries'
             end
           end
         end
      diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb
      index 85e4e2b5300..56993fc27b7 100644
      --- a/spec/support/helpers/gitaly_setup.rb
      +++ b/spec/support/helpers/gitaly_setup.rb
      @@ -9,6 +9,7 @@
       require 'securerandom'
       require 'socket'
       require 'logger'
      +require 'fileutils'
       require 'bundler'
       
       module GitalySetup
      diff --git a/spec/support/shared_examples/features/cascading_settings_shared_examples.rb b/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
      index 395f4fc54e0..cb80751ff49 100644
      --- a/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
      +++ b/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
      @@ -6,7 +6,8 @@ RSpec.shared_examples 'a cascading setting' do
             visit group_path
       
             page.within form_group_selector do
      -        find(setting_field_selector).check
      +        enable_setting.call
      +
               find('[data-testid="enforce-for-all-subgroups-checkbox"]').check
             end
       
      diff --git a/yarn.lock b/yarn.lock
      index a23990bc226..bd339f51099 100644
      --- a/yarn.lock
      +++ b/yarn.lock
      @@ -1048,15 +1048,15 @@
         resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.18.0.tgz#aafff929bc5365f7cad736b6d061895b3f9aa381"
         integrity sha512-Okbm4dAAf/aiaRojUT57yfqY/TVka/zAXN4T+hOx/Yho6wUT2eAJ8CcFpctPdt3kUNM4bHU2CZYoGqklbtXkmg==
       
      -"@gitlab/ui@40.7.1":
      -  version "40.7.1"
      -  resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-40.7.1.tgz#e6595c5cc37d994e3f0ba780626fbf4e8174df2a"
      -  integrity sha512-u100mDpdI7RfNVcAYi8n0RRH2FfIiYuMVgt5jPrQ7AAL+QrwLAkqfBZtkT9pSMpycBuuQsxSMHJK5FlnXum46g==
      +"@gitlab/ui@41.10.0":
      +  version "41.10.0"
      +  resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-41.10.0.tgz#20c6475b5b8e47e4eda97d2d76d851191c981251"
      +  integrity sha512-ANxKD7YGSHTPphZE/oGrvyMraLUrC/cll8xGGizipPFo9pza9krobQfBt1tfLMs7DLSbtdmPoIpFjrOLaYM4NA==
         dependencies:
           "@popperjs/core" "^2.11.2"
           bootstrap-vue "2.20.1"
           dompurify "^2.3.8"
      -    echarts "^5.2.1"
      +    echarts "^5.3.2"
           iframe-resizer "^4.3.2"
           lodash "^4.17.20"
           portal-vue "^2.1.6"
      @@ -2455,22 +2455,22 @@
           "@webassemblyjs/wast-parser" "1.9.0"
           "@xtuc/long" "4.2.2"
       
      -"@webpack-cli/configtest@^1.1.1":
      -  version "1.1.1"
      -  resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356"
      -  integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==
      +"@webpack-cli/configtest@^1.2.0":
      +  version "1.2.0"
      +  resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5"
      +  integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==
       
      -"@webpack-cli/info@^1.4.1":
      -  version "1.4.1"
      -  resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.1.tgz#2360ea1710cbbb97ff156a3f0f24556e0fc1ebea"
      -  integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==
      +"@webpack-cli/info@^1.5.0":
      +  version "1.5.0"
      +  resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1"
      +  integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==
         dependencies:
           envinfo "^7.7.3"
       
      -"@webpack-cli/serve@^1.6.1":
      -  version "1.6.1"
      -  resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe"
      -  integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==
      +"@webpack-cli/serve@^1.7.0":
      +  version "1.7.0"
      +  resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1"
      +  integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==
       
       "@wry/context@^0.6.0":
         version "0.6.1"
      @@ -5143,13 +5143,13 @@ ecc-jsbn@~0.1.1:
           jsbn "~0.1.0"
           safer-buffer "^2.1.0"
       
      -echarts@^5.2.1:
      -  version "5.2.2"
      -  resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.2.2.tgz#ec3c8b2a151cbba71ba3c2c7cf9b2f2047ce4370"
      -  integrity sha512-yxuBfeIH5c+0FsoRP60w4De6omXhA06c7eUYBsC1ykB6Ys2yK5fSteIYWvkJ4xJVLQgCvAdO8C4mN6MLeJpBaw==
      +echarts@^5.3.2:
      +  version "5.3.3"
      +  resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.3.3.tgz#df97b09c4c0e2ffcdfb44acf518d50c50e0b838e"
      +  integrity sha512-BRw2serInRwO5SIwRviZ6Xgm5Lb7irgz+sLiFMmy/HOaf4SQ+7oYqxKzRHAKp4xHQ05AuHw1xvoQWJjDQq/FGw==
         dependencies:
           tslib "2.3.0"
      -    zrender "5.2.1"
      +    zrender "5.3.2"
       
       editions@^1.3.3:
         version "1.3.4"
      @@ -13072,18 +13072,18 @@ webpack-bundle-analyzer@^4.5.0:
           sirv "^1.0.7"
           ws "^7.3.1"
       
      -webpack-cli@^4.9.2:
      -  version "4.9.2"
      -  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d"
      -  integrity sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==
      +webpack-cli@^4.10.0:
      +  version "4.10.0"
      +  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31"
      +  integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==
         dependencies:
           "@discoveryjs/json-ext" "^0.5.0"
      -    "@webpack-cli/configtest" "^1.1.1"
      -    "@webpack-cli/info" "^1.4.1"
      -    "@webpack-cli/serve" "^1.6.1"
      +    "@webpack-cli/configtest" "^1.2.0"
      +    "@webpack-cli/info" "^1.5.0"
      +    "@webpack-cli/serve" "^1.7.0"
           colorette "^2.0.14"
           commander "^7.0.0"
      -    execa "^5.0.0"
      +    cross-spawn "^7.0.3"
           fastest-levenshtein "^1.0.12"
           import-local "^3.0.2"
           interpret "^2.2.0"
      @@ -13501,10 +13501,10 @@ zen-observable@0.8.15:
         resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
         integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
       
      -zrender@5.2.1:
      -  version "5.2.1"
      -  resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.2.1.tgz#5f4bbda915ba6d412b0b19dc2431beaad05417bb"
      -  integrity sha512-M3bPGZuyLTNBC6LiNKXJwSCtglMp8XUEqEBG+2MdICDI3d1s500Y4P0CzldQGsqpRVB7fkvf3BKQQRxsEaTlsw==
      +zrender@5.3.2:
      +  version "5.3.2"
      +  resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.3.2.tgz#f67b11d36d3d020d62411d3bb123eb1c93cccd69"
      +  integrity sha512-8IiYdfwHj2rx0UeIGZGGU4WEVSDEdeVCaIg/fomejg1Xu6OifAL1GVzIPHg2D+MyUkbNgPWji90t0a8IDk+39w==
         dependencies:
           tslib "2.3.0"