Commit 1c2c9fd9 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 75ef20e2 964390f4
import { Node } from '@tiptap/core';
import { PARSE_HTML_PRIORITY_LOWEST } from '../constants';
export default Node.create({
name: 'division',
content: 'block*',
group: 'block',
defining: true,
parseHTML() {
return [{ tag: 'div', priority: PARSE_HTML_PRIORITY_LOWEST }];
},
renderHTML({ HTMLAttributes }) {
return ['div', HTMLAttributes, 0];
},
});
import { Node } from '@tiptap/core';
export default Node.create({
name: 'figure',
content: 'block+',
group: 'block',
defining: true,
parseHTML() {
return [{ tag: 'figure' }];
},
renderHTML({ HTMLAttributes }) {
return ['figure', HTMLAttributes, 0];
},
});
import { Node } from '@tiptap/core';
export default Node.create({
name: 'figureCaption',
content: 'inline*',
group: 'block',
defining: true,
parseHTML() {
return [{ tag: 'figcaption' }];
},
renderHTML({ HTMLAttributes }) {
return ['figcaption', HTMLAttributes, 0];
},
});
......@@ -8,9 +8,12 @@ import Bold from '../extensions/bold';
import BulletList from '../extensions/bullet_list';
import Code from '../extensions/code';
import CodeBlockHighlight from '../extensions/code_block_highlight';
import Division from '../extensions/division';
import Document from '../extensions/document';
import Dropcursor from '../extensions/dropcursor';
import Emoji from '../extensions/emoji';
import Figure from '../extensions/figure';
import FigureCaption from '../extensions/figure_caption';
import Gapcursor from '../extensions/gapcursor';
import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
......@@ -71,8 +74,11 @@ export const createContentEditor = ({
Code,
CodeBlockHighlight,
Document,
Division,
Dropcursor,
Emoji,
Figure,
FigureCaption,
Gapcursor,
HardBreak,
Heading,
......
......@@ -9,7 +9,10 @@ import Bold from '../extensions/bold';
import BulletList from '../extensions/bullet_list';
import Code from '../extensions/code';
import CodeBlockHighlight from '../extensions/code_block_highlight';
import Division from '../extensions/division';
import Emoji from '../extensions/emoji';
import Figure from '../extensions/figure';
import FigureCaption from '../extensions/figure_caption';
import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
import HorizontalRule from '../extensions/horizontal_rule';
......@@ -43,6 +46,7 @@ import {
renderOrderedList,
renderImage,
renderPlayable,
renderHTMLNode,
} from './serialization_helpers';
const defaultSerializerConfig = {
......@@ -116,11 +120,14 @@ const defaultSerializerConfig = {
state.write('```');
state.closeBlock(node);
},
[Division.name]: renderHTMLNode('div'),
[Emoji.name]: (state, node) => {
const { name } = node.attrs;
state.write(`:${name}:`);
},
[Figure.name]: renderHTMLNode('figure'),
[FigureCaption.name]: renderHTMLNode('figcaption'),
[HardBreak.name]: renderHardBreak,
[Heading.name]: defaultMarkdownSerializer.nodes.heading,
[HorizontalRule.name]: defaultMarkdownSerializer.nodes.horizontal_rule,
......
......@@ -24,12 +24,20 @@ export function isPlainURL(link, parent, index, side) {
return !link.isInSet(next.marks);
}
function shouldRenderCellInline(cell) {
function containsOnlyText(node) {
if (node.childCount === 1) {
const child = node.child(0);
return child.isText && child.marks.length === 0;
}
return false;
}
function containsParagraphWithOnlyText(cell) {
if (cell.childCount === 1) {
const parent = cell.child(0);
if (parent.type.name === 'paragraph' && parent.childCount === 1) {
const child = parent.child(0);
return child.isText && child.marks.length === 0;
const child = cell.child(0);
if (child.type.name === 'paragraph') {
return containsOnlyText(child);
}
}
......@@ -208,7 +216,7 @@ function renderTableRowAsHTML(state, node) {
renderTagOpen(state, tag, cell.attrs);
if (!shouldRenderCellInline(cell)) {
if (!containsParagraphWithOnlyText(cell)) {
state.closeBlock(node);
state.flushClose();
}
......@@ -222,6 +230,38 @@ function renderTableRowAsHTML(state, node) {
renderTagClose(state, 'tr');
}
export function renderContent(state, node, forceRenderInline) {
if (node.type.inlineContent) {
if (containsOnlyText(node)) {
state.renderInline(node);
} else {
state.closeBlock(node);
state.flushClose();
state.renderInline(node);
state.closeBlock(node);
state.flushClose();
}
} else {
const renderInline = forceRenderInline || containsParagraphWithOnlyText(node);
if (!renderInline) {
state.closeBlock(node);
state.flushClose();
state.renderContent(node);
state.ensureNewLine();
} else {
state.renderInline(forceRenderInline ? node : node.child(0));
}
}
}
export function renderHTMLNode(tagName, forceRenderInline = false) {
return (state, node) => {
renderTagOpen(state, tagName, node.attrs);
renderContent(state, node, forceRenderInline);
renderTagClose(state, tagName, false);
};
}
export function renderOrderedList(state, node) {
const { parens } = node.attrs;
const start = node.attrs.start || 1;
......@@ -241,7 +281,7 @@ export function renderTableCell(state, node) {
return;
}
if (!isInBlockTable(node) || shouldRenderCellInline(node)) {
if (!isInBlockTable(node) || containsParagraphWithOnlyText(node)) {
state.renderInline(node.child(0));
} else {
state.renderContent(node);
......
......@@ -1550,7 +1550,11 @@ class User < ApplicationRecord
end
def manageable_groups(include_groups_with_developer_maintainer_access: false)
owned_and_maintainer_group_hierarchy = Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
owned_and_maintainer_group_hierarchy = if Feature.enabled?(:linear_user_manageable_groups, self, default_enabled: :yaml)
owned_or_maintainers_groups.self_and_descendants
else
Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end
if include_groups_with_developer_maintainer_access
union_sql = ::Gitlab::SQL::Union.new(
......
......@@ -12,6 +12,7 @@ module Projects
@import_data = @params.delete(:import_data)
@relations_block = @params.delete(:relations_block)
@default_branch = @params.delete(:default_branch)
@readme_template = @params.delete(:readme_template)
build_topics
end
......@@ -149,12 +150,16 @@ module Projects
branch_name: @default_branch.presence || @project.default_branch_or_main,
commit_message: 'Initial commit',
file_path: 'README.md',
file_content: experiment(:new_project_readme_content, namespace: @project.namespace).run_with(@project)
file_content: readme_content
}
Files::CreateService.new(@project, current_user, commit_attrs).execute
end
def readme_content
@readme_template.presence || experiment(:new_project_readme_content, namespace: @project.namespace).run_with(@project)
end
def skip_wiki?
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
......
---
name: linear_user_manageable_groups
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68845
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339434
milestone: '14.3'
type: development
group: group::access
default_enabled: false
......@@ -40,7 +40,10 @@ export default {
return this.isEditing ? this.policyType : this.newPolicyType;
},
isEditing() {
return Boolean(this.existingPolicy?.creation_timestamp || this.existingPolicy?.updatedAt);
return Boolean(
this.existingPolicy?.creation_timestamp ||
this.existingPolicy?.type === POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.urlParameter,
);
},
policyTypes() {
return Object.values(POLICY_TYPE_COMPONENT_OPTIONS);
......
......@@ -4,6 +4,7 @@ module Security
module SecurityOrchestrationPolicies
class ProjectCreateService < ::BaseProjectService
ACCESS_LEVELS_TO_ADD = [Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER].freeze
README_TEMPLATE_PATH = Rails.root.join('ee', 'app', 'views', 'projects', 'security', 'policies', 'readme.md.tt')
def execute
return error('Security Policy project already exists.') if project.security_orchestration_policy_configuration.present?
......@@ -41,10 +42,15 @@ module Security
requirements_enabled: false,
builds_enabled: false,
wiki_enabled: false,
snippets_enabled: false
snippets_enabled: false,
readme_template: readme_template
}
end
def readme_template
ERB.new(File.read(README_TEMPLATE_PATH), trim_mode: '<>').result(binding)
end
attr_reader :project
end
end
......
# Security Policy Project for <%= @project.name %>
This project is automatically generated to manage security policies for the project.
The Security Policies Project is a repository used to store policies. All security policies are stored as a YAML file named `.gitlab/security-policies/policy.yml`, with this format:
```yaml
---
scan_execution_policy:
- name: Enforce DAST in every pipeline
description: This policy enforces pipeline configuration to have a job with DAST scan
enabled: true
rules:
- type: pipeline
branches:
- master
actions:
- scan: dast
scanner_profile: Scanner Profile A
site_profile: Site Profile B
- name: Enforce DAST in every pipeline in the main branch
description: This policy enforces pipeline configuration to have a job with DAST scan for the main branch
enabled: true
rules:
- type: pipeline
branches:
- main
actions:
- scan: dast
scanner_profile: Scanner Profile C
site_profile: Site Profile D
```
You can read more about the format and policies schema in the [documentation](https://docs.gitlab.com/ee/user/application_security/policies/#scan-execution-policies-schema).
## Default branch protection settings
This project is preconfigured with the default branch set as a protected branch, and only [project](<%= @project.web_url %>)
maintainers/owners have permission to merge into that branch. This overrides any default branch protection both at the
[group level](https://docs.gitlab.com/ee/user/group/index.html#change-the-default-branch-protection-of-a-group) and at the
[instance level](https://docs.gitlab.com/ee/user/admin_area/settings/visibility_and_access_controls.html#default-branch-protection).
......@@ -93,9 +93,9 @@ describe('PolicyEditor component', () => {
describe('when an existing policy is present', () => {
it.each`
policyType | option | existingPolicy | findComponent
${'container_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.container} | ${{ manifest: mockL3Manifest, updatedAt: '2020-04-14T00:08:30Z' }} | ${findNeworkPolicyEditor}
${'scan_execution_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution} | ${mockDastScanExecutionObject} | ${findScanExecutionPolicyEditor}
policyType | option | existingPolicy | findComponent
${'container_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.container} | ${{ manifest: mockL3Manifest, creation_timestamp: '2020-04-14T00:08:30Z' }} | ${findNeworkPolicyEditor}
${'scan_execution_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution} | ${mockDastScanExecutionObject} | ${findScanExecutionPolicyEditor}
`(
'renders the disabled form select for existing policy of type $policyType',
async ({ existingPolicy, findComponent, option, policyType }) => {
......
......@@ -6,6 +6,6 @@ import {
describe('fromYaml', () => {
it('returns policy object', () => {
expect(fromYaml(mockDastScanExecutionManifest)).toMatchObject(mockDastScanExecutionObject);
expect(fromYaml(mockDastScanExecutionManifest)).toStrictEqual(mockDastScanExecutionObject);
});
});
......@@ -57,7 +57,6 @@ rules:
- type: pipeline
branches:
- main
updatedAt: '2020-04-14T00:08:30Z'
actions:
- scan: dast
site_profile: required_site_profile
......@@ -70,7 +69,6 @@ export const mockDastScanExecutionObject = {
description: 'This policy enforces pipeline configuration to have a job with DAST scan',
enabled: false,
rules: [{ type: 'pipeline', branches: ['main'] }],
updatedAt: '2020-04-14T00:08:30Z',
actions: [
{
scan: 'dast',
......
......@@ -18,7 +18,7 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProjectCreateService do
project.add_developer(developer)
end
it 'creates policy project with maintainers and developers from target project as developers' do
it 'creates policy project with maintainers and developers from target project as developers', :aggregate_failures do
response = service.execute
policy_project = response[:policy_project]
......@@ -26,6 +26,8 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProjectCreateService do
expect(policy_project.namespace).to eq(project.namespace)
expect(policy_project.team.developers).to contain_exactly(maintainer, developer)
expect(policy_project.container_registry_access_level).to eq(ProjectFeature::DISABLED)
expect(policy_project.repository.readme.data).to include('# Security Policy Project for')
expect(policy_project.repository.readme.data).to include('## Default branch protection settings')
end
end
......
......@@ -3,7 +3,10 @@ import Bold from '~/content_editor/extensions/bold';
import BulletList from '~/content_editor/extensions/bullet_list';
import Code from '~/content_editor/extensions/code';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
import Division from '~/content_editor/extensions/division';
import Emoji from '~/content_editor/extensions/emoji';
import Figure from '~/content_editor/extensions/figure';
import FigureCaption from '~/content_editor/extensions/figure_caption';
import HardBreak from '~/content_editor/extensions/hard_break';
import Heading from '~/content_editor/extensions/heading';
import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
......@@ -38,7 +41,10 @@ const tiptapEditor = createTestEditor({
BulletList,
Code,
CodeBlockHighlight,
Division,
Emoji,
Figure,
FigureCaption,
HardBreak,
Heading,
HorizontalRule,
......@@ -68,7 +74,10 @@ const {
bulletList,
code,
codeBlock,
division,
emoji,
figure,
figureCaption,
heading,
hardBreak,
horizontalRule,
......@@ -95,7 +104,10 @@ const {
bulletList: { nodeType: BulletList.name },
code: { markType: Code.name },
codeBlock: { nodeType: CodeBlockHighlight.name },
division: { nodeType: Division.name },
emoji: { markType: Emoji.name },
figure: { nodeType: Figure.name },
figureCaption: { nodeType: FigureCaption.name },
hardBreak: { nodeType: HardBreak.name },
heading: { nodeType: Heading.name },
horizontalRule: { nodeType: HorizontalRule.name },
......@@ -533,6 +545,61 @@ this is not really json but just trying out whether this case works or not
);
});
it('correctly renders div', () => {
expect(
serialize(
division(paragraph('just a paragraph in a div')),
division(paragraph('just some ', bold('styled'), ' ', italic('content'), ' in a div')),
),
).toBe(
'<div>just a paragraph in a div</div>\n<div>\n\njust some **styled** _content_ in a div\n\n</div>',
);
});
it('correctly renders figure', () => {
expect(
serialize(
figure(
paragraph(image({ src: 'elephant.jpg', alt: 'An elephant at sunset' })),
figureCaption('An elephant at sunset'),
),
),
).toBe(
`
<figure>
![An elephant at sunset](elephant.jpg)
<figcaption>An elephant at sunset</figcaption>
</figure>
`.trim(),
);
});
it('correctly renders figure with styled caption', () => {
expect(
serialize(
figure(
paragraph(image({ src: 'elephant.jpg', alt: 'An elephant at sunset' })),
figureCaption(italic('An elephant at sunset')),
),
),
).toBe(
`
<figure>
![An elephant at sunset](elephant.jpg)
<figcaption>
_An elephant at sunset_
</figcaption>
</figure>
`.trim(),
);
});
it('correctly serializes a table with inline content', () => {
expect(
serialize(
......
......@@ -33,6 +33,32 @@
* <ruby>漢<rt>ㄏㄢˋ</rt></ruby>
* C<sub>7</sub>H<sub>16</sub> + O<sub>2</sub> → CO<sub>2</sub> + H<sub>2</sub>O
* The **Pythagorean theorem** is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>
- name: div
markdown: |-
<div>plain text</div>
<div>
just a plain ol' div, not much to _expect_!
</div>
- name: figure
markdown: |-
<figure>
![Elephant at sunset](elephant-sunset.jpg)
<figcaption>An elephant at sunset</figcaption>
</figure>
<figure>
![A crocodile wearing crocs](croc-crocs.jpg)
<figcaption>
A crocodile wearing _crocs_!
</figcaption>
</figure>
- name: link
markdown: '[GitLab](https://gitlab.com)'
- name: attachment_link
......
......@@ -1755,14 +1755,26 @@ RSpec.describe User do
end
describe '#manageable_groups' do
it 'includes all the namespaces the user can manage' do
expect(user.manageable_groups).to contain_exactly(group, subgroup)
shared_examples 'manageable groups examples' do
it 'includes all the namespaces the user can manage' do
expect(user.manageable_groups).to contain_exactly(group, subgroup)
end
it 'does not include duplicates if a membership was added for the subgroup' do
subgroup.add_owner(user)
expect(user.manageable_groups).to contain_exactly(group, subgroup)
end
end
it 'does not include duplicates if a membership was added for the subgroup' do
subgroup.add_owner(user)
it_behaves_like 'manageable groups examples'
context 'when feature flag :linear_user_manageable_groups is disabled' do
before do
stub_feature_flags(linear_user_manageable_groups: false)
end
expect(user.manageable_groups).to contain_exactly(group, subgroup)
it_behaves_like 'manageable groups examples'
end
end
......
......@@ -601,6 +601,18 @@ RSpec.describe Projects::CreateService, '#execute' do
MARKDOWN
end
end
context 'and readme_template is specified' do
before do
opts[:readme_template] = "# GitLab\nThis is customized template."
end
it_behaves_like 'creates README.md'
it 'creates README.md with specified template' do
expect(project.repository.readme.data).to include('This is customized template.')
end
end
end
end
......
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