Commit 9aeaf63f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 7c21d786 cd36ef64
......@@ -2,6 +2,7 @@
import { GlButton, GlModal } from '@gitlab/ui';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
name: 'DesignReplyForm',
......@@ -60,6 +61,9 @@ export default {
? s__('DesignManagement|Comment')
: s__('DesignManagement|Save comment');
},
markdownDocsPath() {
return helpPagePath('user/markdown');
},
},
mounted() {
this.focusInput();
......@@ -89,7 +93,7 @@ export default {
:can-attach-file="false"
:enable-autocomplete="true"
:textarea-value="value"
markdown-docs-path="/help/user/markdown"
:markdown-docs-path="markdownDocsPath"
class="bordered-box"
>
<template #textarea>
......
import { joinPaths, setUrlFragment } from '~/lib/utils/url_utility';
const HELP_PAGE_URL_ROOT = '/help/';
/**
* Generate link to a GitLab documentation page.
*
* This is designed to mirror the Ruby `help_page_path` helper function, so that
* the two can be used interchangeably.
* @param {String} path - Path to doc file relative to the doc/ directory in the GitLab repository.
* Optionally, including `.md` or `.html` prefix
* @param {String} options.anchor - Name of the anchor to scroll to on the documentation page.
*/
export const helpPagePath = (path, { anchor = '' } = {}) => {
let helpPath = joinPaths(gon.relative_url_root || '/', HELP_PAGE_URL_ROOT, path);
if (anchor) {
helpPath = setUrlFragment(helpPath, anchor);
}
return helpPath;
};
......@@ -5,12 +5,15 @@
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
.js-remove-member-modal
.project-members-page.gl-mt-3
.row.gl-mt-3
.col-lg-12
.gl-display-flex.gl-flex-wrap
- if can_manage_members
.gl-w-half.gl-xs-w-full
%h4
= _('Group members')
%p
= html_escape(_('You can invite a new member to %{strong_start}%{group_name}%{strong_end}.')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- if invite_members_allowed?(@group)
.gl-w-half.gl-xs-w-full
.gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
......
......@@ -3,7 +3,7 @@
%section.qa-deploy-tokens-settings.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded), data: { qa_selector: 'deploy_tokens_settings_content' } }
.settings-header
%h4= s_('DeployTokens|Deploy Tokens')
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
%button.gl-button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
= description
......
---
title: Update button style of expand/collapse button on Deploy Tokens page
merge_request: 51077
author: nuwe1
type: other
---
title: Add space and helper to the group members page
merge_request: 50954
author: Yogi (@yo)
type: changed
......@@ -369,6 +369,19 @@ You can combine one or more of the following:
= link_to 'Help page', help_page_path('user/permissions')
```
#### Linking to `/help` in JavaScript
To link to the documentation from a JavaScript or a Vue component, use the `helpPagePath` function from [`help_page_helper.js`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/helpers/help_page_helper.js):
```javascript
import { helpPagePath } from '~/helpers/help_page_helper';
helpPagePath('user/permissions', { anchor: 'anchor-link' })
// evaluates to '/help/user/permissions#anchor-link' for GitLab.com
```
This is preferred over static paths, as the helper also works on instances installed under a [relative URL](../../install/relative_url.md).
### GitLab `/help` tests
Several [RSpec tests](https://gitlab.com/gitlab-org/gitlab/blob/master/spec/features/help_pages_spec.rb)
......
<script>
import { GlLink, GlProgressBar } from '@gitlab/ui';
export default {
components: {
GlLink,
GlProgressBar,
},
props: {
href: {
type: String,
required: true,
},
navIconImagePath: {
type: String,
required: true,
},
percentageComplete: {
type: Number,
required: true,
},
title: {
type: String,
required: true,
},
},
};
</script>
<template>
<gl-link :title="title" :href="href">
<div class="gl-display-flex gl-flex-direction-column gl-align-items-stretch gl-w-full">
<span class="gl-display-flex gl-align-items-center">
<span class="nav-icon-container svg-container">
<img :src="navIconImagePath" width="16" class="svg" />
</span>
<span class="nav-item-name gl-white-space-normal">
{{ title }}
</span>
</span>
<span class="gl-display-flex gl-align-items-stretch gl-mt-3">
<gl-progress-bar :value="percentageComplete" class="gl-flex-grow-1" />
</span>
</div>
</gl-link>
</template>
import Vue from 'vue';
import TrialStatusWidget from './components/trial_status_widget.vue';
export default () => {
const el = document.getElementById('js-trial-status-widget');
if (!el) return undefined;
const { percentageComplete } = el.dataset;
return new Vue({
el,
render: (createElement) =>
createElement(TrialStatusWidget, {
props: {
...el.dataset,
percentageComplete: Number(percentageComplete),
},
}),
});
};
......@@ -3,6 +3,7 @@ import 'bootstrap/js/dist/modal';
import initEETrialBanner from 'ee/ee_trial_banner';
import trackNavbarEvents from 'ee/event_tracking/navbar';
import initNamespaceStorageLimitAlert from 'ee/namespace_storage_limit_alert';
import initTrialStatusWidget from 'ee/contextual_sidebar/group_trial_status_widget';
$(() => {
/**
......@@ -15,4 +16,6 @@ $(() => {
initNamespaceStorageLimitAlert();
trackNavbarEvents();
initTrialStatusWidget();
});
......@@ -54,20 +54,10 @@ export default {
},
data: () => ({
slide: 0,
textSlide: 0,
carouselImages: [
{
index: 0,
imageUrl: securityDependencyImageUrl,
},
{
index: 1,
imageUrl: securityScanningImageUrl,
},
{
index: 2,
imageUrl: securityDashboardImageUrl,
},
securityDependencyImageUrl,
securityScanningImageUrl,
securityDashboardImageUrl,
],
}),
computed: {
......@@ -88,7 +78,6 @@ export default {
label: 'security-discover-carousel',
property: `sliding${this.slide}-${slide}`,
});
this.textSlide = slide;
},
},
i18n: {
......@@ -99,24 +88,15 @@ export default {
discoverUpgradeLabel: s__('Discover|Upgrade now'),
discoverTrialLabel: s__('Discover|Start a free trial'),
carouselCaptions: [
{
index: 0,
caption: s__(
s__(
'Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services.',
),
},
{
index: 1,
caption: s__(
s__(
'Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging.',
),
},
{
index: 2,
caption: s__(
s__(
"Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely.",
),
},
],
discoverPlanCaption: s__(
'Discover|See the other features of the %{linkStart}gold plan%{linkEnd}',
......@@ -127,48 +107,40 @@ export default {
<template>
<div class="discover-box">
<h4 class="discover-title center gl-text-gray-900">
<h2 class="discover-title center gl-text-gray-900 gl-mx-auto">
{{ $options.i18n.discoverTitle }}
</h4>
</h2>
<div class="discover-carousels">
<gl-carousel
v-model="slide"
class="discover-carousel"
:no-wrap="true"
class="discover-carousel discover-image-carousel gl-mx-auto gl-text-center gl-border-solid gl-border-1 gl-bg-gray-10"
no-wrap
controls
:interval="0"
indicators
img-width="1440"
img-height="700"
@sliding-start="onSlideStart"
>
<gl-carousel-slide v-for="{ index, imageUrl } in carouselImages" :key="index" img-blank>
<img
:src="imageUrl"
class="discover-carousel-img w-100 box-shadow-default image-fluid d-block"
<gl-carousel-slide
v-for="(imageUrl, index) in carouselImages"
:key="index"
:img-src="imageUrl"
/>
</gl-carousel-slide>
</gl-carousel>
<gl-carousel
ref="textCarousel"
v-model="textSlide"
class="discover-carousel discover-text-carousel"
:no-wrap="true"
v-model="slide"
class="discover-carousel discover-text-carousel gl-mx-auto gl-text-center"
no-wrap
:interval="0"
img-width="1440"
img-height="200"
>
<gl-carousel-slide
v-for="{ index, caption } in $options.i18n.carouselCaptions"
:key="index"
img-blank
>
<p class="gl-text-gray-900 gl-text-left">
<gl-carousel-slide v-for="caption in $options.i18n.carouselCaptions" :key="caption">
<template #img>
{{ caption }}
</p>
</template>
</gl-carousel-slide>
</gl-carousel>
<div class="discover-footer d-flex flex-nowrap flex-row justify-content-between mx-auto my-0">
<p class="gl-text-gray-900 text-left mb-5">
<div class="discover-footer mx-auto my-0">
<p class="gl-text-gray-900 text-center mb-5">
<gl-sprintf :message="$options.i18n.discoverPlanCaption">
<template #link="{ content }">
<gl-link
......@@ -181,6 +153,7 @@ export default {
</gl-sprintf>
</p>
</div>
</div>
<div class="discover-buttons d-flex flex-nowrap flex-row justify-content-between mx-auto my-0">
<gl-button
class="discover-button-upgrade"
......
......@@ -178,9 +178,28 @@
}
.discover-box {
.discover-image-carousel {
border-color: $gray-50;
padding-top: 20px;
padding-bottom: 20px;
max-width: 1000px;
@media (min-width: map-get($grid-breakpoints, lg)) {
padding-top: 30px;
padding-bottom: 30px;
}
}
.discover-title {
margin: 40px auto 2px;
max-width: 500px;
margin-top: 40px;
margin-bottom: 40px;
}
.discover-carousels {
padding-left: 30px;
padding-right: 30px;
margin-left: auto;
margin-right: auto;
}
.discover-carousel-img {
......@@ -198,7 +217,7 @@
flex-wrap: nowrap;
justify-content: space-between;
margin: 0 auto;
max-width: 520px;
max-width: 280px;
}
.discover-footer {
......@@ -221,35 +240,73 @@
}
.discover-text-carousel {
max-width: 650px;
.carousel-caption {
height: 100%;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
}
.carousel-control-next-icon,
.carousel-control-prev-icon {
width: 10px;
@media (min-width: map-get($grid-breakpoints, md)) {
width: inherit;
}
}
.carousel-control-next {
right: -5%;
}
.carousel-control-prev {
left: -5%;
}
.discover-carousel {
margin: 0 auto;
max-width: 720px;
margin-bottom: 30px;
.carousel-inner {
width: 90%;
margin-left: auto;
margin-right: auto;
}
.carousel-indicators {
bottom: -20px;
@media (min-width: map-get($grid-breakpoints, lg)) {
bottom: -15px;
}
li {
background-color: $gray-200;
width: 8px;
height: 8px;
background-color: $gray-500;
width: 6px;
height: 6px;
border-radius: 100%;
margin-right: 16px;
@media (min-width: map-get($grid-breakpoints, md)) {
width: $gl-spacing-scale-3;
height: $gl-spacing-scale-3;
}
}
.active {
background-color: $gray-200;
background-color: $gray-500;
}
}
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23bababa' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23666666' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23bababa' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23666666' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
}
}
}
- return unless show_trial_status_widget?(group)
= nav_link do
= link_to group_billings_path(group), title: trial_days_remaining_in_words(group) do
.gl-display-flex.gl-flex-direction-column.gl-align-items-stretch.gl-w-full
%span.gl-display-flex.gl-align-items-center
%span.nav-icon-container.svg-container
= image_tag 'illustrations/golden_tanuki.svg', class: 'svg', size: 16
%span.nav-item-name.gl-white-space-normal
= trial_days_remaining_in_words(group)
%span.gl-display-flex.gl-align-items-stretch.gl-mt-4
.progress.gl-flex-grow-1{ value: trial_percentage_complete(group) }
.progress-bar{ role: 'progressbar', 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': trial_percentage_complete(group), style: "width: #{trial_percentage_complete(group)}%" }
#js-trial-status-widget{ data: { href: group_billings_path(group),
nav_icon_image_path: image_path('illustrations/golden_tanuki.svg'),
title: trial_days_remaining_in_words(group),
percentage_complete: trial_percentage_complete(group) } }
---
title: Update Styling of Security & Compliance Carousel
merge_request: 50877
author:
type: changed
......@@ -25,7 +25,8 @@ module EE
}.freeze
ALLOWLISTED_SIGN_IN_ROUTES = {
'sessions' => %w{create}
'sessions' => %w{create},
'oauth/tokens' => %w{create}
}.freeze
private
......@@ -90,7 +91,7 @@ module EE
end
def sign_in_route?
return unless request.post? && request.path.start_with?('/users/sign_in')
return unless request.post? && request.path.start_with?('/users/sign_in', '/oauth/token')
ALLOWLISTED_SIGN_IN_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TrialStatusWidget component matches the snapshot 1`] = `
<gl-link-stub
href="billing/path-for/group"
title="Gold Trial – 27 days left"
>
<div
class="gl-display-flex gl-flex-direction-column gl-align-items-stretch gl-w-full"
>
<span
class="gl-display-flex gl-align-items-center"
>
<span
class="nav-icon-container svg-container"
>
<img
class="svg"
src="illustrations/golden_tanuki.svg"
width="16"
/>
</span>
<span
class="nav-item-name gl-white-space-normal"
>
Gold Trial – 27 days left
</span>
</span>
<span
class="gl-display-flex gl-align-items-stretch gl-mt-3"
>
<gl-progress-bar-stub
class="gl-flex-grow-1"
value="10"
/>
</span>
</div>
</gl-link-stub>
`;
import { shallowMount } from '@vue/test-utils';
import TrialStatusWidget from 'ee/contextual_sidebar/components/trial_status_widget.vue';
describe('TrialStatusWidget component', () => {
let wrapper;
const createComponent = () => {
return shallowMount(TrialStatusWidget, {
propsData: {
href: 'billing/path-for/group',
navIconImagePath: 'illustrations/golden_tanuki.svg',
percentageComplete: 10,
title: 'Gold Trial – 27 days left',
},
});
};
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
......@@ -93,12 +93,19 @@ RSpec.shared_examples 'write access for a read-only GitLab (EE) instance in main
end
end
it "expects a POST to /users/sign_in URL to be allowed" do
response = request.post('/users/sign_in')
where(:description, :path) do
'sign in route' | '/users/sign_in'
'oauth token route' | '/oauth/token'
end
with_them do
it "expects a POST to #{description} URL to be allowed" do
response = request.post(path)
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
end
end
end
end
end
......@@ -16,20 +16,20 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
let!(:gitlab_subscription) { create(:gitlab_subscription, :active_trial, namespace: group) }
context 'when the experiment is off' do
it 'is not visible' do
it 'is not rendered' do
render
expect(rendered).not_to have_text /Gold Trial – \d+ days left/
expect(rendered).not_to have_selector '#js-trial-status-widget'
end
end
context 'when the experiment is on' do
let(:show_trial_status_widget) { true }
it 'is visible' do
it 'is rendered' do
render
expect(rendered).to have_text 'Gold Trial – 15 days left'
expect(rendered).to have_selector '#js-trial-status-widget'
end
end
end
......
......@@ -32204,6 +32204,9 @@ msgstr ""
msgid "You can invite a new member to %{project_name}."
msgstr ""
msgid "You can invite a new member to %{strong_start}%{group_name}%{strong_end}."
msgstr ""
msgid "You can invite another group to %{project_name}."
msgstr ""
......
import { helpPagePath } from '~/helpers/help_page_helper';
describe('help page helper', () => {
it.each`
relative_url_root | path | anchor | expected
${undefined} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'}
${''} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'}
${'/'} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'}
${'/gitlab'} | ${'administration/index'} | ${undefined} | ${'/gitlab/help/administration/index'}
${'/gitlab/'} | ${'administration/index'} | ${undefined} | ${'/gitlab/help/administration/index'}
${undefined} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'}
${'/'} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'}
${''} | ${'administration/index.md'} | ${undefined} | ${'/help/administration/index.md'}
${''} | ${'administration/index.md'} | ${'installing-gitlab'} | ${'/help/administration/index.md#installing-gitlab'}
${''} | ${'administration/index'} | ${'installing-gitlab'} | ${'/help/administration/index#installing-gitlab'}
${''} | ${'administration/index'} | ${'#installing-gitlab'} | ${'/help/administration/index#installing-gitlab'}
${''} | ${'/administration/index'} | ${undefined} | ${'/help/administration/index'}
${''} | ${'administration/index/'} | ${undefined} | ${'/help/administration/index/'}
${''} | ${'/administration/index/'} | ${undefined} | ${'/help/administration/index/'}
${'/'} | ${'/administration/index'} | ${undefined} | ${'/help/administration/index'}
`(
'generates correct URL when path is `$path`, relative url is `$relative_url_root` and anchor is `$anchor`',
({ relative_url_root, anchor, path, expected }) => {
window.gon = { relative_url_root };
expect(helpPagePath(path, { anchor })).toBe(expected);
},
);
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment