From 647e9a6eed1d6cc29b62e432f89ed8260fd773a5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 24 Mar 2022 06:08:31 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab-ci.yml | 2 +- app/assets/javascripts/boards/boards_util.js | 2 +- .../pages/projects/wikis/show/index.js | 2 + .../shared/wikis/components/wiki_content.vue | 92 ++++++++++++++++++ .../pages/shared/wikis/render_gfm_facade.js | 5 + .../javascripts/pages/shared/wikis/show.js | 27 ++++++ app/controllers/concerns/wiki_actions.rb | 9 +- app/events/ci/pipeline_created_event.rb | 1 + app/helpers/wiki_helper.rb | 10 ++ app/views/groups/settings/_transfer.html.haml | 3 +- app/views/shared/wikis/show.html.haml | 6 +- .../development/wiki_async_load.yml | 8 ++ doc/development/event_store.md | 3 +- locale/gitlab.pot | 6 ++ .../jenkins/jenkins_build_status_spec.rb | 2 +- spec/deprecation_toolkit_env.rb | 3 + spec/events/ci/pipeline_created_event_spec.rb | 27 ++++++ spec/features/projects/wikis_spec.rb | 32 ++++-- spec/frontend/boards/boards_util_spec.js | 29 ++++-- .../wikis/components/wiki_content_spec.js | 97 +++++++++++++++++++ 20 files changed, 341 insertions(+), 25 deletions(-) create mode 100644 app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue create mode 100644 app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js create mode 100644 app/assets/javascripts/pages/shared/wikis/show.js create mode 100644 config/feature_flags/development/wiki_async_load.yml create mode 100644 spec/events/ci/pipeline_created_event_spec.rb create mode 100644 spec/frontend/pages/shared/wikis/components/wiki_content_spec.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05d4c2bb6d4..ac072f69b5b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -94,7 +94,7 @@ variables: # Run with decomposed databases by default DECOMPOSED_DB: "true" - DOCS_REVIEW_APPS_DOMAIN: "178.62.207.141.nip.io" + DOCS_REVIEW_APPS_DOMAIN: "35.193.151.162.nip.io" DOCS_GITLAB_REPO_SUFFIX: "ee" REVIEW_APPS_DOMAIN: "gitlab-review.app" diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 70afd244782..9fca9860282 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -121,7 +121,7 @@ export function formatIssueInput(issueInput, boardConfig) { : issueInput?.milestoneId, labelIds: [...labelIds, ...(labels?.map((l) => fullLabelId(l)) || [])], assigneeIds: [...assigneeIds, ...(assigneeId ? [fullUserId(assigneeId)] : [])], - weight, + weight: weight > -1 ? weight : undefined, }; } diff --git a/app/assets/javascripts/pages/projects/wikis/show/index.js b/app/assets/javascripts/pages/projects/wikis/show/index.js index c08a10122b6..7ca5f6964cd 100644 --- a/app/assets/javascripts/pages/projects/wikis/show/index.js +++ b/app/assets/javascripts/pages/projects/wikis/show/index.js @@ -1,3 +1,5 @@ +import { mountApplications } from '~/pages/shared/wikis/show'; import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit'; +mountApplications(); mountEditApplications(); diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue new file mode 100644 index 00000000000..7c23f60954a --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue @@ -0,0 +1,92 @@ + + diff --git a/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js b/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js new file mode 100644 index 00000000000..90cc2983153 --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/render_gfm_facade.js @@ -0,0 +1,5 @@ +import $ from 'jquery'; + +export const renderGFM = (el) => { + return $(el).renderGFM(); +}; diff --git a/app/assets/javascripts/pages/shared/wikis/show.js b/app/assets/javascripts/pages/shared/wikis/show.js new file mode 100644 index 00000000000..9906cb595f8 --- /dev/null +++ b/app/assets/javascripts/pages/shared/wikis/show.js @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import Wikis from './wikis'; +import WikiContent from './components/wiki_content.vue'; + +const mountWikiContentApp = () => { + const el = document.querySelector('.js-async-wiki-page-content'); + + if (el) { + const { getWikiContentUrl } = el.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + render(createElement) { + return createElement(WikiContent, { + props: { getWikiContentUrl }, + }); + }, + }); + } +}; + +export const mountApplications = () => { + // eslint-disable-next-line no-new + new Wikis(); + mountWikiContentApp(); +}; diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 714a6f280f3..3acc32510e4 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -223,7 +223,7 @@ module WikiActions def page strong_memoize(:page) do - wiki.find_page(*page_params) + wiki.find_page(*page_params, load_content: load_content?) end end @@ -310,6 +310,13 @@ module WikiActions def send_wiki_file_blob(wiki, file_blob) send_blob(wiki.repository, file_blob) end + + def load_content? + return false if params[:action] == 'history' + return false if params[:action] == 'show' && Feature.enabled?(:wiki_async_load, container, default_enabled: :yaml) + + true + end end WikiActions.prepend_mod diff --git a/app/events/ci/pipeline_created_event.rb b/app/events/ci/pipeline_created_event.rb index 8b971b63cea..2f2bee0903a 100644 --- a/app/events/ci/pipeline_created_event.rb +++ b/app/events/ci/pipeline_created_event.rb @@ -5,6 +5,7 @@ module Ci def schema { 'type' => 'object', + 'required' => ['pipeline_id'], 'properties' => { 'pipeline_id' => { 'type' => 'integer' } } diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index ba876f6cb65..79c3e3a836b 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -134,6 +134,16 @@ module WikiHelper current_user&.can?(:admin_project, container) && !container.has_confluence? end + + def wiki_page_render_api_endpoint(page) + api_v4_projects_wikis_path(wiki_page_render_api_endpoint_params(page)) + end + + private + + def wiki_page_render_api_endpoint_params(page) + { id: page.container.id, slug: ERB::Util.url_encode(page.slug), params: { version: page.version.id } } + end end WikiHelper.prepend_mod_with('WikiHelper') diff --git a/app/views/groups/settings/_transfer.html.haml b/app/views/groups/settings/_transfer.html.haml index dde8213b293..08bde4e88e2 100644 --- a/app/views/groups/settings/_transfer.html.haml +++ b/app/views/groups/settings/_transfer.html.haml @@ -13,8 +13,7 @@ %li= s_('GroupSettings|You will need to update your local repositories to point to the new location.') %li= s_("GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.") - if group.paid? - .gl-alert.gl-alert-info.gl-mb-5 - = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') + = render 'shared/global_alert', dismissible: false, alert_class: 'gl-mb-5' do .gl-alert-body = html_escape(_("This group can't be transferred because it is linked to a subscription. To transfer this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "".html_safe, linkEnd: ''.html_safe } .js-transfer-group-form{ data: initial_data } diff --git a/app/views/shared/wikis/show.html.haml b/app/views/shared/wikis/show.html.haml index e6980aae3e1..08e78f083fd 100644 --- a/app/views/shared/wikis/show.html.haml +++ b/app/views/shared/wikis/show.html.haml @@ -26,6 +26,10 @@ %div - if can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding = link_to sprite_icon('pencil', css_class: 'gl-icon'), wiki_page_path(@wiki, @page, action: :edit), title: 'Edit', role: "button", class: 'btn gl-button btn-icon btn-default js-wiki-edit', data: { qa_selector: 'edit_page_button', testid: 'wiki_edit_button' } - = render 'shared/wikis/wiki_content' + + - if Feature.enabled?(:wiki_async_load, @wiki.container, default_enabled: :yaml) + .js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } } + - else + = render 'shared/wikis/wiki_content' = render 'shared/wikis/sidebar' diff --git a/config/feature_flags/development/wiki_async_load.yml b/config/feature_flags/development/wiki_async_load.yml new file mode 100644 index 00000000000..f5b2c00518e --- /dev/null +++ b/config/feature_flags/development/wiki_async_load.yml @@ -0,0 +1,8 @@ +--- +name: wiki_async_load +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82394 +rollout_issue_url: +milestone: '14.9' +type: development +group: group::editor +default_enabled: false diff --git a/doc/development/event_store.md b/doc/development/event_store.md index 2f6acc5f6dc..967272dcf2e 100644 --- a/doc/development/event_store.md +++ b/doc/development/event_store.md @@ -293,8 +293,7 @@ in the `handle_event` method of the subscriber worker. ## Testing -A publisher doesn't must care what subscribed to the event being published. The publisher's -responsibility is to ensure that the event is published correctly. +The publisher's responsibility is to ensure that the event is published correctly. To test that an event has been published correctly, we can use the RSpec matcher `:publish_event`: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bc5ff475999..9bbb3e52c9c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -37131,6 +37131,12 @@ msgstr "" msgid "The content editor may change the markdown formatting style of the document, which may not match your original markdown style." msgstr "" +msgid "The content for this wiki page failed to load. To fix this error, reload the page." +msgstr "" + +msgid "The content for this wiki page failed to render." +msgstr "" + msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository." msgstr "" diff --git a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb index 5c39fbe2930..ea531d84634 100644 --- a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :requires_admin, :skip_live_env do + RSpec.describe 'Create', :requires_admin, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/195179', type: :flaky } do describe 'Jenkins integration' do let(:project_name) { "project_with_jenkins_#{SecureRandom.hex(4)}" } diff --git a/spec/deprecation_toolkit_env.rb b/spec/deprecation_toolkit_env.rb index fa4fdf805ec..ad861a7d58e 100644 --- a/spec/deprecation_toolkit_env.rb +++ b/spec/deprecation_toolkit_env.rb @@ -56,8 +56,11 @@ module DeprecationToolkitEnv # In this case, we recommend to add a silence together with an issue to patch or update # the dependency causing the problem. # See https://gitlab.com/gitlab-org/gitlab/-/commit/aea37f506bbe036378998916d374966c031bf347#note_647515736 + # + # - lib/gitlab/lazy.rb: https://gitlab.com/gitlab-org/gitlab/-/issues/356367 def self.allowed_kwarg_warning_paths %w[ + lib/gitlab/lazy.rb ] end diff --git a/spec/events/ci/pipeline_created_event_spec.rb b/spec/events/ci/pipeline_created_event_spec.rb new file mode 100644 index 00000000000..191c2e450dc --- /dev/null +++ b/spec/events/ci/pipeline_created_event_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PipelineCreatedEvent do + using RSpec::Parameterized::TableSyntax + + where(:data, :valid) do + { pipeline_id: 1 } | true + { pipeline_id: nil } | false + { pipeline_id: "test" } | false + {} | false + { job_id: 1 } | false + end + + with_them do + let(:event) { described_class.new(data: data) } + + it 'validates the data according to the schema' do + if valid + expect { event }.not_to raise_error + else + expect { event }.to raise_error(Gitlab::EventStore::InvalidEvent) + end + end + end +end diff --git a/spec/features/projects/wikis_spec.rb b/spec/features/projects/wikis_spec.rb index 879ffd2932b..e253d03b411 100644 --- a/spec/features/projects/wikis_spec.rb +++ b/spec/features/projects/wikis_spec.rb @@ -8,14 +8,26 @@ RSpec.describe 'Project wikis', :js do let(:wiki) { create(:project_wiki, user: user, project: project) } let(:project) { create(:project, namespace: user.namespace, creator: user) } - it_behaves_like 'User creates wiki page' - it_behaves_like 'User deletes wiki page' - it_behaves_like 'User previews wiki changes' - it_behaves_like 'User updates wiki page' - it_behaves_like 'User uses wiki shortcuts' - it_behaves_like 'User views AsciiDoc page with includes' - it_behaves_like 'User views a wiki page' - it_behaves_like 'User views wiki pages' - it_behaves_like 'User views wiki sidebar' - it_behaves_like 'User views Git access wiki page' + shared_examples 'wiki feature tests' do + it_behaves_like 'User creates wiki page' + it_behaves_like 'User deletes wiki page' + it_behaves_like 'User previews wiki changes' + it_behaves_like 'User updates wiki page' + it_behaves_like 'User uses wiki shortcuts' + it_behaves_like 'User views AsciiDoc page with includes' + it_behaves_like 'User views a wiki page' + it_behaves_like 'User views wiki pages' + it_behaves_like 'User views wiki sidebar' + it_behaves_like 'User views Git access wiki page' + end + + it_behaves_like 'wiki feature tests' + + context 'when feature flag :wiki_async_load is disabled' do + before do + stub_feature_flags(wiki_async_load: false) + end + + it_behaves_like 'wiki feature tests' + end end diff --git a/spec/frontend/boards/boards_util_spec.js b/spec/frontend/boards/boards_util_spec.js index 09e7639a873..ab3cf072357 100644 --- a/spec/frontend/boards/boards_util_spec.js +++ b/spec/frontend/boards/boards_util_spec.js @@ -1,6 +1,12 @@ import { formatIssueInput, filterVariables } from '~/boards/boards_util'; describe('formatIssueInput', () => { + const issueInput = { + labelIds: ['gid://gitlab/GroupLabel/5'], + projectPath: 'gitlab-org/gitlab-test', + id: 'gid://gitlab/Issue/11', + }; + it('correctly merges boardConfig into the issue', () => { const boardConfig = { labels: [ @@ -14,12 +20,6 @@ describe('formatIssueInput', () => { weight: 1, }; - const issueInput = { - labelIds: ['gid://gitlab/GroupLabel/5'], - projectPath: 'gitlab-org/gitlab-test', - id: 'gid://gitlab/Issue/11', - }; - const result = formatIssueInput(issueInput, boardConfig); expect(result).toEqual({ projectPath: 'gitlab-org/gitlab-test', @@ -30,6 +30,23 @@ describe('formatIssueInput', () => { weight: 1, }); }); + + it('does not add weight to input if weight is NONE', () => { + const boardConfig = { + weight: -2, // NO_WEIGHT + }; + + const result = formatIssueInput(issueInput, boardConfig); + const expected = { + projectPath: 'gitlab-org/gitlab-test', + id: 'gid://gitlab/Issue/11', + labelIds: ['gid://gitlab/GroupLabel/5'], + assigneeIds: [], + milestoneId: undefined, + }; + + expect(result).toEqual(expected); + }); }); describe('filterVariables', () => { diff --git a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js new file mode 100644 index 00000000000..365bb878485 --- /dev/null +++ b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js @@ -0,0 +1,97 @@ +import { GlSkeletonLoader, GlAlert } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import WikiContent from '~/pages/shared/wikis/components/wiki_content.vue'; +import { renderGFM } from '~/pages/shared/wikis/render_gfm_facade'; +import axios from '~/lib/utils/axios_utils'; +import httpStatus from '~/lib/utils/http_status'; +import waitForPromises from 'helpers/wait_for_promises'; + +jest.mock('~/pages/shared/wikis/render_gfm_facade'); + +describe('pages/shared/wikis/components/wiki_content', () => { + const PATH = '/test'; + let wrapper; + let mock; + + function buildWrapper(propsData = {}) { + wrapper = shallowMount(WikiContent, { + propsData: { getWikiContentUrl: PATH, ...propsData }, + stubs: { + GlSkeletonLoader, + GlAlert, + }, + }); + } + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findGlAlert = () => wrapper.findComponent(GlAlert); + const findGlSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); + const findContent = () => wrapper.find('[data-testid="wiki_page_content"]'); + + describe('when loading content', () => { + beforeEach(() => { + buildWrapper(); + }); + + it('renders skeleton loader', () => { + expect(findGlSkeletonLoader().exists()).toBe(true); + }); + + it('does not render content container or error alert', () => { + expect(findGlAlert().exists()).toBe(false); + expect(findContent().exists()).toBe(false); + }); + }); + + describe('when content loads successfully', () => { + const content = 'content'; + + beforeEach(() => { + mock.onGet(PATH, { params: { render_html: true } }).replyOnce(httpStatus.OK, { content }); + buildWrapper(); + return waitForPromises(); + }); + + it('renders content container', () => { + expect(findContent().text()).toBe(content); + }); + + it('does not render skeleton loader or error alert', () => { + expect(findGlAlert().exists()).toBe(false); + expect(findGlSkeletonLoader().exists()).toBe(false); + }); + + it('calls renderGFM after nextTick', async () => { + await nextTick(); + + expect(renderGFM).toHaveBeenCalledWith(wrapper.element); + }); + }); + + describe('when loading content fails', () => { + beforeEach(() => { + mock.onGet(PATH).replyOnce(httpStatus.INTERNAL_SERVER_ERROR, ''); + buildWrapper(); + return waitForPromises(); + }); + + it('renders error alert', () => { + expect(findGlAlert().exists()).toBe(true); + }); + + it('does not render skeleton loader or content container', () => { + expect(findContent().exists()).toBe(false); + expect(findGlSkeletonLoader().exists()).toBe(false); + }); + }); +});