Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b132e99b27
commit
647e9a6eed
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { mountApplications } from '~/pages/shared/wikis/show';
|
||||
import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit';
|
||||
|
||||
mountApplications();
|
||||
mountEditApplications();
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
import { GlSkeletonLoader, GlSafeHtmlDirective, GlAlert } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { renderGFM } from '../render_gfm_facade';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlSkeletonLoader,
|
||||
GlAlert,
|
||||
},
|
||||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
props: {
|
||||
getWikiContentUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoadingContent: false,
|
||||
loadingContentFailed: false,
|
||||
content: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadWikiContent();
|
||||
},
|
||||
methods: {
|
||||
async loadWikiContent() {
|
||||
this.loadingContentFailed = false;
|
||||
this.isLoadingContent = true;
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { content },
|
||||
} = await axios.get(this.getWikiContentUrl, { params: { render_html: true } });
|
||||
this.content = content;
|
||||
|
||||
this.$nextTick()
|
||||
.then(() => {
|
||||
renderGFM(this.$refs.content);
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
message: this.$options.i18n.renderingContentFailed,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
this.loadingContentFailed = true;
|
||||
} finally {
|
||||
this.isLoadingContent = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
loadingContentFailed: __(
|
||||
'The content for this wiki page failed to load. To fix this error, reload the page.',
|
||||
),
|
||||
retryLoadingContent: __('Retry'),
|
||||
renderingContentFailed: __('The content for this wiki page failed to render.'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-skeleton-loader v-if="isLoadingContent" :width="830" :height="113">
|
||||
<rect width="540" height="16" rx="4" />
|
||||
<rect y="49" width="701" height="16" rx="4" />
|
||||
<rect y="24" width="830" height="16" rx="4" />
|
||||
<rect y="73" width="540" height="16" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
<gl-alert
|
||||
v-else-if="loadingContentFailed"
|
||||
:dismissible="false"
|
||||
variant="danger"
|
||||
:primary-button-text="$options.i18n.retryLoadingContent"
|
||||
@primaryAction="loadWikiContent"
|
||||
>
|
||||
{{ $options.i18n.loadingContentFailed }}
|
||||
</gl-alert>
|
||||
<div
|
||||
v-else-if="!loadingContentFailed && !isLoadingContent"
|
||||
ref="content"
|
||||
data-qa-selector="wiki_page_content"
|
||||
data-testid="wiki_page_content"
|
||||
class="js-wiki-page-content md"
|
||||
v-html="content /* eslint-disable-line vue/no-v-html */"
|
||||
></div>
|
||||
</template>
|
|
@ -0,0 +1,5 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export const renderGFM = (el) => {
|
||||
return $(el).renderGFM();
|
||||
};
|
|
@ -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();
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@ module Ci
|
|||
def schema
|
||||
{
|
||||
'type' => 'object',
|
||||
'required' => ['pipeline_id'],
|
||||
'properties' => {
|
||||
'pipeline_id' => { 'type' => 'integer' }
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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: "<a href=\"#{help_page_path('subscriptions/index', anchor: 'change-the-linked-namespace')}\">".html_safe, linkEnd: '</a>'.html_safe }
|
||||
.js-transfer-group-form{ data: initial_data }
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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`:
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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)}" }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue