diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index b3bba2fdc36..6b2e817bfed 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -53,7 +53,6 @@ /doc/user/application_security/cve_id_request.md @fneill /doc/user/application_security/security_dashboard @fneill /doc/user/application_security/vulnerabilities @fneill -/doc/user/application_security/vulnerability_management @fneill /doc/user/application_security/vulnerability_report @fneill /doc/user/clusters/ @marcia /doc/user/compliance/ @rdickenson @eread @@ -86,7 +85,6 @@ [Docs Create] /doc/administration/file_hooks.md @aqualls -/doc/administration/git_annex.md @aqualls /doc/administration/git_protocol.md @aqualls /doc/administration/invalidate_markdown_cache.md @aqualls /doc/administration/issue_closing_pattern.md @aqualls @@ -179,7 +177,6 @@ /lib/gitlab/background_migration/ @gitlab-org/maintainers/database /ee/lib/ee/gitlab/background_migration/ @gitlab-org/maintainers/database /lib/gitlab/database/ @gitlab-org/maintainers/database -/ee/lib/gitlab/database/ @gitlab-org/maintainers/database /lib/gitlab/sql/ @gitlab-org/maintainers/database /lib/gitlab/github_import/ @gitlab-org/maintainers/database /app/finders/ @gitlab-org/maintainers/database @@ -191,7 +188,6 @@ /.gitlab/ci/ @gl-quality/eng-prod /.gitlab/ci/docs.gitlab-ci.yml @gl-quality/eng-prod @gl-docsteam /.gitlab/ci/releases.gitlab-ci.yml @gl-quality/eng-prod @gitlab-org/delivery -/.gitlab/ci/dast.gitlab-ci.yml @dappelt @ngeorge1 @gl-quality/eng-prod /.gitlab/ci/reports.gitlab-ci.yml @gitlab-com/gl-security/appsec @gl-quality/eng-prod /.gitlab/CODEOWNERS @gl-quality/eng-prod Dangerfile @gl-quality/eng-prod @@ -236,8 +232,6 @@ Dangerfile @gl-quality/eng-prod /ee/app/policies/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team /ee/app/presenters/projects/security/ @gitlab-org/secure/threat-insights-backend-team /ee/lib/api/vulnerabilit*.rb @gitlab-org/secure/threat-insights-backend-team -/ee/lib/gitlab/ci/reports/security/vulnerability_reports_comparer.rb @gitlab-org/secure/threat-insights-backend-team -/ee/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb @gitlab-org/secure/threat-insights-backend-team /ee/spec/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team /ee/spec/policies/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team /ee/spec/presenters/projects/security/ @gitlab-org/secure/threat-insights-backend-team @@ -273,8 +267,6 @@ Dangerfile @gl-quality/eng-prod /spec/lib/gitlab/kubernetes/network_policy_spec.rb @gitlab-org/protect/container-security-backend /ee/app/services/network_policies/** @gitlab-org/protect/container-security-backend /ee/spec/services/network_policies/** @gitlab-org/protect/container-security-backend -/ee/app/controllers/projects/security/waf_anomalies_controller.rb @gitlab-org/protect/container-security-backend -/ee/spec/controllers/projects/security/waf_anomalies_controller_spec.rb @gitlab-org/protect/container-security-backend /app/models/clusters/applications/cilium.rb @gitlab-org/protect/container-security-backend /spec/models/clusters/applications/cilium_spec.rb @gitlab-org/protect/container-security-backend /ee/app/controllers/projects/security/network_policies_controller.rb @gitlab-org/protect/container-security-backend @@ -304,7 +296,7 @@ Dangerfile @gl-quality/eng-prod /lib/gitlab/discussions_diff/ @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor /lib/gitlab/quick_actions/ @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor -/ee/app/models/merge_request.rb @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor +/ee/app/models/ee/merge_request.rb @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor /ee/app/services/merge_requests/ @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor /ee/app/workers/merge_requests/ @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor /ee/app/workers/merge_request_reset_approvals_worker.rb @dskim_gitlab @garyh @patrickbajao @marc_shaw @kerrizor @@ -312,7 +304,6 @@ Dangerfile @gl-quality/eng-prod /app/assets/javascripts/diffs @viktomas @jboyson @iamphill @thomasrandolph /app/assets/javascripts/batch_comments/ @viktomas @jboyson @iamphill @thomasrandolph /app/assets/javascripts/notes @viktomas @jboyson @iamphill @thomasrandolph -/app/assets/javascripts/merge_request @viktomas @jboyson @iamphill @thomasrandolph /app/assets/javascripts/merge_conflicts @viktomas @jboyson @iamphill @thomasrandolph /app/assets/javascripts/mr_notes @viktomas @jboyson @iamphill @thomasrandolph /app/assets/javascripts/mr_popover @viktomas @jboyson @iamphill @thomasrandolph @@ -334,7 +325,6 @@ Dangerfile @gl-quality/eng-prod /lib/gitlab/usage_data_counters/ @gitlab-org/growth/product-intelligence/engineers [Growth Experiments] -/app/assets/javascripts/lib/utils/experimentation.js @gitlab-org/growth/experiment-devs /app/experiments/ @gitlab-org/growth/experiment-devs /app/models/experiment.rb @gitlab-org/growth/experiment-devs /app/models/experiment_subject.rb @gitlab-org/growth/experiment-devs diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 69b25c6adf2..3f26f4ff1ac 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -2613,3 +2613,170 @@ Style/OpenStructUse: - 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb' - 'spec/tooling/rspec_flaky/flaky_example_spec.rb' - 'tooling/rspec_flaky/flaky_example.rb' + +GraphQL/ArgumentName: + Exclude: + - 'ee/app/graphql/mutations/audit_events/external_audit_event_destinations/update.rb' + +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: group_definitions, define_resolver_after_definition +GraphQL/FieldDefinitions: + Exclude: + - 'app/graphql/types/commit_type.rb' + - 'app/graphql/types/group_type.rb' + - 'app/graphql/types/issue_type.rb' + - 'app/graphql/types/label_type.rb' + - 'app/graphql/types/merge_request_type.rb' + - 'app/graphql/types/namespace_type.rb' + - 'app/graphql/types/notes/note_type.rb' + - 'app/graphql/types/project_type.rb' + - 'app/graphql/types/projects/topic_type.rb' + - 'app/graphql/types/release_type.rb' + - 'ee/app/graphql/types/ci/code_quality_degradation_type.rb' + - 'ee/app/graphql/types/epic_type.rb' + - 'ee/app/graphql/types/group_release_stats_type.rb' + - 'ee/app/graphql/types/iteration_type.rb' + - 'ee/app/graphql/types/requirements_management/requirement_type.rb' + +# Cop supports --auto-correct. +GraphQL/FieldHashKey: + Exclude: + - 'app/graphql/types/ci/config/job_type.rb' + - 'app/graphql/types/ci/status_action_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb' + - 'app/graphql/types/packages/helm/dependency_type.rb' + +# Cop supports --auto-correct. +GraphQL/FieldMethod: + Exclude: + - 'app/graphql/types/ci/job_type.rb' + - 'app/graphql/types/merge_request_type.rb' + - 'app/graphql/types/metrics/dashboards/annotation_type.rb' + - 'app/graphql/types/packages/package_details_type.rb' + - 'app/graphql/types/project_type.rb' + - 'ee/app/graphql/types/dast/profile_type.rb' + - 'ee/app/graphql/types/dast_site_validation_type.rb' + - 'ee/app/graphql/types/group_release_stats_type.rb' + - 'ee/app/graphql/types/incident_management/oncall_rotation_type.rb' + +# Cop supports --auto-correct. +GraphQL/OrderedArguments: + Exclude: + - 'app/graphql/mutations/jira_import/start.rb' + - 'app/graphql/mutations/merge_requests/accept.rb' + - 'app/graphql/resolvers/base_issues_resolver.rb' + - 'app/graphql/resolvers/design_management/designs_resolver.rb' + - 'app/graphql/resolvers/design_management/version/design_at_version_resolver.rb' + - 'app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb' + - 'app/graphql/resolvers/design_management/version_in_collection_resolver.rb' + - 'app/graphql/resolvers/group_milestones_resolver.rb' + - 'app/graphql/resolvers/merge_requests_resolver.rb' + - 'app/graphql/resolvers/paginated_tree_resolver.rb' + - 'app/graphql/resolvers/tree_resolver.rb' + - 'app/graphql/resolvers/users/groups_resolver.rb' + - 'app/graphql/types/commit_action_type.rb' + - 'app/graphql/types/diff_paths_input_type.rb' + - 'app/graphql/types/issues/negated_issue_filter_input_type.rb' + - 'app/graphql/types/jira_users_mapping_input_type.rb' + - 'app/graphql/types/notes/diff_image_position_input_type.rb' + - 'app/graphql/types/notes/diff_position_base_input_type.rb' + - 'app/graphql/types/notes/diff_position_input_type.rb' + +# Cop supports --auto-correct. +GraphQL/OrderedFields: + Exclude: + - 'app/graphql/types/board_list_type.rb' + - 'app/graphql/types/ci/analytics_type.rb' + - 'app/graphql/types/ci/ci_cd_setting_type.rb' + - 'app/graphql/types/ci/config/group_type.rb' + - 'app/graphql/types/ci/config/job_type.rb' + - 'app/graphql/types/ci/config/stage_type.rb' + - 'app/graphql/types/ci/detailed_status_type.rb' + - 'app/graphql/types/ci/group_type.rb' + - 'app/graphql/types/ci/job_type.rb' + - 'app/graphql/types/ci/runner_architecture_type.rb' + - 'app/graphql/types/ci/runner_platform_type.rb' + - 'app/graphql/types/ci/runner_type.rb' + - 'app/graphql/types/ci/stage_type.rb' + - 'app/graphql/types/ci/status_action_type.rb' + - 'app/graphql/types/ci/template_type.rb' + - 'app/graphql/types/commit_type.rb' + - 'app/graphql/types/container_expiration_policy_type.rb' + - 'app/graphql/types/container_repository_tag_type.rb' + - 'app/graphql/types/container_repository_type.rb' + - 'app/graphql/types/dependency_proxy/blob_type.rb' + - 'app/graphql/types/dependency_proxy/image_ttl_group_policy_type.rb' + - 'app/graphql/types/dependency_proxy/manifest_type.rb' + - 'app/graphql/types/design_management/design_collection_type.rb' + - 'app/graphql/types/diff_refs_type.rb' + - 'app/graphql/types/diff_stats_summary_type.rb' + - 'app/graphql/types/diff_stats_type.rb' + - 'app/graphql/types/error_tracking/sentry_detailed_error_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_collection_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_frequency_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_stack_trace_context_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb' + - 'app/graphql/types/error_tracking/sentry_error_type.rb' + - 'app/graphql/types/evidence_type.rb' + - 'app/graphql/types/grafana_integration_type.rb' + - 'app/graphql/types/issue_type.rb' + - 'app/graphql/types/jira_import_type.rb' + - 'app/graphql/types/jira_user_type.rb' + - 'app/graphql/types/label_type.rb' + - 'app/graphql/types/merge_request_type.rb' + - 'app/graphql/types/metadata/kas_type.rb' + - 'app/graphql/types/metadata_type.rb' + - 'app/graphql/types/namespace/package_settings_type.rb' + - 'app/graphql/types/namespace_type.rb' + - 'app/graphql/types/notes/diff_position_type.rb' + - 'app/graphql/types/notes/discussion_type.rb' + - 'app/graphql/types/notes/note_type.rb' + - 'app/graphql/types/packages/composer/json_type.rb' + - 'app/graphql/types/packages/composer/metadatum_type.rb' + - 'app/graphql/types/packages/conan/file_metadatum_type.rb' + - 'app/graphql/types/packages/conan/metadatum_type.rb' + - 'app/graphql/types/packages/helm/dependency_type.rb' + - 'app/graphql/types/packages/helm/maintainer_type.rb' + - 'app/graphql/types/packages/helm/metadata_type.rb' + - 'app/graphql/types/packages/maven/metadatum_type.rb' + - 'app/graphql/types/packages/nuget/metadatum_type.rb' + - 'app/graphql/types/packages/package_dependency_link_type.rb' + - 'app/graphql/types/packages/package_file_type.rb' + - 'app/graphql/types/packages/package_tag_type.rb' + - 'app/graphql/types/packages/package_type.rb' + - 'app/graphql/types/project_statistics_type.rb' + - 'app/graphql/types/project_type.rb' + - 'app/graphql/types/projects/services/jira_project_type.rb' + - 'app/graphql/types/release_asset_link_type.rb' + - 'app/graphql/types/release_links_type.rb' + - 'app/graphql/types/release_type.rb' + - 'app/graphql/types/repository_type.rb' + - 'app/graphql/types/root_storage_statistics_type.rb' + - 'app/graphql/types/task_completion_status.rb' + - 'app/graphql/types/tree/blob_type.rb' + - 'app/graphql/types/tree/submodule_type.rb' + - 'app/graphql/types/tree/tree_entry_type.rb' + - 'app/graphql/types/user_callout_type.rb' + - 'app/graphql/types/user_status_type.rb' + - 'ee/app/graphql/types/analytics/devops_adoption/snapshot_type.rb' + - 'ee/app/graphql/types/epic_descendant_count_type.rb' + - 'ee/app/graphql/types/epic_descendant_weight_sum_type.rb' + - 'ee/app/graphql/types/epic_health_status_type.rb' + - 'ee/app/graphql/types/epic_type.rb' + - 'ee/app/graphql/types/geo/geo_node_type.rb' + - 'ee/app/graphql/types/requirements_management/requirement_states_count_type.rb' + - 'ee/app/graphql/types/scan_execution_policy_type.rb' + - 'ee/app/graphql/types/scan_type.rb' + - 'ee/app/graphql/types/scanned_resource_type.rb' + - 'ee/app/graphql/types/security_report_summary_section_type.rb' + - 'ee/app/graphql/types/timebox_report_type.rb' + +# Configuration parameters: Max, CountComments, ExcludedMethods. +GraphQL/ResolverMethodLength: + Exclude: + - 'app/graphql/types/ci/detailed_status_type.rb' + - 'app/graphql/types/ci/runner_type.rb' + - 'app/graphql/types/ci/stage_type.rb' + - 'app/graphql/types/packages/package_type.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index ea65aa7b7ce..c6de3340f58 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -b6dda5d1f7a7e05c34ed0f72f161a46aee536d75 +15bfbf4430778f416e42f931a5bf813e1a9c7fc5 diff --git a/Gemfile b/Gemfile index 87a0cff84c1..da3a5504866 100644 --- a/Gemfile +++ b/Gemfile @@ -197,7 +197,7 @@ gem 'state_machines-activerecord', '~> 0.8.0' gem 'acts-as-taggable-on', '~> 7.0' # Background jobs -gem 'sidekiq', '~> 6.2.2' +gem 'sidekiq', '~> 6.3' gem 'sidekiq-cron', '~> 1.0' gem 'redis-namespace', '~> 1.8.1' gem 'gitlab-sidekiq-fetcher', '0.8.0', require: 'sidekiq-reliable-fetch' @@ -376,7 +376,7 @@ group :development, :test do gem 'spring', '~> 2.1.0' gem 'spring-commands-rspec', '~> 1.0.4' - gem 'gitlab-styles', '~> 6.4.0', require: false + gem 'gitlab-styles', '~> 6.5.0', require: false gem 'haml_lint', '~> 0.36.0', require: false gem 'bundler-audit', '~> 0.7.0.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 270ea4532f9..78b227123ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -490,9 +490,10 @@ GEM openid_connect (~> 1.2) gitlab-sidekiq-fetcher (0.8.0) sidekiq (~> 6.1) - gitlab-styles (6.4.0) + gitlab-styles (6.5.0) rubocop (~> 0.91, >= 0.91.1) rubocop-gitlab-security (~> 0.1.1) + rubocop-graphql (~> 0.10) rubocop-performance (~> 1.9.2) rubocop-rails (~> 2.9) rubocop-rspec (~> 1.44) @@ -1107,6 +1108,8 @@ GEM parser (>= 2.7.1.5) rubocop-gitlab-security (0.1.1) rubocop (>= 0.51) + rubocop-graphql (0.10.3) + rubocop (>= 0.87, < 2) rubocop-performance (1.9.2) rubocop (>= 0.90.0, < 2.0) rubocop-ast (>= 0.4.0) @@ -1175,7 +1178,7 @@ GEM shellany (0.0.1) shoulda-matchers (4.0.1) activesupport (>= 4.2.0) - sidekiq (6.2.2) + sidekiq (6.3.1) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -1477,7 +1480,7 @@ DEPENDENCIES gitlab-net-dns (~> 0.9.1) gitlab-omniauth-openid-connect (~> 0.8.0) gitlab-sidekiq-fetcher (= 0.8.0) - gitlab-styles (~> 6.4.0) + gitlab-styles (~> 6.5.0) gitlab_chronic_duration (~> 0.10.6.2) gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.4.0) @@ -1620,7 +1623,7 @@ DEPENDENCIES sentry-raven (~> 3.1) settingslogic (~> 2.0.9) shoulda-matchers (~> 4.0.1) - sidekiq (~> 6.2.2) + sidekiq (~> 6.3) sidekiq-cron (~> 1.0) simple_po_parser (~> 1.1.2) simplecov (~> 0.18.5) diff --git a/app/assets/images/logos/shimo.svg b/app/assets/images/logos/shimo.svg new file mode 100644 index 00000000000..65bd1cc7167 --- /dev/null +++ b/app/assets/images/logos/shimo.svg @@ -0,0 +1 @@ + diff --git a/app/assets/javascripts/content_editor/extensions/attachment.js b/app/assets/javascripts/content_editor/extensions/attachment.js index 29ee282f2d2..72df1d071d1 100644 --- a/app/assets/javascripts/content_editor/extensions/attachment.js +++ b/app/assets/javascripts/content_editor/extensions/attachment.js @@ -5,9 +5,11 @@ import { handleFileEvent } from '../services/upload_helpers'; export default Extension.create({ name: 'attachment', - defaultOptions: { - uploadsPath: null, - renderMarkdown: null, + addOptions() { + return { + uploadsPath: null, + renderMarkdown: null, + }; }, addCommands() { diff --git a/app/assets/javascripts/content_editor/extensions/audio.js b/app/assets/javascripts/content_editor/extensions/audio.js index 25d4068c93f..ea48ee0cee0 100644 --- a/app/assets/javascripts/content_editor/extensions/audio.js +++ b/app/assets/javascripts/content_editor/extensions/audio.js @@ -2,8 +2,10 @@ import Playable from './playable'; export default Playable.extend({ name: 'audio', - defaultOptions: { - ...Playable.options, - mediaType: 'audio', + addOptions() { + return { + ...this.parent?.(), + mediaType: 'audio', + }; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/html_marks.js b/app/assets/javascripts/content_editor/extensions/html_marks.js index 3abf0e3eee2..9579f3b06f6 100644 --- a/app/assets/javascripts/content_editor/extensions/html_marks.js +++ b/app/assets/javascripts/content_editor/extensions/html_marks.js @@ -31,13 +31,12 @@ const attrs = { export default marks.map((name) => Mark.create({ name, - inclusive: false, - - defaultOptions: { - HTMLAttributes: {}, + addOptions() { + return { + HTMLAttributes: {}, + }; }, - addAttributes() { return (attrs[name] || []).reduce( (acc, attr) => ({ diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js index 837fab0585f..d7fb617f7ee 100644 --- a/app/assets/javascripts/content_editor/extensions/image.js +++ b/app/assets/javascripts/content_editor/extensions/image.js @@ -7,9 +7,11 @@ const resolveImageEl = (element) => element.nodeName === 'IMG' ? element : element.querySelector('img'); export default Image.extend({ - defaultOptions: { - ...Image.options, - inline: true, + addOptions() { + return { + ...this.parent?.(), + inline: true, + }; }, addAttributes() { return { diff --git a/app/assets/javascripts/content_editor/extensions/inline_diff.js b/app/assets/javascripts/content_editor/extensions/inline_diff.js index 22bb1ac072e..f76943a0669 100644 --- a/app/assets/javascripts/content_editor/extensions/inline_diff.js +++ b/app/assets/javascripts/content_editor/extensions/inline_diff.js @@ -3,8 +3,10 @@ import { Mark, markInputRule, mergeAttributes } from '@tiptap/core'; export default Mark.create({ name: 'inlineDiff', - defaultOptions: { - HTMLAttributes: {}, + addOptions() { + return { + HTMLAttributes: {}, + }; }, addAttributes() { diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js index 27bc05dce6f..f9b12f631fe 100644 --- a/app/assets/javascripts/content_editor/extensions/link.js +++ b/app/assets/javascripts/content_editor/extensions/link.js @@ -18,10 +18,13 @@ export const extractHrefFromMarkdownLink = (match) => { }; export default Link.extend({ - defaultOptions: { - ...Link.options, - openOnClick: false, + addOptions() { + return { + ...this.parent?.(), + openOnClick: false, + }; }, + addInputRules() { const markdownLinkSyntaxInputRuleRegExp = /(?:^|\s)\[([\w|\s|-]+)\]\((?.+?)\)$/gm; const urlSyntaxRegExp = /(?:^|\s)(?(?:https?:\/\/|www\.)[\S]+)(?:\s|\n)$/gim; diff --git a/app/assets/javascripts/content_editor/extensions/task_item.js b/app/assets/javascripts/content_editor/extensions/task_item.js index 9b050edcb28..6efef3f8198 100644 --- a/app/assets/javascripts/content_editor/extensions/task_item.js +++ b/app/assets/javascripts/content_editor/extensions/task_item.js @@ -2,9 +2,11 @@ import { TaskItem } from '@tiptap/extension-task-item'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; export default TaskItem.extend({ - defaultOptions: { - nested: true, - HTMLAttributes: {}, + addOptions() { + return { + nested: true, + HTMLAttributes: {}, + }; }, addAttributes() { diff --git a/app/assets/javascripts/content_editor/extensions/video.js b/app/assets/javascripts/content_editor/extensions/video.js index 9923b7c04cd..312e8cd5ff6 100644 --- a/app/assets/javascripts/content_editor/extensions/video.js +++ b/app/assets/javascripts/content_editor/extensions/video.js @@ -2,9 +2,11 @@ import Playable from './playable'; export default Playable.extend({ name: 'video', - defaultOptions: { - ...Playable.options, - mediaType: 'video', - extraElementAttrs: { width: '400' }, + addOptions() { + return { + ...this.parent?.(), + mediaType: 'video', + extraElementAttrs: { width: '400' }, + }; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/word_break.js b/app/assets/javascripts/content_editor/extensions/word_break.js index fa7e02f8cc8..457b7c36564 100644 --- a/app/assets/javascripts/content_editor/extensions/word_break.js +++ b/app/assets/javascripts/content_editor/extensions/word_break.js @@ -7,10 +7,12 @@ export default Node.create({ selectable: false, atom: true, - defaultOptions: { - HTMLAttributes: { - class: 'gl-display-inline-flex gl-px-1 gl-bg-blue-100 gl-rounded-base gl-font-sm', - }, + addOptions() { + return { + HTMLAttributes: { + class: 'gl-display-inline-flex gl-px-1 gl-bg-blue-100 gl-rounded-base gl-font-sm', + }, + }; }, parseHTML() { diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index f404fa4e0e8..7c7127dfa44 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -44,6 +44,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) { let addFileToForm; let updateAttachingMessage; let uploadFile; + let hasPlainText; formTextarea.wrap('
'); formTextarea.on('paste', (event) => handlePaste(event)); @@ -184,7 +185,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) { event.preventDefault(); const text = converter.convertToTableMarkdown(); pasteText(text); - } else { + } else if (!hasPlainText(pasteEvent)) { const fileList = [...clipboardData.files]; fileList.forEach((file) => { if (file.type.indexOf('image') !== -1) { @@ -203,6 +204,11 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) { } }; + hasPlainText = (data) => { + const clipboardDataList = [...data.clipboardData.items]; + return clipboardDataList.some((item) => item.type === 'text/plain'); + }; + pasteText = (text, shouldPad) => { let formattedText = text; if (shouldPad) { diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue index ab04c6a38a5..4491fddf4d9 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_item.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue @@ -185,6 +185,13 @@ export default { :title="__('Confidential')" :aria-label="__('Confidential')" /> + {{ issuable.title }} @@ -202,7 +209,7 @@ export default { {{ reference }} - + diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 7f2082e5b90..013361495c9 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -11,7 +11,7 @@ import { import fuzzaldrinPlus from 'fuzzaldrin-plus'; import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql'; import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql'; -import createFlash from '~/flash'; +import createFlash, { FLASH_TYPES } from '~/flash'; import { TYPE_USER } from '~/graphql_shared/constants'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { ITEM_TYPE } from '~/groups/constants'; @@ -157,6 +157,9 @@ export default { initialEmail: { default: '', }, + isIssueRepositioningDisabled: { + default: false, + }, isProject: { default: false, }, @@ -184,8 +187,13 @@ export default { }, data() { const state = getParameterByName(PARAM_STATE); - const sortKey = getSortKey(getParameterByName(PARAM_SORT)); const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC; + let sortKey = getSortKey(getParameterByName(PARAM_SORT)) || defaultSortKey; + + if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) { + this.showIssueRepositioningMessage(); + sortKey = defaultSortKey; + } return { dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)), @@ -196,7 +204,7 @@ export default { pageInfo: {}, pageParams: getInitialPageParams(sortKey), showBulkEditSidebar: false, - sortKey: sortKey || defaultSortKey, + sortKey, state: state || IssuableStates.Opened, }; }, @@ -611,11 +619,22 @@ export default { }); }, handleSort(sortKey) { + if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) { + this.showIssueRepositioningMessage(); + return; + } + if (this.sortKey !== sortKey) { this.pageParams = getInitialPageParams(sortKey); } this.sortKey = sortKey; }, + showIssueRepositioningMessage() { + createFlash({ + message: this.$options.i18n.issueRepositioningMessage, + type: FLASH_TYPES.NOTICE, + }); + }, toggleBulkEditSidebar(showBulkEditSidebar) { this.showBulkEditSidebar = showBulkEditSidebar; }, diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index da9b96d0e22..468f3f4cd73 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -75,6 +75,9 @@ export const i18n = { editIssues: __('Edit issues'), errorFetchingCounts: __('An error occurred while getting issue counts'), errorFetchingIssues: __('An error occurred while loading issues'), + issueRepositioningMessage: __( + 'Issues are being rebalanced at the moment, so manual reordering is disabled.', + ), jiraIntegrationMessage: s__( 'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.', ), diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js index 59034964afb..77b67cdf763 100644 --- a/app/assets/javascripts/issues_list/index.js +++ b/app/assets/javascripts/issues_list/index.js @@ -129,6 +129,7 @@ export function mountIssuesListApp() { hasMultipleIssueAssigneesFeature, importCsvIssuesPath, initialEmail, + isIssueRepositioningDisabled, isProject, isSignedIn, jiraIntegrationPath, @@ -161,6 +162,7 @@ export function mountIssuesListApp() { hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), hasIterationsFeature: parseBoolean(hasIterationsFeature), hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature), + isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled), isProject: parseBoolean(isProject), isSignedIn: parseBoolean(isSignedIn), jiraIntegrationPath, diff --git a/app/assets/javascripts/issues_list/queries/issue.fragment.graphql b/app/assets/javascripts/issues_list/queries/issue.fragment.graphql index 9c46cb3ef64..07dae3fd756 100644 --- a/app/assets/javascripts/issues_list/queries/issue.fragment.graphql +++ b/app/assets/javascripts/issues_list/queries/issue.fragment.graphql @@ -6,6 +6,7 @@ fragment IssueFragment on Issue { createdAt downvotes dueDate + hidden humanTimeEstimate mergeRequestsCount moved diff --git a/app/assets/javascripts/vue_shared/components/registry/list_item.vue b/app/assets/javascripts/vue_shared/components/registry/list_item.vue index 933a215112b..89de95f8d9a 100644 --- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue @@ -54,7 +54,7 @@ export default { class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1" :class="optionalClasses" > -
+
Admin**. 1. On the left sidebar, select **Settings > General**, then expand **Account and limit**. @@ -18,6 +23,17 @@ You can change the default maximum number of projects that users can create in t If you set **Default projects limit** to 0, users are not allowed to create projects in their users personal namespace. However, projects can still be created in a group. +### Projects limit for a user + +You can edit a specific user, and change the maximum number of projects this user +can create in their personal namespace: + +1. On the top bar, select **Menu > Admin**. +1. On the left sidebar, select **Overview** > **Users**. +1. From the list of users, select a user. +1. Select **Edit**. +1. Increase or decrease the **Projects limit** value. + ## Max attachment size You can change the maximum file size for attachments in comments and replies in GitLab: diff --git a/lib/gitlab/patch/sidekiq_client.rb b/lib/gitlab/patch/sidekiq_client.rb deleted file mode 100644 index 2de13560cce..00000000000 --- a/lib/gitlab/patch/sidekiq_client.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Patch - module SidekiqClient - private - - # This is a copy of https://github.com/mperham/sidekiq/blob/v6.2.2/lib/sidekiq/client.rb#L187-L194 - # but using `conn.pipelined` instead of `conn.multi`. The multi call isn't needed here because in - # the case of scheduled jobs, only one Redis call is made. For other jobs, we don't really need - # the commands to be atomic. - def raw_push(payloads) - @redis_pool.with do |conn| # rubocop:disable Gitlab/ModuleWithInstanceVariables - conn.pipelined do - atomic_push(conn, payloads) - end - end - true - end - end - end -end diff --git a/lib/gitlab/patch/sidekiq_cron_poller.rb b/lib/gitlab/patch/sidekiq_poller.rb similarity index 91% rename from lib/gitlab/patch/sidekiq_cron_poller.rb rename to lib/gitlab/patch/sidekiq_poller.rb index 56ca24c68f5..d4264cec1ab 100644 --- a/lib/gitlab/patch/sidekiq_cron_poller.rb +++ b/lib/gitlab/patch/sidekiq_poller.rb @@ -2,7 +2,7 @@ module Gitlab module Patch - module SidekiqCronPoller + module SidekiqPoller def enqueue Rails.application.reloader.wrap do ::Gitlab::WithRequestStore.with_request_store do diff --git a/lib/gitlab/sidekiq_enq.rb b/lib/gitlab/sidekiq_enq.rb deleted file mode 100644 index de0c00fe561..00000000000 --- a/lib/gitlab/sidekiq_enq.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - class SidekiqEnq - LUA_ZPOPBYSCORE = <<~EOS - local key, now = KEYS[1], ARGV[1] - local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1) - if jobs[1] then - redis.call("zrem", key, jobs[1]) - return jobs[1] - end - EOS - - LUA_ZPOPBYSCORE_SHA = Digest::SHA1.hexdigest(LUA_ZPOPBYSCORE) - - def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = Sidekiq::Scheduled::SETS) - Rails.application.reloader.wrap do - ::Gitlab::WithRequestStore.with_request_store do - if Feature.enabled?(:atomic_sidekiq_scheduler, default_enabled: :yaml) - atomic_find_jobs_and_enqueue(now, sorted_sets) - else - find_jobs_and_enqueue(now, sorted_sets) - end - - ensure - ::Gitlab::Database::LoadBalancing.release_hosts - end - end - end - - private - - # This is a copy of https://github.com/mperham/sidekiq/blob/32c55e31659a1e6bd42f98334cca5eef2863de8d/lib/sidekiq/scheduled.rb#L11-L34 - # - # It effectively reverts - # https://github.com/mperham/sidekiq/commit/9b75467b33759888753191413eddbc15c37a219e - # because we observe that the extra ZREMs caused by this change can lead to high - # CPU usage on Redis at peak times: - # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1179 - # - def find_jobs_and_enqueue(now, sorted_sets) - # A job's "score" in Redis is the time at which it should be processed. - # Just check Redis for the set of jobs with a timestamp before now. - Sidekiq.redis do |conn| - sorted_sets.each do |sorted_set| - start_time = ::Gitlab::Metrics::System.monotonic_time - jobs = redundant_jobs = 0 - - Sidekiq.logger.info(message: 'Enqueuing scheduled jobs', status: 'start', sorted_set: sorted_set) - - # Get the next item in the queue if it's score (time to execute) is <= now. - # We need to go through the list one at a time to reduce the risk of something - # going wrong between the time jobs are popped from the scheduled queue and when - # they are pushed onto a work queue and losing the jobs. - while job = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 1]).first - # Pop item off the queue and add it to the work queue. If the job can't be popped from - # the queue, it's because another process already popped it so we can move on to the - # next one. - if conn.zrem(sorted_set, job) - jobs += 1 - Sidekiq::Client.push(Sidekiq.load_json(job)) - else - redundant_jobs += 1 - end - end - - end_time = ::Gitlab::Metrics::System.monotonic_time - Sidekiq.logger.info(message: 'Enqueuing scheduled jobs', - status: 'done', - sorted_set: sorted_set, - jobs_count: jobs, - redundant_jobs_count: redundant_jobs, - duration_s: end_time - start_time) - end - end - end - - def atomic_find_jobs_and_enqueue(now, sorted_sets) - Sidekiq.redis do |conn| - sorted_sets.each do |sorted_set| - start_time = ::Gitlab::Metrics::System.monotonic_time - jobs = 0 - - Sidekiq.logger.info(message: 'Atomically enqueuing scheduled jobs', status: 'start', sorted_set: sorted_set) - - while job = redis_eval_lua(conn, LUA_ZPOPBYSCORE, LUA_ZPOPBYSCORE_SHA, keys: [sorted_set], argv: [now]) - jobs += 1 - Sidekiq::Client.push(Sidekiq.load_json(job)) - end - - end_time = ::Gitlab::Metrics::System.monotonic_time - Sidekiq.logger.info(message: 'Atomically enqueuing scheduled jobs', - status: 'done', - sorted_set: sorted_set, - jobs_count: jobs, - duration_s: end_time - start_time) - end - end - end - - def redis_eval_lua(conn, script, sha, keys: nil, argv: nil) - conn.evalsha(sha, keys: keys, argv: argv) - rescue ::Redis::CommandError => e - if e.message.start_with?('NOSCRIPT') - conn.eval(script, keys: keys, argv: argv) - else - raise - end - end - end -end diff --git a/lib/sidebars/projects/menus/shimo_menu.rb b/lib/sidebars/projects/menus/shimo_menu.rb new file mode 100644 index 00000000000..c93c4f6a0a4 --- /dev/null +++ b/lib/sidebars/projects/menus/shimo_menu.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ShimoMenu < ::Sidebars::Menu + override :link + def link + project_integrations_shimo_path(context.project) + end + + override :title + def title + s_('Shimo|Shimo') + end + + override :image_path + def image_path + 'logos/shimo.svg' + end + + override :image_html_options + def image_html_options + { + size: 16 + } + end + + override :render? + def render? + context.project.has_shimo? + end + + override :active_routes + def active_routes + { controller: :shimo } + end + end + end + end +end diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb index 8fbd71c1543..6bb4fb52e2a 100644 --- a/lib/sidebars/projects/panel.rb +++ b/lib/sidebars/projects/panel.rb @@ -32,8 +32,7 @@ module Sidebars add_menu(Sidebars::Projects::Menus::InfrastructureMenu.new(context)) add_menu(Sidebars::Projects::Menus::PackagesRegistriesMenu.new(context)) add_menu(Sidebars::Projects::Menus::AnalyticsMenu.new(context)) - add_menu(confluence_or_wiki_menu) - add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context)) + add_wiki_menus add_menu(Sidebars::Projects::Menus::SnippetsMenu.new(context)) add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context)) add_invite_members_menu @@ -46,10 +45,16 @@ module Sidebars end end - def confluence_or_wiki_menu - confluence_menu = ::Sidebars::Projects::Menus::ConfluenceMenu.new(context) + def add_wiki_menus + add_menu((third_party_wiki_menu || Sidebars::Projects::Menus::WikiMenu).new(context)) + add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context)) + end - confluence_menu.render? ? confluence_menu : Sidebars::Projects::Menus::WikiMenu.new(context) + def third_party_wiki_menu + wiki_menu_list = [::Sidebars::Projects::Menus::ConfluenceMenu] + wiki_menu_list << ::Sidebars::Projects::Menus::ShimoMenu if Feature.enabled?(:shimo_integration, context.project) + + wiki_menu_list.find { |wiki_menu| wiki_menu.new(context).render? } end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d6d872c8334..6694d6b5e89 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17092,6 +17092,9 @@ msgstr "" msgid "Hi %{username}!" msgstr "" +msgid "Hidden" +msgstr "" + msgid "Hide" msgstr "" @@ -31883,15 +31886,27 @@ msgstr "" msgid "Sherlock Transactions" msgstr "" +msgid "Shimo|Go to Shimo Workspace" +msgstr "" + msgid "Shimo|Link to a Shimo Workspace from the sidebar." msgstr "" msgid "Shimo|Shimo" msgstr "" +msgid "Shimo|Shimo Workspace" +msgstr "" + msgid "Shimo|Shimo Workspace URL" msgstr "" +msgid "Shimo|Shimo Workspace integration is enabled" +msgstr "" + +msgid "Shimo|You've enabled the Shimo Workspace integration. You can view your wiki directly in Shimo." +msgstr "" + msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account." msgstr "" diff --git a/package.json b/package.json index 440c5e1fa29..2f76133a40d 100644 --- a/package.json +++ b/package.json @@ -63,36 +63,36 @@ "@rails/ujs": "6.1.4-1", "@sentry/browser": "5.30.0", "@sourcegraph/code-host-integration": "0.0.60", - "@tiptap/core": "^2.0.0-beta.125", - "@tiptap/extension-blockquote": "^2.0.0-beta.19", - "@tiptap/extension-bold": "^2.0.0-beta.19", - "@tiptap/extension-bullet-list": "^2.0.0-beta.18", - "@tiptap/extension-code": "^2.0.0-beta.20", - "@tiptap/extension-code-block-lowlight": "2.0.0-beta.47", - "@tiptap/extension-document": "^2.0.0-beta.13", - "@tiptap/extension-dropcursor": "^2.0.0-beta.19", - "@tiptap/extension-gapcursor": "^2.0.0-beta.27", - "@tiptap/extension-hard-break": "^2.0.0-beta.24", - "@tiptap/extension-heading": "^2.0.0-beta.18", - "@tiptap/extension-history": "^2.0.0-beta.16", - "@tiptap/extension-horizontal-rule": "^2.0.0-beta.24", - "@tiptap/extension-image": "^2.0.0-beta.19", - "@tiptap/extension-italic": "^2.0.0-beta.19", - "@tiptap/extension-link": "^2.0.0-beta.23", - "@tiptap/extension-list-item": "^2.0.0-beta.14", - "@tiptap/extension-ordered-list": "^2.0.0-beta.19", - "@tiptap/extension-paragraph": "^2.0.0-beta.17", - "@tiptap/extension-strike": "^2.0.0-beta.21", - "@tiptap/extension-subscript": "^2.0.0-beta.4", - "@tiptap/extension-superscript": "^2.0.0-beta.4", - "@tiptap/extension-table": "^2.0.0-beta.34", - "@tiptap/extension-table-cell": "^2.0.0-beta.15", - "@tiptap/extension-table-header": "^2.0.0-beta.17", - "@tiptap/extension-table-row": "^2.0.0-beta.14", - "@tiptap/extension-task-item": "^2.0.0-beta.21", - "@tiptap/extension-task-list": "^2.0.0-beta.18", - "@tiptap/extension-text": "^2.0.0-beta.13", - "@tiptap/vue-2": "^2.0.0-beta.60", + "@tiptap/core": "^2.0.0-beta.138", + "@tiptap/extension-blockquote": "^2.0.0-beta.24", + "@tiptap/extension-bold": "^2.0.0-beta.24", + "@tiptap/extension-bullet-list": "^2.0.0-beta.23", + "@tiptap/extension-code": "^2.0.0-beta.25", + "@tiptap/extension-code-block-lowlight": "2.0.0-beta.55", + "@tiptap/extension-document": "^2.0.0-beta.15", + "@tiptap/extension-dropcursor": "^2.0.0-beta.24", + "@tiptap/extension-gapcursor": "^2.0.0-beta.33", + "@tiptap/extension-hard-break": "^2.0.0-beta.30", + "@tiptap/extension-heading": "^2.0.0-beta.23", + "@tiptap/extension-history": "^2.0.0-beta.21", + "@tiptap/extension-horizontal-rule": "^2.0.0-beta.30", + "@tiptap/extension-image": "^2.0.0-beta.24", + "@tiptap/extension-italic": "^2.0.0-beta.24", + "@tiptap/extension-link": "^2.0.0-beta.28", + "@tiptap/extension-list-item": "^2.0.0-beta.19", + "@tiptap/extension-ordered-list": "^2.0.0-beta.24", + "@tiptap/extension-paragraph": "^2.0.0-beta.22", + "@tiptap/extension-strike": "^2.0.0-beta.26", + "@tiptap/extension-subscript": "^2.0.0-beta.9", + "@tiptap/extension-superscript": "^2.0.0-beta.9", + "@tiptap/extension-table": "^2.0.0-beta.42", + "@tiptap/extension-table-cell": "^2.0.0-beta.20", + "@tiptap/extension-table-header": "^2.0.0-beta.22", + "@tiptap/extension-table-row": "^2.0.0-beta.19", + "@tiptap/extension-task-item": "^2.0.0-beta.28", + "@tiptap/extension-task-list": "^2.0.0-beta.23", + "@tiptap/extension-text": "^2.0.0-beta.15", + "@tiptap/vue-2": "^2.0.0-beta.68", "@toast-ui/editor": "^2.5.2", "@toast-ui/vue-editor": "^2.5.2", "apollo-cache-inmemory": "^1.6.6", @@ -162,10 +162,10 @@ "portal-vue": "^2.1.7", "prismjs": "^1.21.0", "prosemirror-markdown": "^1.6.0", - "prosemirror-model": "^1.14.3", + "prosemirror-model": "^1.15.0", "prosemirror-state": "^1.3.4", "prosemirror-tables": "^1.1.1", - "prosemirror-view": "^1.20.3", + "prosemirror-view": "^1.23.1", "raphael": "^2.2.7", "raw-loader": "^4.0.2", "scrollparent": "^2.0.1", diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index cfd93c2df10..c41d9016e52 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -28,7 +28,6 @@ import TableHeader from '~/content_editor/extensions/table_header'; import TableRow from '~/content_editor/extensions/table_row'; import TaskItem from '~/content_editor/extensions/task_item'; import TaskList from '~/content_editor/extensions/task_list'; -import Text from '~/content_editor/extensions/text'; import markdownSerializer from '~/content_editor/services/markdown_serializer'; import { createTestEditor, createDocBuilder } from '../test_utils'; @@ -58,7 +57,6 @@ const tiptapEditor = createTestEditor({ Link, ListItem, OrderedList, - Paragraph, Strike, Table, TableCell, @@ -66,7 +64,6 @@ const tiptapEditor = createTestEditor({ TableRow, TaskItem, TaskList, - Text, ], }); diff --git a/spec/frontend/dropzone_input_spec.js b/spec/frontend/dropzone_input_spec.js index 12e10f7c5f4..11414e8890d 100644 --- a/spec/frontend/dropzone_input_spec.js +++ b/spec/frontend/dropzone_input_spec.js @@ -32,6 +32,8 @@ describe('dropzone_input', () => { }); describe('handlePaste', () => { + let form; + const triggerPasteEvent = (clipboardData = {}) => { const event = $.Event('paste'); const origEvent = new Event('paste'); @@ -45,11 +47,15 @@ describe('dropzone_input', () => { beforeEach(() => { loadFixtures('issues/new-issue.html'); - const form = $('#new_issue'); + form = $('#new_issue'); form.data('uploads-path', TEST_UPLOAD_PATH); dropzoneInput(form); }); + afterEach(() => { + form = null; + }); + it('pastes Markdown tables', () => { jest.spyOn(PasteMarkdownTable.prototype, 'isTable'); jest.spyOn(PasteMarkdownTable.prototype, 'convertToTableMarkdown'); @@ -86,6 +92,27 @@ describe('dropzone_input', () => { expect(axiosMock.history.post[0].data.get('file').name).toHaveLength(246); }); + it('disables generated image file when clipboardData have both image and text', () => { + const TEST_PLAIN_TEXT = 'This wording is a plain text.'; + triggerPasteEvent({ + types: ['text/plain', 'Files'], + getData: () => TEST_PLAIN_TEXT, + items: [ + { + kind: 'text', + type: 'text/plain', + }, + { + kind: 'file', + type: 'image/png', + getAsFile: () => new Blob(), + }, + ], + }); + + expect(form.find('.js-gfm-input')[0].value).toBe(''); + }); + it('display original file name in comment box', async () => { const axiosMock = new MockAdapter(axios); triggerPasteEvent({ diff --git a/spec/frontend/issuable_list/components/issuable_item_spec.js b/spec/frontend/issuable_list/components/issuable_item_spec.js index ac3bf7f3269..a4d90613ca6 100644 --- a/spec/frontend/issuable_list/components/issuable_item_spec.js +++ b/spec/frontend/issuable_list/components/issuable_item_spec.js @@ -1,19 +1,25 @@ import { GlLink, GlLabel, GlIcon, GlFormCheckbox, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; +import { shallowMountExtended as shallowMount } from 'helpers/vue_test_utils_helper'; import IssuableItem from '~/issuable_list/components/issuable_item.vue'; import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; import { mockIssuable, mockRegularLabel, mockScopedLabel } from '../mock_data'; -const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots = {} } = {}) => +const createComponent = ({ + issuableSymbol = '#', + issuable = mockIssuable, + enableLabelPermalinks = true, + showCheckbox = true, + slots = {}, +} = {}) => shallowMount(IssuableItem, { propsData: { issuableSymbol, issuable, - enableLabelPermalinks: true, + enableLabelPermalinks, showDiscussions: true, - showCheckbox: false, + showCheckbox, }, slots, stubs: { @@ -34,7 +40,6 @@ describe('IssuableItem', () => { beforeEach(() => { gon.gitlab_url = MOCK_GITLAB_URL; - wrapper = createComponent(); }); afterEach(() => { @@ -45,6 +50,8 @@ describe('IssuableItem', () => { describe('computed', () => { describe('author', () => { it('returns `issuable.author` reference', () => { + wrapper = createComponent(); + expect(wrapper.vm.author).toEqual(mockIssuable.author); }); }); @@ -59,7 +66,7 @@ describe('IssuableItem', () => { `( 'returns $returnValue when value of `issuable.author.id` is $authorId', async ({ authorId, returnValue }) => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, author: { @@ -86,7 +93,7 @@ describe('IssuableItem', () => { `( 'returns $returnValue when `issuable.webUrl` is $urlType', async ({ issuableWebUrl, returnValue }) => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, webUrl: issuableWebUrl, @@ -102,11 +109,13 @@ describe('IssuableItem', () => { describe('labels', () => { it('returns `issuable.labels.nodes` reference when it is available', () => { + wrapper = createComponent(); + expect(wrapper.vm.labels).toEqual(mockLabels); }); it('returns `issuable.labels` reference when it is available', async () => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, labels: mockLabels, @@ -119,7 +128,7 @@ describe('IssuableItem', () => { }); it('returns empty array when none of `issuable.labels.nodes` or `issuable.labels` are available', async () => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, labels: null, @@ -134,12 +143,16 @@ describe('IssuableItem', () => { describe('assignees', () => { it('returns `issuable.assignees` reference when it is available', () => { + wrapper = createComponent(); + expect(wrapper.vm.assignees).toBe(mockIssuable.assignees); }); }); describe('updatedAt', () => { it('returns string containing timeago string based on `issuable.updatedAt`', () => { + wrapper = createComponent(); + expect(wrapper.vm.updatedAt).toContain('updated'); expect(wrapper.vm.updatedAt).toContain('ago'); }); @@ -155,7 +168,7 @@ describe('IssuableItem', () => { `( 'returns $returnValue when issuable.userDiscussionsCount is $userDiscussionsCount', ({ userDiscussionsCount, returnValue }) => { - const wrapperWithDiscussions = createComponent({ + wrapper = createComponent({ issuableSymbol: '#', issuable: { ...mockIssuable, @@ -163,9 +176,7 @@ describe('IssuableItem', () => { }, }); - expect(wrapperWithDiscussions.vm.showDiscussions).toBe(returnValue); - - wrapperWithDiscussions.destroy(); + expect(wrapper.findByTestId('issuable-discussions').exists()).toBe(returnValue); }, ); }); @@ -180,6 +191,8 @@ describe('IssuableItem', () => { `( 'return $returnValue when provided label param is a $labelType label', ({ label, returnValue }) => { + wrapper = createComponent(); + expect(wrapper.vm.scopedLabel(label)).toBe(returnValue); }, ); @@ -191,19 +204,23 @@ describe('IssuableItem', () => { ${{ title: 'foo' }} | ${'title'} | ${'foo'} ${{ name: 'foo' }} | ${'name'} | ${'foo'} `('returns string value of `label.$propWithTitle`', ({ label, returnValue }) => { + wrapper = createComponent(); + expect(wrapper.vm.labelTitle(label)).toBe(returnValue); }); }); describe('labelTarget', () => { it('returns target string for a provided label param when `enableLabelPermalinks` is true', () => { + wrapper = createComponent(); + expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe( '?label_name[]=Documentation%20Update', ); }); it('returns string "#" for a provided label param when `enableLabelPermalinks` is false', async () => { - wrapper.setProps({ + wrapper = createComponent({ enableLabelPermalinks: false, }); @@ -223,7 +240,7 @@ describe('IssuableItem', () => { `( 'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`', async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, webUrl, @@ -243,7 +260,7 @@ describe('IssuableItem', () => { ); it('renders checkbox when `showCheckbox` prop is true', async () => { - wrapper.setProps({ + wrapper = createComponent({ showCheckbox: true, }); @@ -262,7 +279,7 @@ describe('IssuableItem', () => { }); it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, webUrl: 'http://jira.atlassian.net/browse/IG-1', @@ -277,7 +294,7 @@ describe('IssuableItem', () => { }); it('renders issuable confidential icon when issuable is confidential', async () => { - wrapper.setProps({ + wrapper = createComponent({ issuable: { ...mockIssuable, confidential: true, @@ -296,7 +313,21 @@ describe('IssuableItem', () => { }); }); + it('renders spam icon when issuable is hidden', async () => { + wrapper = createComponent({ issuable: { ...mockIssuable, hidden: true } }); + + const hiddenIcon = wrapper.findComponent(GlIcon); + + expect(hiddenIcon.props('name')).toBe('spam'); + expect(hiddenIcon.attributes()).toMatchObject({ + title: 'This issue is hidden because its author has been banned', + arialabel: 'Hidden', + }); + }); + it('renders task status', () => { + wrapper = createComponent(); + const taskStatus = wrapper.find('[data-testid="task-status"]'); const expected = `${mockIssuable.taskCompletionStatus.completedCount} of ${mockIssuable.taskCompletionStatus.count} tasks completed`; @@ -304,6 +335,8 @@ describe('IssuableItem', () => { }); it('renders issuable reference', () => { + wrapper = createComponent(); + const referenceEl = wrapper.find('[data-testid="issuable-reference"]'); expect(referenceEl.exists()).toBe(true); @@ -311,7 +344,7 @@ describe('IssuableItem', () => { }); it('renders issuable reference via slot', () => { - const wrapperWithRefSlot = createComponent({ + wrapper = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { @@ -320,15 +353,15 @@ describe('IssuableItem', () => { `, }, }); - const referenceEl = wrapperWithRefSlot.find('.js-reference'); + const referenceEl = wrapper.find('.js-reference'); expect(referenceEl.exists()).toBe(true); expect(referenceEl.text()).toBe(`${mockIssuable.iid}`); - - wrapperWithRefSlot.destroy(); }); it('renders issuable createdAt info', () => { + wrapper = createComponent(); + const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]'); expect(createdAtEl.exists()).toBe(true); @@ -337,6 +370,8 @@ describe('IssuableItem', () => { }); it('renders issuable author info', () => { + wrapper = createComponent(); + const authorEl = wrapper.find('[data-testid="issuable-author"]'); expect(authorEl.exists()).toBe(true); @@ -351,7 +386,7 @@ describe('IssuableItem', () => { }); it('renders issuable author info via slot', () => { - const wrapperWithAuthorSlot = createComponent({ + wrapper = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { @@ -360,16 +395,14 @@ describe('IssuableItem', () => { `, }, }); - const authorEl = wrapperWithAuthorSlot.find('.js-author'); + const authorEl = wrapper.find('.js-author'); expect(authorEl.exists()).toBe(true); expect(authorEl.text()).toBe(mockAuthor.name); - - wrapperWithAuthorSlot.destroy(); }); it('renders timeframe via slot', () => { - const wrapperWithTimeframeSlot = createComponent({ + wrapper = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { @@ -378,15 +411,15 @@ describe('IssuableItem', () => { `, }, }); - const timeframeEl = wrapperWithTimeframeSlot.find('.js-timeframe'); + const timeframeEl = wrapper.find('.js-timeframe'); expect(timeframeEl.exists()).toBe(true); expect(timeframeEl.text()).toBe('Jan 1, 2020 - Mar 31, 2020'); - - wrapperWithTimeframeSlot.destroy(); }); it('renders gl-label component for each label present within `issuable` prop', () => { + wrapper = createComponent(); + const labelsEl = wrapper.findAll(GlLabel); expect(labelsEl.exists()).toBe(true); @@ -402,7 +435,7 @@ describe('IssuableItem', () => { }); it('renders issuable status via slot', () => { - const wrapperWithStatusSlot = createComponent({ + wrapper = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { @@ -411,15 +444,15 @@ describe('IssuableItem', () => { `, }, }); - const statusEl = wrapperWithStatusSlot.find('.js-status'); + const statusEl = wrapper.find('.js-status'); expect(statusEl.exists()).toBe(true); expect(statusEl.text()).toBe(`${mockIssuable.state}`); - - wrapperWithStatusSlot.destroy(); }); it('renders discussions count', () => { + wrapper = createComponent(); + const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]'); expect(discussionsEl.exists()).toBe(true); @@ -432,6 +465,8 @@ describe('IssuableItem', () => { }); it('renders issuable-assignees component', () => { + wrapper = createComponent(); + const assigneesEl = wrapper.find(IssuableAssignees); expect(assigneesEl.exists()).toBe(true); @@ -443,6 +478,8 @@ describe('IssuableItem', () => { }); it('renders issuable updatedAt info', () => { + wrapper = createComponent(); + const updatedAtEl = wrapper.find('[data-testid="issuable-updated-at"]'); expect(updatedAtEl.attributes('title')).toBe('Sep 10, 2020 11:41am UTC'); diff --git a/spec/frontend/issues_list/components/issues_list_app_spec.js b/spec/frontend/issues_list/components/issues_list_app_spec.js index 3f52c7b4afe..c398acf081b 100644 --- a/spec/frontend/issues_list/components/issues_list_app_spec.js +++ b/spec/frontend/issues_list/components/issues_list_app_spec.js @@ -17,7 +17,7 @@ import { locationSearch, urlParams, } from 'jest/issues_list/mock_data'; -import createFlash from '~/flash'; +import createFlash, { FLASH_TYPES } from '~/flash'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import IssuableByEmail from '~/issuable/components/issuable_by_email.vue'; @@ -29,6 +29,8 @@ import { CREATED_DESC, DUE_DATE_OVERDUE, PARAM_DUE_DATE, + RELATIVE_POSITION, + RELATIVE_POSITION_ASC, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_AUTHOR, TOKEN_TYPE_CONFIDENTIAL, @@ -314,6 +316,29 @@ describe('IssuesListApp component', () => { }, }); }); + + describe('when issue repositioning is disabled and the sort is manual', () => { + beforeEach(() => { + setWindowLocation(`?sort=${RELATIVE_POSITION}`); + wrapper = mountComponent({ provide: { isIssueRepositioningDisabled: true } }); + }); + + it('changes the sort to the default of created descending', () => { + expect(findIssuableList().props()).toMatchObject({ + initialSortBy: CREATED_DESC, + urlParams: { + sort: urlSortParams[CREATED_DESC], + }, + }); + }); + + it('shows an alert to tell the user that manual reordering is disabled', () => { + expect(createFlash).toHaveBeenCalledWith({ + message: IssuesListApp.i18n.issueRepositioningMessage, + type: FLASH_TYPES.NOTICE, + }); + }); + }); }); describe('state', () => { @@ -762,6 +787,30 @@ describe('IssuesListApp component', () => { }); }, ); + + describe('when issue repositioning is disabled', () => { + const initialSort = CREATED_DESC; + + beforeEach(() => { + setWindowLocation(`?sort=${initialSort}`); + wrapper = mountComponent({ provide: { isIssueRepositioningDisabled: true } }); + + findIssuableList().vm.$emit('sort', RELATIVE_POSITION_ASC); + }); + + it('does not update the sort to manual', () => { + expect(findIssuableList().props('urlParams')).toMatchObject({ + sort: urlSortParams[initialSort], + }); + }); + + it('shows an alert to tell the user that manual reordering is disabled', () => { + expect(createFlash).toHaveBeenCalledWith({ + message: IssuesListApp.i18n.issueRepositioningMessage, + type: FLASH_TYPES.NOTICE, + }); + }); + }); }); describe('when "update-legacy-bulk-edit" event is emitted by IssuableList', () => { diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js index 19a8af4d9c2..d408316a063 100644 --- a/spec/frontend/issues_list/mock_data.js +++ b/spec/frontend/issues_list/mock_data.js @@ -22,6 +22,7 @@ export const getIssuesQueryResponse = { createdAt: '2021-05-22T04:08:01Z', downvotes: 2, dueDate: '2021-05-29', + hidden: false, humanTimeEstimate: null, mergeRequestsCount: false, moved: false, diff --git a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap index b576f1b2553..7e3ff734981 100644 --- a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap @@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = ` data-qa-selector="package_row" >
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap index c95538546c1..7aa42a1f1e5 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap @@ -5,7 +5,7 @@ exports[`VersionRow renders 1`] = ` class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100" >
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap index 2f2be797251..bb65db807f4 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap @@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = ` data-qa-selector="package_row" >
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 43b27dded3b..31e19080efe 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -302,6 +302,7 @@ RSpec.describe IssuesHelper do allow(helper).to receive(:can?).and_return(true) allow(helper).to receive(:image_path).and_return('#') allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') + allow(helper).to receive(:issue_repositioning_disabled?).and_return(true) allow(helper).to receive(:url_for).and_return('#') expected = { @@ -318,6 +319,7 @@ RSpec.describe IssuesHelper do has_any_issues: project_issues(project).exists?.to_s, import_csv_issues_path: '#', initial_email: project.new_issuable_address(current_user, 'issue'), + is_issue_repositioning_disabled: 'true', is_project: 'true', is_signed_in: current_user.present?.to_s, jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), diff --git a/spec/lib/gitlab/sidekiq_enq_spec.rb b/spec/lib/gitlab/sidekiq_enq_spec.rb deleted file mode 100644 index 6903f01bf5f..00000000000 --- a/spec/lib/gitlab/sidekiq_enq_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::SidekiqEnq, :clean_gitlab_redis_queues do - let(:retry_set) { Sidekiq::Scheduled::SETS.first } - let(:schedule_set) { Sidekiq::Scheduled::SETS.last } - - around do |example| - freeze_time { example.run } - end - - shared_examples 'finds jobs that are due and enqueues them' do - before do - Sidekiq.redis do |redis| - redis.zadd(retry_set, (Time.current - 1.day).to_f.to_s, '{"jid": 1}') - redis.zadd(retry_set, Time.current.to_f.to_s, '{"jid": 2}') - redis.zadd(retry_set, (Time.current + 1.day).to_f.to_s, '{"jid": 3}') - - redis.zadd(schedule_set, (Time.current - 1.day).to_f.to_s, '{"jid": 4}') - redis.zadd(schedule_set, Time.current.to_f.to_s, '{"jid": 5}') - redis.zadd(schedule_set, (Time.current + 1.day).to_f.to_s, '{"jid": 6}') - end - end - - it 'enqueues jobs that are due' do - expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 1 }) - expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 2 }) - expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 4 }) - expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 5 }) - - Gitlab::SidekiqEnq.new.enqueue_jobs - - Sidekiq.redis do |redis| - expect(redis.zscan_each(retry_set).map(&:first)).to contain_exactly('{"jid": 3}') - expect(redis.zscan_each(schedule_set).map(&:first)).to contain_exactly('{"jid": 6}') - end - end - end - - context 'when atomic_sidekiq_scheduler is disabled' do - before do - stub_feature_flags(atomic_sidekiq_scheduler: false) - end - - it_behaves_like 'finds jobs that are due and enqueues them' - - context 'when ZRANGEBYSCORE returns a job that is already removed by another process' do - before do - Sidekiq.redis do |redis| - redis.zadd(schedule_set, Time.current.to_f.to_s, '{"jid": 1}') - - allow(redis).to receive(:zrangebyscore).and_wrap_original do |m, *args, **kwargs| - m.call(*args, **kwargs).tap do |jobs| - redis.zrem(schedule_set, jobs.first) if args[0] == schedule_set && jobs.first - end - end - end - end - - it 'calls ZREM but does not enqueue the job' do - Sidekiq.redis do |redis| - expect(redis).to receive(:zrem).with(schedule_set, '{"jid": 1}').twice.and_call_original - end - expect(Sidekiq::Client).not_to receive(:push) - - Gitlab::SidekiqEnq.new.enqueue_jobs - end - end - end - - context 'when atomic_sidekiq_scheduler is enabled' do - before do - stub_feature_flags(atomic_sidekiq_scheduler: true) - end - - context 'when Lua script is not yet loaded' do - before do - Gitlab::Redis::Queues.with { |redis| redis.script(:flush) } - end - - it_behaves_like 'finds jobs that are due and enqueues them' - end - - context 'when Lua script is already loaded' do - before do - Gitlab::SidekiqEnq.new.enqueue_jobs - end - - it_behaves_like 'finds jobs that are due and enqueues them' - end - end -end diff --git a/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb b/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb new file mode 100644 index 00000000000..534267a329e --- /dev/null +++ b/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Projects::Menus::ShimoMenu do + let_it_be_with_reload(:project) { create(:project) } + + let(:context) { Sidebars::Projects::Context.new(current_user: project.owner, container: project) } + + subject(:shimo_menu) { described_class.new(context) } + + describe '#render?' do + context 'without a valid Shimo integration' do + it "doesn't render the menu" do + expect(shimo_menu.render?).to be_falsey + end + end + + context 'with a valid Shimo integration' do + let_it_be_with_reload(:shimo_integration) { create(:shimo_integration, project: project) } + + context 'when integration is active' do + it 'renders the menu' do + expect(shimo_menu.render?).to eq true + end + + it 'renders menu link' do + expected_url = Rails.application.routes.url_helpers.project_integrations_shimo_path(project) + expect(shimo_menu.link).to eq expected_url + end + end + + context 'when integration is inactive' do + before do + shimo_integration.update!(active: false) + end + + it "doesn't render the menu" do + expect(shimo_menu.render?).to eq false + end + end + end + end +end diff --git a/spec/models/integrations/shimo_spec.rb b/spec/models/integrations/shimo_spec.rb index 25df8d2b249..41f3f3c0c16 100644 --- a/spec/models/integrations/shimo_spec.rb +++ b/spec/models/integrations/shimo_spec.rb @@ -38,4 +38,26 @@ RSpec.describe ::Integrations::Shimo do end end end + + describe 'Caching has_shimo on project_settings' do + let(:project) { create(:project) } + + subject { project.project_setting.has_shimo? } + + it 'sets the property to true when integration is active' do + create(:shimo_integration, project: project, active: true) + + is_expected.to be(true) + end + + it 'sets the property to false when integration is not active' do + create(:shimo_integration, project: project, active: false) + + is_expected.to be(false) + end + + it 'creates a project_setting record if one was not already created' do + expect { create(:shimo_integration) }.to change(ProjectSetting, :count).by(1) + end + end end diff --git a/spec/requests/projects/integrations/shimos_controller_spec.rb b/spec/requests/projects/integrations/shimos_controller_spec.rb new file mode 100644 index 00000000000..7322143f87e --- /dev/null +++ b/spec/requests/projects/integrations/shimos_controller_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Projects::Integrations::ShimosController do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, developer_projects: [project]) } + let_it_be(:shimo_integration) { create(:shimo_integration, project: project) } + + before do + sign_in(user) + end + + describe 'GET #show' do + context 'when Shimo integration is inactive' do + before do + shimo_integration.update!(active: false) + end + + it 'returns 404 status' do + get project_integrations_shimo_path(project) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when Shimo integration is active' do + it 'renders the "show" template' do + get project_integrations_shimo_path(project) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:show) + expect(response.body).to include shimo_integration.external_wiki_url + end + end + end +end diff --git a/yarn.lock b/yarn.lock index d9dfcf2a36a..a4ce69ffa7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1491,223 +1491,223 @@ dom-accessibility-api "^0.5.1" pretty-format "^26.4.2" -"@tiptap/core@^2.0.0-beta.125": - version "2.0.0-beta.125" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.125.tgz#672a86f21727614bfe8d5bf41544ce83ea4a7c87" - integrity sha512-I3lju5hcjK6nhC29jJCdGoRdP0C5nPBHGN8y/fbFmCX0oQ/THuzVdMgaGPpkHjralPPMS3DqHD/Rx8Kbo8zClg== +"@tiptap/core@^2.0.0-beta.138": + version "2.0.0-beta.138" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.138.tgz#3a73b32b10f07ba2842142552457b52abf8cfc41" + integrity sha512-Cg3ig6c+NCBILYaVNf5h8vJdsRynhKzy+zQzH/91kLoWzpNV5J6R2sW32Oufuwvr0Kra1+kKKh/WIGpB3Ia4RA== dependencies: "@types/prosemirror-commands" "^1.0.4" "@types/prosemirror-keymap" "^1.0.4" "@types/prosemirror-model" "^1.13.2" "@types/prosemirror-schema-list" "^1.0.3" - "@types/prosemirror-state" "^1.2.7" + "@types/prosemirror-state" "^1.2.8" "@types/prosemirror-transform" "^1.1.4" "@types/prosemirror-view" "^1.19.1" - prosemirror-commands "^1.1.11" - prosemirror-keymap "^1.1.3" - prosemirror-model "^1.14.3" + prosemirror-commands "^1.1.12" + prosemirror-keymap "^1.1.5" + prosemirror-model "^1.15.0" prosemirror-schema-list "^1.1.6" prosemirror-state "^1.3.4" prosemirror-transform "^1.3.3" - prosemirror-view "^1.20.3" + prosemirror-view "^1.22.0" -"@tiptap/extension-blockquote@^2.0.0-beta.19": - version "2.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.19.tgz#4b1dfd0ec511f889ddb0f34cd09e57a0db877dc0" - integrity sha512-9y8keXSm4E5mdh/EocdbrJ/H71qyXv2jajRHIsXj1SaJqLaz4JbSQGrX3j2r0ia3KW3YNFFIHl/z33fU70YfGQ== +"@tiptap/extension-blockquote@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.24.tgz#4dcaf676ded8c3b551efd8f2d6b51cc882ba7d03" + integrity sha512-u9D/ZOziO4rMBKeLj7JA7fOc9h8wU6zrzVEsX9MbJwmuicoJZ1lIQ9cyrFWwmlfznzuXLaAxm3iZuHt7xxMppQ== -"@tiptap/extension-bold@^2.0.0-beta.19": - version "2.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.19.tgz#9426b5fcb2bfb79082f9efefbafff955d26d023e" - integrity sha512-pbYMK3Lz78XFi+1OisdjuGQcjRnzHXeYivh9A4xP1fmSOS6t/lQsu2P2uDhFNXtz45dLcLTOkqnE0j5lj3TGMg== +"@tiptap/extension-bold@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.24.tgz#a8d1076922580db528cc6988fde08f731dcfe733" + integrity sha512-2VTCtY2JI0wpDwWT0a2fMFkjbgxDpwD3wvtY3/ndh5pyNX0JQCXtJarFzfZZurWvLNQ8QPRRel73182RBYUOHQ== -"@tiptap/extension-bubble-menu@^2.0.0-beta.42": - version "2.0.0-beta.42" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.42.tgz#40c2783860721e861b3eb11aa2a076d1944058c2" - integrity sha512-H+pGpSk0mK4BRm4RbevgT+ir1bEwlS1dEwi2YIPYVl+JSpuGS2P1jTWeZpCou9cewNuxxwrAarhp4aEvJrL8UQ== +"@tiptap/extension-bubble-menu@^2.0.0-beta.49": + version "2.0.0-beta.49" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.49.tgz#f9863b1abad5f87d298d4e6527005484137a6166" + integrity sha512-JbaSG3otBuMKRyTn0OqVscZnwqJ7c+qyKAnoZJit5EK1RS72cTfGWZvvAxaslAM4DeE9avJeudUi/tN5Iafv4A== dependencies: prosemirror-state "^1.3.4" - prosemirror-view "^1.20.3" - tippy.js "^6.3.2" + prosemirror-view "^1.22.0" + tippy.js "^6.3.6" -"@tiptap/extension-bullet-list@^2.0.0-beta.18": - version "2.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.18.tgz#ab33426a3ea03cea11863e44aad9e48fc18d478c" - integrity sha512-dOf2Wx9bmgpBQIxhw7b+g1GhbIyIox7FIiIEkkSgqDtx8wPPYlnGwHRxopj4a57VbqRkRtspJZp52/vhP3is5w== +"@tiptap/extension-bullet-list@^2.0.0-beta.23": + version "2.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.23.tgz#64698c98039ad301c94a9041bbd117e82957be21" + integrity sha512-ReoUiz9f1IX87RX+GRE+fCaLEzNNwmiP4kli3QH8/qrLK3qxvZYr9N31fUeOHecCctUofPSbQB79B39zSo9Ouw== -"@tiptap/extension-code-block-lowlight@2.0.0-beta.47": - version "2.0.0-beta.47" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.47.tgz#2a3253f778375e4a420dd77967931b4da7926913" - integrity sha512-+k0uACctl4PIrZQrZkiPapYL/Uq8Skc6gBhlvtJ3+U9+U798Rm7QZpGxEe9iXW4KC69E4LtD1JNj27Ofns35Cg== +"@tiptap/extension-code-block-lowlight@2.0.0-beta.55": + version "2.0.0-beta.55" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.55.tgz#7ea0a9a64c1cf69514b359dcb0dbeb130afa2976" + integrity sha512-1Ckq4d3Q0EeEXlIX6QcHcHylvCEg2uj/BZ4jvclHc5rLYC1NndiwRoM5wkjj9xZ3WQHDHN405ocgFk+yUvID6g== dependencies: - "@tiptap/extension-code-block" "^2.0.0-beta.24" + "@tiptap/extension-code-block" "^2.0.0-beta.29" "@types/lowlight" "^0.0.3" lowlight "^1.20.0" - prosemirror-model "^1.14.3" + prosemirror-model "^1.15.0" prosemirror-state "^1.3.4" - prosemirror-view "^1.20.3" + prosemirror-view "^1.22.0" -"@tiptap/extension-code-block@^2.0.0-beta.24": - version "2.0.0-beta.24" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.24.tgz#f3d45d4ae0cdc2bf94ed5e61a5421c72b2bd3a53" - integrity sha512-Q6KxBe3FB+dMe/prlfeixXSVqGTmnOmIL2/10B1RzSj7Mj9SgzqQEHZFm3dKVqpYuMOYJ6S6edkW33E0Wq9ahQ== +"@tiptap/extension-code-block@^2.0.0-beta.29": + version "2.0.0-beta.29" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.29.tgz#ad7f537bc1f12decf027d66c7328f36a8b07795c" + integrity sha512-IoBJxqZ4F7dApRL3NisvMCBJmzpV0LmfJlFQacgm64Li15dP/QyDuvXpku03gT3y9e4f9Gv/KTKwJizXEVABhw== dependencies: prosemirror-state "^1.3.4" -"@tiptap/extension-code@^2.0.0-beta.20": - version "2.0.0-beta.20" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.20.tgz#0f5ff7e827ae09c26d23ddc9e62f6375d2f1c0f8" - integrity sha512-25twg/rsg5CxTOfDgYzg1GbwrtdzNX2vCQyYsauXfPI1kbrWXdVBYWeL4iHdJk5WElfH9WUbQ4kMGh13/KCG/g== +"@tiptap/extension-code@^2.0.0-beta.25": + version "2.0.0-beta.25" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.25.tgz#055dc8dc6d19d3f0439f57dd8ba6433e2f2fd733" + integrity sha512-kXBR4Zp79lpUEfqJtBGv9tO1mj9jFQLMj0iVhj8e8ZporNKei5JfDOY83kwFcKAE60i7tiRDtV3OizpAKMeqDg== -"@tiptap/extension-document@^2.0.0-beta.13": - version "2.0.0-beta.13" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.13.tgz#8cfb29d4de64bf4a790817f730c05b4f9b7167b2" - integrity sha512-nrufdKziA/wovaY4DjGkc8OGuIZi8CH8CW3+yYfeWbruwFKkyZHlZy9nplFWSEqBHPAeqD+px9r91yGMW3ontA== +"@tiptap/extension-document@^2.0.0-beta.15": + version "2.0.0-beta.15" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.15.tgz#5d17a0289244a913ab2ef08e8495a1e46950711e" + integrity sha512-ypENC+xUYD5m2t+KOKNYqyXnanXd5fxyIyhR1qeEEwwQwMXGNrO3kCH6O4mIDCpy+/WqHvVay2tV5dVsXnvY8w== -"@tiptap/extension-dropcursor@^2.0.0-beta.19": - version "2.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.19.tgz#8a37ffe27e484eb44dd18297830d1fd8ce0c50ce" - integrity sha512-rslIcVvD42NNh5sEbkCkG03DWMFBrS5KoK+lDOdIcC1DjmTtpVgcLvvE01btzaB3ljx+UVqI2Zaxa6VOiTeEMw== +"@tiptap/extension-dropcursor@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.24.tgz#e0263c8d784304cb885aea299bfd5255d3435765" + integrity sha512-B4bzY84g82VY78kv6BFNSCgO9Sc3dtgkvzFDJ57X2QweYyLkXbYeZxI8SqO7Nva1QRZadBlFyRPm+aP1rLZsew== dependencies: "@types/prosemirror-dropcursor" "^1.0.3" prosemirror-dropcursor "^1.3.5" -"@tiptap/extension-floating-menu@^2.0.0-beta.36": - version "2.0.0-beta.36" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.36.tgz#313082e2dd22b6b20c81aa4e98f7c9bcfd47ebe7" - integrity sha512-Pm9KK+Y7YUgMrlvqa/MgxV0WOTBiOp8d2kpt7OwGv/ahjc4amv0HFbei7glsiJ56VtOv8lsjiuBS+m2ctWHWVQ== +"@tiptap/extension-floating-menu@^2.0.0-beta.44": + version "2.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.44.tgz#fca9eefd9bcc74fdf035b012b9eb9fbfda331cf1" + integrity sha512-R8EF6XXlwoiHvCuXV3qGsEKae5u0OYrKHJNeOgau5SANg/ab+pjFzr3Lrt43NFpOKf5rzD3p2OJxnikudceLSg== dependencies: prosemirror-state "^1.3.4" - prosemirror-view "^1.20.3" - tippy.js "^6.3.2" + prosemirror-view "^1.22.0" + tippy.js "^6.3.6" -"@tiptap/extension-gapcursor@^2.0.0-beta.27": - version "2.0.0-beta.27" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.27.tgz#ced6cb7d39a388e60f8ec22889a015beb81b417a" - integrity sha512-Sk041ygN+PhAGiCnDfEtFl8sFmm+clKTHJZJwb6ixcdfgY9xkTpkrgswsAY5fvLnLxZUJvZSDOrsvysbh6M2MQ== +"@tiptap/extension-gapcursor@^2.0.0-beta.33": + version "2.0.0-beta.33" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.33.tgz#99414204e61655d4df61efc27823732176719532" + integrity sha512-Yu6BJ1bseyXIgLlcw/2R/2wWe1mIQilMwW7hhfDJPLbFwLJrMINtA9hxd2qY7mtW19/CveT5HOihQS6QEk59iw== dependencies: "@types/prosemirror-gapcursor" "^1.0.4" prosemirror-gapcursor "^1.2.0" -"@tiptap/extension-hard-break@^2.0.0-beta.24": - version "2.0.0-beta.24" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.24.tgz#8518cf253c24a316824fdac41ffe195fe934bfb2" - integrity sha512-0oEHUlQKQZRQmrbKARFPBVVRBWdekR27ro+qg+T6nzEHYSRkJ7dWBswGSNul1v1XEp52JqniZ3el0w1xdYMR0w== +"@tiptap/extension-hard-break@^2.0.0-beta.30": + version "2.0.0-beta.30" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.30.tgz#165494f1194a7bad08907e6d64d349dd15851b72" + integrity sha512-X9xj/S+CikrbIE7ccUFVwit5QHEbflnKVxod+4zPwr1cxogFbE9AyLZE2MpYdx3z9LcnTYYi9leBqFrP4T/Olw== -"@tiptap/extension-heading@^2.0.0-beta.18": - version "2.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.18.tgz#ffaee32ab0286ac047a3f38a52f870538155e30c" - integrity sha512-JJtB1pNHkqC9z/z/6B+xQpDd1w5EaLp++yG8eoY9NCq3ZCRhwULda+Uq7reA9D0PdEDpASsTSS2qLu8ZAtgUeA== +"@tiptap/extension-heading@^2.0.0-beta.23": + version "2.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.23.tgz#8aafadc58a8d536b7f7885e4ff0f64d30908a868" + integrity sha512-/WLymJjY+MMvee79rWHSKDBGVRw4dbBUMrFLqKLjQQBS1xS8+UW2TYzRrAH6HAH4Tc6WO39ZDbd9K9kc9wqPnA== -"@tiptap/extension-history@^2.0.0-beta.16": - version "2.0.0-beta.16" - resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.16.tgz#f40317bab795e2daf981aa1a01d6025f306be72c" - integrity sha512-nrNwV8a7zUt1t2I/kPX5Y6N9vZ8mrugimJIQmPGIp/4mmw1SEUzkaPpIsv6+ELmqMHSDktQ0ofb3pXeWDXWZvw== +"@tiptap/extension-history@^2.0.0-beta.21": + version "2.0.0-beta.21" + resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.21.tgz#5d96a17a83a7130744f0757a3275dd5b11eb1bf7" + integrity sha512-0v8Cl30V4dsabdpspLdk+f+lMoIvLFlJN5WRxtc7RRZ5gfJVxPHwooIKdvC51brfh/oJtWFCNMRjhoz0fRaF9A== dependencies: "@types/prosemirror-history" "^1.0.3" prosemirror-history "^1.2.0" -"@tiptap/extension-horizontal-rule@^2.0.0-beta.24": - version "2.0.0-beta.24" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.24.tgz#ffd6226a98a62c5314012b8c1cc60c90709d0b9c" - integrity sha512-kRHJySSJl6QgPvnD+MkN3rzwQgInbq5zE4oxPPbgqYkaAcVSL/q7JBQK1dXMMFaslQlYmEgM6Eh3oU5vo9gUdQ== +"@tiptap/extension-horizontal-rule@^2.0.0-beta.30": + version "2.0.0-beta.30" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.30.tgz#56d497f1187384d131f3f3320f30748c1e4b766f" + integrity sha512-h/PlkvfcMuoBGRfD7Cbeh8mxZiEc2pKveLDwOfCES9TKV5i2lqcIgctpohWyISuFcTq4K+OFgr910+Rsp8qwEg== dependencies: prosemirror-state "^1.3.4" -"@tiptap/extension-image@^2.0.0-beta.19": - version "2.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.19.tgz#40bd95d6d4306d28640bf60a074f20c792f375c6" - integrity sha512-17ax4H6Y+xyePfLtL5Z2V2AuWKHziukixHigA+Go4yOdEHlSDvl+x8eNYAZkxy2nH1yFW+uu7Onv8Ln/jWzqLg== +"@tiptap/extension-image@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.24.tgz#1010676f79925cbe11a44b6d8eee1251910fbc1d" + integrity sha512-7oiX/Ovj9WN4xTBqWbQWd4H3SUO2eNzOiKHebVo3eqWG8NxzOGfuU0iRCENtEa7vTiRrFgyeBotneMALDpDnTQ== -"@tiptap/extension-italic@^2.0.0-beta.19": - version "2.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.19.tgz#8f140e778e8cbd2281df6463d895d3632280c26f" - integrity sha512-gEVDqEz25glLuOPW1IOPJy/AIrTgsm164XSi9lnwS1uZa1bmEAKpoALN0+9VzSVaBOmrg2tycMo+iuOhYxFb7w== +"@tiptap/extension-italic@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.24.tgz#0a08d06dbd8dbf10f18ed17f019aa42d7ac9dbe0" + integrity sha512-pMAWFaLFb0Z0SC5pjoTKzC6m4CQOdUYeVlHvTS/550Z9lf8cqMawQ8H6Yk6brIuANyh7iUi4/zpq4H4rAdUCuw== -"@tiptap/extension-link@^2.0.0-beta.23": - version "2.0.0-beta.23" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.23.tgz#7591dfe6eb8f65548c2b6a562c2b6ec0568d23c2" - integrity sha512-XnNdu6OyB09M4Qsru5j/GsDwj/EFjLQNmGZSQIS3GoaEcxrOImohnEZBZO9WJ11A5IT2GilpRZn2wHscdKoBdA== +"@tiptap/extension-link@^2.0.0-beta.28": + version "2.0.0-beta.28" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.28.tgz#4385f36b6bb31fab34a86fb7c686ca05a37ed572" + integrity sha512-dZNaEjoDhgjmts44KqgtOYObCdDYZq/yFhsZ8QfqEgNHJMvBNTDaPXwBDW9i3BTkkyLTmwR/qwWxqDrfDdKh2A== dependencies: linkifyjs "^3.0.3" prosemirror-state "^1.3.4" -"@tiptap/extension-list-item@^2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.14.tgz#65a9ff9daa11bc9ca8bc2989a891abe68081cfbd" - integrity sha512-t6xwEqP+d5443Ul2Jvqz9kXb3ro7bA7yY9HA0vskm3120WxxHW9jxgxZN+82Ot5Tm7nXOAlsN6vuqnt4idnxZQ== - -"@tiptap/extension-ordered-list@^2.0.0-beta.19": +"@tiptap/extension-list-item@^2.0.0-beta.19": version "2.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.19.tgz#eeab2e8488b84ba7deb93b299370dc187d37c98a" - integrity sha512-PHC5pA1gohxCJF2xMXOzruPt8XWyWLun3vhJL2AIUUzUoGJmSRhsc8Wreeozdlf8HkKrqnsIuk5tp6IEsau6Pw== + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.19.tgz#657f2c5624a30f3effff723f4fadb0851a61dab8" + integrity sha512-z/5NrRKwwJc2ZkgoGxRQmA/VENxQugZoxKhUu2qoUdg5cJRcW+ERoKTiY1/AR+4M2k1izNWQMIz3nQNWMx1kQA== -"@tiptap/extension-paragraph@^2.0.0-beta.17": - version "2.0.0-beta.17" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.17.tgz#f8f0263359b95dec9c10078699697908568d9be9" - integrity sha512-qCQVCf9c2hgaeIdfy22PaoZyW5Vare/1aGkOEAaZma5RjrUbV9hrRKwoW9LsDjnh1EN1fIeKdg02yEhnHWtG8A== +"@tiptap/extension-ordered-list@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.24.tgz#69c56e2cfbf582b338d5dbc94c5eda4593775cb5" + integrity sha512-pXgwV+vuBAHMBGnUPa8fjxHapGCitfBJ1k8o3XvhotO7243Y7KOfYT7kg6XrY6dmTwCX2WLkIc912PP/E60y3A== -"@tiptap/extension-strike@^2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.21.tgz#d4b7f0d52a275bc6a4ddc60fe546495d9f77f041" - integrity sha512-uYU5k05MChVtZUwWMXAl+xp3IGx/N/+8VZaeJDlIZfg0hew+ZdEGVjwzgCQc1PAuEZriHhbpPg1yOdcHjWga8Q== +"@tiptap/extension-paragraph@^2.0.0-beta.22": + version "2.0.0-beta.22" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.22.tgz#7740fb6393296ec58e98332b2855ebdc3fd05226" + integrity sha512-BY6GWHlMvGiXLgPHcfZRUHKzMi1jKw3i1JrpMEQ8JLwYD3koI/6UOB/qphwUJkCkIPQXNkZw4/aSBxL9uChRDg== -"@tiptap/extension-subscript@^2.0.0-beta.4": - version "2.0.0-beta.4" - resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.4.tgz#07907df58695eb02bf6904d2c3635111003b30fd" - integrity sha512-eEjUXkgfeIBIgzdg3/GQGdta9Ww0Wwfiovn7ZvalRofRT4dnoiS0/83t1pQL81JT+ENow5jtx8RZHlaw/fMP4g== +"@tiptap/extension-strike@^2.0.0-beta.26": + version "2.0.0-beta.26" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.26.tgz#19eda1a61706ac9690ecbc794c711deb4e1efc3e" + integrity sha512-BA+oqqYOZzRLiMYlHX6BJXlIGaNIR9LObgkHEXAj/JPK7do4wDOcuVaw+dlWS+tzvTebLbC9GYAALfNqVBlTwA== -"@tiptap/extension-superscript@^2.0.0-beta.4": - version "2.0.0-beta.4" - resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.4.tgz#16906d71dd8f9892101cf792f42005f8cd404516" - integrity sha512-rTQCnSnloSf6UN1y3zhu6j41MxrcCVWm5JIPX8VEt60WsOXJLAc/YJHLYi2FWhh/Psq8k78sPrmZbjYUrj3Dkw== +"@tiptap/extension-subscript@^2.0.0-beta.9": + version "2.0.0-beta.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.9.tgz#4d86e904ec081384696562a5f550e81a1ddf2c76" + integrity sha512-wrmcDbXeilBW9HjJi34KpUioBCYD5zyaRu1hnIymVRwmfLNrwGpNyo/8VZTfNDdvliyMYHiRVFI4Mott6OzmgA== -"@tiptap/extension-table-cell@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.15.tgz#2059946b1657f0f22415bda84cee77fb1eb232b1" - integrity sha512-MyZHJlFOF2z4Y1DQ/uuFbDPYQxSrl/PW8TEpAh2UQI/PGVsKkjzHItTvLtWSgo7t+tuPHbEOvIBDfpQKSyfbWg== +"@tiptap/extension-superscript@^2.0.0-beta.9": + version "2.0.0-beta.9" + resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.9.tgz#be43b0e85f6440ed200831309060c3f0691811f2" + integrity sha512-BxXvCDGtIiuPmY9JbgIEjNbrFzXN3SBj178CfmDcu/FrQimwcqmsfLP3JOu1ZbUwQJuNwubx026vHXPzHlLXDA== -"@tiptap/extension-table-header@^2.0.0-beta.17": - version "2.0.0-beta.17" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.17.tgz#1bd008825146e6f5fc607a1d8682b5d47ba08f25" - integrity sha512-HctO608QQEPe29QBtxXcDfBKfv+m9u2jBJ5AYpo7HjYGfUYRxv5uQpGXX3RW/c50+AhyTF0skF6Td1jiZcEPPw== +"@tiptap/extension-table-cell@^2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.20.tgz#859456fe8d7276a87161006a3f9b3642b306524c" + integrity sha512-IllQyxLQvgm1FAewz3U+DkgNHRthmuVrtUQnG6la45qdUOLCOrpFbRRaQ1LJ/BpbvZ2Xs1o2yAa97BqZOPwovQ== -"@tiptap/extension-table-row@^2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.14.tgz#9ec98c73e309ee966b71ccd140019874d179e0c8" - integrity sha512-mewdlTqgBCyzeZIZ6F08gfuzwsiYjQ7BvABo2UhDfr0+EN2UvfJj0bT3tGgeZhMxT5Js2DXL+c+ZOVJxWJ9faQ== +"@tiptap/extension-table-header@^2.0.0-beta.22": + version "2.0.0-beta.22" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.22.tgz#a1672970d8985c383411bb21c18e71fc7e53e262" + integrity sha512-nMrghrfl+ZS4EDixs3lgXnHw1Q+ECyTugpRvS36rP7b8GFp3GXm9DfbIAUzwGGfcq1D7DwRnJUDM6ARdWXyw0w== -"@tiptap/extension-table@^2.0.0-beta.34": - version "2.0.0-beta.34" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.34.tgz#6669d7d937b981e39f7dfa04b9adbabc18f789bc" - integrity sha512-DyY+m0PcvdVwh0XynDbsHXu/omUoTzQRQPI4z035J+b2HnLQnYjviNqblY/x8/rGX4qGGjBUmKvQZgwPGg7Dfw== +"@tiptap/extension-table-row@^2.0.0-beta.19": + version "2.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.19.tgz#b45e82f29dfcc7405440ba237b069dbb93d1a94a" + integrity sha512-ldEVDpIUX7ZqbViTy4c/RfyNGRv++O/r3A/Ivuon1PykaDDTbPlp5JM89FunAD39cLAbo2HKtweqdmzCMlZsqA== + +"@tiptap/extension-table@^2.0.0-beta.42": + version "2.0.0-beta.42" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.42.tgz#bd7e2886f9f4e6d6c53fa1a5fdf24e05bd58a4af" + integrity sha512-M9vL4ZODthTSiSRn4yC/gPfPgn7fgpoIj0qm6LF0HMcZbsyDA7eH7E33Xed93OwdMaJuLeq2qqdo1Sg71AJwpQ== dependencies: prosemirror-tables "^1.1.1" - prosemirror-view "^1.20.3" + prosemirror-view "^1.22.0" -"@tiptap/extension-task-item@^2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.21.tgz#37a8d30949cefd79b80f107d426d14eb9aeaf567" - integrity sha512-7pCOc+jSlveTGIUUVQO5LOGZN20vb1fAIFMUKAxG756MMI69eVhpdqrXcmovYT8qs//DcvxC/sEjus+ZzFtKVg== +"@tiptap/extension-task-item@^2.0.0-beta.28": + version "2.0.0-beta.28" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.28.tgz#7c514ae94b4ab386f7406297e7fa0bbd5e9b5ddd" + integrity sha512-BucJLBiKUupdUF7k5Gqe73HKPgJddZTISOy+3hERt9XPGwa1iCzjwMB41X9+c8HniBDgSyI2aV0zKTT931xFfg== -"@tiptap/extension-task-list@^2.0.0-beta.18": - version "2.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.18.tgz#8b13593e3818995e7b5058bc400ee04c68314d69" - integrity sha512-EkPM+We5TP6MWwk+dH3FX/aizNRgVzHDTnnGyMisEievaazi11oKmz72svVCfF+BJJaRdVroCbeRoLWYH7by7w== +"@tiptap/extension-task-list@^2.0.0-beta.23": + version "2.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.23.tgz#ca29039de53f7315e5612bfd1fb4ca6d471b9a2f" + integrity sha512-NjAQhtWDtkDpeKtJPItNeLi1fLLVACBFMq2yRCtKBXDGYg2X5w9CYPqXzh8gAIM2qs11wIgS60UtvF2No7Crxg== -"@tiptap/extension-text@^2.0.0-beta.13": - version "2.0.0-beta.13" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.13.tgz#da0af8d9a3f149d20076e15d88c6af21fb6d940f" - integrity sha512-0EtAwuRldCAoFaL/iXgkRepEeOd55rPg5N4FQUN1xTwZT7PDofukP0DG/2jff/Uj17x4uTaJAa9qlFWuNnDvjw== +"@tiptap/extension-text@^2.0.0-beta.15": + version "2.0.0-beta.15" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.15.tgz#f08cff1b78f1c6996464dfba1fef8ec1e107617f" + integrity sha512-S3j2+HyV2gsXZP8Wg/HA+YVXQsZ3nrXgBM9HmGAxB0ESOO50l7LWfip0f3qcw1oRlh5H3iLPkA6/f7clD2/TFA== -"@tiptap/vue-2@^2.0.0-beta.60": - version "2.0.0-beta.60" - resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.60.tgz#18b1c3e7f1a94e5a047c6e25f4e176a42b68e2d1" - integrity sha512-6V2BucVL440yPy4YqCVxnaq7ZkiR9RmOtLydcNIt9/Abc/iQTkp2Q8qyDUqbGTfGfMEEdpuHd4m9ZRk3cuZ14g== +"@tiptap/vue-2@^2.0.0-beta.68": + version "2.0.0-beta.68" + resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.68.tgz#9f67bd57c9860d2263ae4f32b169f6307f7d273b" + integrity sha512-Denvq8TdO5mzOkFLbQHWpmz/90tFehN2eRBRH20lPBQyrHSW3kxLleNVf2UsmZhu9b9d83s2MrFNJp1wAkhCKg== dependencies: - "@tiptap/extension-bubble-menu" "^2.0.0-beta.42" - "@tiptap/extension-floating-menu" "^2.0.0-beta.36" - prosemirror-view "^1.20.3" + "@tiptap/extension-bubble-menu" "^2.0.0-beta.49" + "@tiptap/extension-floating-menu" "^2.0.0-beta.44" + prosemirror-view "^1.22.0" "@toast-ui/editor@^2.5.2": version "2.5.2" @@ -1934,10 +1934,10 @@ "@types/prosemirror-model" "*" "@types/prosemirror-state" "*" -"@types/prosemirror-state@*", "@types/prosemirror-state@^1.2.7": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/prosemirror-state/-/prosemirror-state-1.2.7.tgz#cd55062e4043a31e3426f47668f1d7038b5d8dfb" - integrity sha512-clJf5uw3/XQnBJtl2RqYXoLMGBySnLYl43xtDvFfQZKkLnnYcM1SDU8dcz7lWjl2Dm+H98RpLOl44pp7DYT+wA== +"@types/prosemirror-state@*", "@types/prosemirror-state@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@types/prosemirror-state/-/prosemirror-state-1.2.8.tgz#65080eeec52f63c50bf7034377f07773b4f6b2ac" + integrity sha512-mq9uyQWcpu8jeamO6Callrdvf/e1H/aRLR2kZWSpZrPHctEsxWHBbluD/wqVjXBRIOoMHLf6ZvOkrkmGLoCHVA== dependencies: "@types/prosemirror-model" "*" "@types/prosemirror-transform" "*" @@ -9781,10 +9781,10 @@ prosemirror-collab@^1.2.2: dependencies: prosemirror-state "^1.0.0" -prosemirror-commands@^1.1.11, prosemirror-commands@^1.1.4: - version "1.1.11" - resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.11.tgz#369252fcca5397ee7b011b963cc4da45b0b7cb70" - integrity sha512-uXDVkOGJbFHocdacMJihrnQCT7tHswO48ewq6ByqLxTwOrI8Y4B4aHvwUbM4epwElv/YjgC+DuqXm/gEHPym4w== +prosemirror-commands@^1.1.12, prosemirror-commands@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.12.tgz#5cb0fef4e5a0039e2fa19b42a5626af03d7c2ec3" + integrity sha512-+CrMs3w/ZVPSkR+REg8KL/clyFLv/1+SgY/OMN+CB22Z24j9TZDje72vL36lOZ/E4NeRXuiCcmENcW/vAcG67A== dependencies: prosemirror-model "^1.0.0" prosemirror-state "^1.0.0" @@ -9826,10 +9826,10 @@ prosemirror-inputrules@^1.1.2, prosemirror-inputrules@^1.1.3: prosemirror-state "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.1.3, prosemirror-keymap@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.4.tgz#8b481bf8389a5ac40d38dbd67ec3da2c7eac6a6d" - integrity sha512-Al8cVUOnDFL4gcI5IDlG6xbZ0aOD/i3B17VT+1JbHWDguCgt/lBHVTHUBcKvvbSg6+q/W4Nj1Fu6bwZSca3xjg== +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.1.4, prosemirror-keymap@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.5.tgz#b5984c7d30f5c75956c853126c54e9e624c0327b" + integrity sha512-8SZgPH3K+GLsHL2wKuwBD9rxhsbnVBTwpHCO4VUO5GmqUQlxd/2GtBVWTsyLq4Dp3N9nGgPd3+lZFKUDuVp+Vw== dependencies: prosemirror-state "^1.0.0" w3c-keyname "^2.2.0" @@ -9842,10 +9842,10 @@ prosemirror-markdown@^1.6.0: markdown-it "^10.0.0" prosemirror-model "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.13.1, prosemirror-model@^1.14.3, prosemirror-model@^1.2.0, prosemirror-model@^1.8.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.14.3.tgz#a9c250d3c4023ddf10ecb41a0a7a130e9741d37e" - integrity sha512-yzZlBaSxfUPIIP6U5Edh5zKxJPZ5f7bwZRhiCuH3UYkWhj+P3d8swHsbuAMOu/iDatDc5J/Qs5Mb3++mZf+CvQ== +prosemirror-model@^1.0.0, prosemirror-model@^1.13.1, prosemirror-model@^1.14.3, prosemirror-model@^1.15.0, prosemirror-model@^1.2.0, prosemirror-model@^1.8.1: + version "1.15.0" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.15.0.tgz#23bc09098daa7c309dba90a76a1b989ce6f61405" + integrity sha512-hQJv7SnIhlAy9ga3lhPPgaufhvCbQB9tHwscJ9E1H1pPHmN8w5V/lURueoYv9Kc3/bpNWoyHa8r3g//m7N0ChQ== dependencies: orderedmap "^1.1.0" @@ -9899,10 +9899,10 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor dependencies: prosemirror-model "^1.0.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.20.3.tgz#9781fe59cf0728e749ff4116f8a69d30d8cea943" - integrity sha512-2ImL9K/tIEk+aC2GT8shzfmT2U0Y8UQZ13L5AY0A4Tcj09o/ICGE362gKUE3Ze/Xr/nMw61Zv5JMSQUszAj9dw== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.22.0, prosemirror-view@^1.23.1: + version "1.23.1" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.23.1.tgz#ea84e685003fab655b835bf2fe834dba66d1798b" + integrity sha512-ZB0GqRqqkGvh7ggk7asFyKl3mqu3M5URBg0tf578kuP326RqL7nbIS0jEix95Vfb/U43J1T8PV6OCWQ5fZPVjg== dependencies: prosemirror-model "^1.14.3" prosemirror-state "^1.0.0" @@ -11554,10 +11554,10 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== -tippy.js@^6.3.2: - version "6.3.2" - resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.2.tgz#c03a0b88f170dffeba42f569771801dddc1f6340" - integrity sha512-35XVQI7Zl/jHZ51+8eHu/vVRXBjWYGobPm5G9FxOchj4r5dWhghKGS0nm0ARUKZTF96V7pPn7EbXS191NTwldw== +tippy.js@^6.3.6: + version "6.3.7" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" + integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== dependencies: "@popperjs/core" "^2.9.0"