Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-09-09 21:09:36 +00:00
parent 764ecdaf4d
commit cb25fb1a8e
11 changed files with 534 additions and 99 deletions

View file

@ -62,7 +62,8 @@ class ApplicationController < ActionController::Base
:bitbucket_import_enabled?, :bitbucket_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?,
:bitbucket_server_import_enabled?, :fogbugz_import_enabled?, :bitbucket_server_import_enabled?, :fogbugz_import_enabled?,
:git_import_enabled?, :gitlab_project_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?,
:manifest_import_enabled?, :phabricator_import_enabled? :manifest_import_enabled?, :phabricator_import_enabled?,
:masked_page_url
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
# concerns due to caching private data. # concerns due to caching private data.

View file

@ -16,6 +16,7 @@ module GitlabRoutingHelper
include ::Routing::SnippetsHelper include ::Routing::SnippetsHelper
include ::Routing::WikiHelper include ::Routing::WikiHelper
include ::Routing::GraphqlHelper include ::Routing::GraphqlHelper
include ::Routing::PseudonymizationHelper
included do included do
Gitlab::Routing.includes_helpers(self) Gitlab::Routing.includes_helpers(self)
end end

View file

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Routing
module PseudonymizationHelper
def masked_page_url
return unless Feature.enabled?(:mask_page_urls, type: :ops)
mask_params(Rails.application.routes.recognize_path(request.original_fullpath))
end
private
def mask_params(request_params)
return if request_params[:action] == 'new'
namespace_type = request_params[:controller].split('/')[1]
namespace_type.present? ? url_with_namespace_type(request_params, namespace_type) : url_without_namespace_type(request_params)
end
def url_without_namespace_type(request_params)
masked_url = "#{request.protocol}#{request.host_with_port}/"
masked_url += case request_params[:controller]
when 'groups'
"namespace:#{group.id}/"
when 'projects'
"namespace:#{project.namespace.id}/project:#{project.id}/"
when 'root'
''
end
masked_url
end
def url_with_namespace_type(request_params, namespace_type)
masked_url = "#{request.protocol}#{request.host_with_port}/"
if request_params.has_key?(:project_id)
masked_url += "namespace:#{project.namespace.id}/project:#{project.id}/-/#{namespace_type}/"
end
if request_params.has_key?(:id)
masked_url += namespace_type == 'blob' ? ':repository_path' : request_params[:id]
end
masked_url
end
end
end

View file

@ -0,0 +1,8 @@
---
name: mask_page_urls
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69448
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340181
milestone: '14.3'
type: ops
group: group::product intelligence
default_enabled: false

View file

@ -161,6 +161,10 @@ deprovisions
dequarantine dequarantine
dequarantined dequarantined
dequarantining dequarantining
deserialization
deserialize
deserializers
deserializes
DevOps DevOps
Dhall Dhall
disambiguates disambiguates

View file

@ -199,7 +199,7 @@ Example responses:
} }
``` ```
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) also see the `new_epic` Users on [GitLab Ultimate](https://about.gitlab.com/pricing/) also see the `new_epic`
parameter: parameter:
```json ```json

View file

@ -23,11 +23,20 @@ There are two types of redirects:
- [GitLab Pages redirects](../../user/project/pages/redirects.md), - [GitLab Pages redirects](../../user/project/pages/redirects.md),
for users who view the docs on [`docs.gitlab.com`](https://docs.gitlab.com). for users who view the docs on [`docs.gitlab.com`](https://docs.gitlab.com).
The Technical Writing team manages the [process](https://gitlab.com/gitlab-org/technical-writing/-/blob/main/.gitlab/issue_templates/tw-monthly-tasks.md) The Technical Writing team manages the [process](https://gitlab.com/gitlab-org/technical-writing/-/blob/main/.gitlab/issue_templates/tw-monthly-tasks.md)
to regularly update and [clean up the redirects](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/raketasks.md#clean-up-redirects). to regularly update and [clean up the redirects](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/raketasks.md#clean-up-redirects).
If you're a contributor, you may add a new redirect, but you don't need to delete If you're a contributor, you may add a new redirect, but you don't need to delete
the old ones. This process is automatic and handled by the Technical the old ones. This process is automatic and handled by the Technical
Writing team. Writing team.
NOTE:
If the old page you're renaming doesn't exist in a stable branch, skip the
following steps and ask a Technical Writer to add the redirect in
[`redirects.yaml`](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/content/_data/redirects.yaml).
For example, if you add a new page on the 3rd of the month and then rename it before it gets
added in the stable branch on the 18th, the old page will never be part of the internal `/help`.
In that case, you can jump straight to the
[Pages redirect](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/maintenance.md#pages-redirects).
To add a redirect: To add a redirect:

View file

@ -4,10 +4,10 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
--- ---
# Content Editor **(FREE)** # Content Editor development guidelines **(FREE)**
The Content Editor is a UI component that provides a WYSIWYG editing The Content Editor is a UI component that provides a WYSIWYG editing
experience for [GitLab Flavored Markdown](../../user/markdown.md) (GFM) in the GitLab application. experience for [GitLab Flavored Markdown](../../user/markdown.md) in the GitLab application.
It also serves as the foundation for implementing Markdown-focused editors It also serves as the foundation for implementing Markdown-focused editors
that target other engines, like static site generators. that target other engines, like static site generators.
@ -16,103 +16,339 @@ to build the Content Editor. These frameworks provide a level of abstraction on
the native the native
[`contenteditable`](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content) web technology. [`contenteditable`](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content) web technology.
## Architecture remarks ## Usage guide
At a high level, the Content Editor: Follow these instructions to include the Content Editor in a feature.
- Imports arbitrary Markdown. 1. [Include the Content Editor component](#include-the-content-editor-component).
- Renders it in a HTML editing area. 1. [Set and get Markdown](#set-and-get-markdown).
- Exports it back to Markdown with changes introduced by the user. 1. [Listen for changes](#listen-for-changes).
The Content Editor relies on the ### Include the Content Editor component
[Markdown API endpoint](../../api/markdown.md) to transform Markdown
into HTML. It sends the Markdown input to the REST API and displays the API's
HTML output in the editing area. The editor exports the content back to Markdown
using a client-side library that serializes editable documents into Markdown.
![Content Editor high level diagram](img/content_editor_highlevel_diagram.png) Import the `ContentEditor` Vue component. We recommend using asynchronous named imports to
take advantage of caching, as the ContentEditor is a big dependency.
Check the [Content Editor technical design document](https://docs.google.com/document/d/1fKOiWpdHned4KOLVOOFYVvX1euEjMP5rTntUhpapdBg)
for more information about the design decisions that drive the development of the editor.
**NOTE**: We also designed the Content Editor to be extensible. We intend to provide
more information about extension development for supporting new types of content in upcoming
milestones.
## GitLab Flavored Markdown support
The [GitLab Flavored Markdown](../../user/markdown.md) extends
the [CommonMark specification](https://spec.commonmark.org/0.29/) with support for a
variety of content types like diagrams, math expressions, and tables. Supporting
all GitLab Flavored Markdown content types in the Content Editor is a work in progress. For
the status of the ongoing development for CommonMark and GitLab Flavored Markdown support, read:
- [Basic Markdown formatting extensions](https://gitlab.com/groups/gitlab-org/-/epics/5404) epic.
- [GitLab Flavored Markdown extensions](https://gitlab.com/groups/gitlab-org/-/epics/5438) epic.
## Usage
To include the Content Editor in your feature, import the `createContentEditor` factory
function and the `ContentEditor` Vue component. `createContentEditor` sets up an instance
of [tiptap's Editor class](https://www.tiptap.dev/api/editor/) with all the necessary
extensions to support editing GitLab Flavored Markdown content. It also creates
a Markdown serializer that allows exporting tiptap's document format to Markdown.
`createContentEditor` requires a `renderMarkdown` parameter invoked
by the editor every time it needs to convert Markdown to HTML. The Content Editor
does not provide a default value for this function yet.
**NOTE**: The Content Editor is in an early development stage. Usage and development
guidelines are subject to breaking changes in the upcoming months.
```html ```html
<script> <script>
import { GlButton } from '@gitlab/ui';
import { createContentEditor, ContentEditor } from '~/content_editor';
import { __ } from '~/locale';
import createFlash from '~/flash';
export default { export default {
components: { components: {
ContentEditor, ContentEditor: () =>
GlButton, import(
/* webpackChunkName: 'content_editor' */ '~/content_editor/components/content_editor.vue'
),
}, },
data() { // rest of the component definition
return { }
contentEditor: null, </script>
} ```
},
created() {
this.contentEditor = createContentEditor({
renderMarkdown: (markdown) => Api.markdown({ text: markdown }),
});
try { The Content Editor requires two properties:
await this.contentEditor.setSerializedContent(this.content);
} catch (e) { - `renderMarkdown` is an asynchronous function that returns the response (String) of invoking the
createFlash({ [Markdown API](../../api/markdown.md).
message: __('There was an error loading content in the editor'), error: e - `uploadsPath` is a URL that points to a [GitLab upload service](../uploads.md#upload-encodings)
}); with `multipart/form-data` support.
}
}, See the [`WikiForm.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue#L207)
component for a production example of these two properties.
### Set and get Markdown
The `ContentEditor` Vue component doesn't implement Vue data binding flow (`v-model`)
because setting and getting Markdown are expensive operations. Data binding would
trigger these operations every time the user interacts with the component.
Instead, you should obtain an instance of the `ContentEditor` class by listening to the
`initialized` event:
```html
<script>
import createFlash from '~/flash';
import { __ } from '~/locale';
export default {
methods: { methods: {
async save() { async loadInitialContent(contentEditor) {
await Api.updateContent({ this.contentEditor = contentEditor;
content: this.contentEditor.getSerializedContent(),
}); try {
await this.contentEditor.setSerializedContent(this.content);
} catch (e) {
createFlash(__('Could not load initial document'));
}
},
submitChanges() {
const markdown = this.contentEditor.getSerializedContent();
}, },
}, },
}; };
</script> </script>
<template>
<content-editor
:render-markdown="renderMarkdown"
:uploads-path="pageInfo.uploadsPath"
@initialized="loadInitialContent"
/>
</template>
```
### Listen for changes
You can still react to changes in the Content Editor. Reacting to changes helps
you know if the document is empty or dirty. Use the `@change` event handler for
this purpose.
```html
<script>
export default {
data() {
return {
empty: false,
};
},
methods: {
handleContentEditorChange({ empty }) {
this.empty = empty;
}
},
};
</script>
<template> <template>
<div> <div>
<content-editor :content-editor="contentEditor" /> <content-editor
<gl-button @click="save()">Save</gl-button> :render-markdown="renderMarkdown"
:uploads-path="pageInfo.uploadsPath"
@initialized="loadInitialContent"
@change="handleContentEditorChange"
/>
<gl-button :disabled="empty" @click="submitChanges">
{{ __('Submit changes') }}
</gl-button>
</div> </div>
</template> </template>
``` ```
Call `setSerializedContent` to set initial Markdown in the Editor. This method is ## Implementation guide
asynchronous because it makes an API request to render the Markdown input.
`getSerializedContent` returns a Markdown string that represents the serialized The Content Editor is composed of three main layers:
version of the editable document.
- **The editing tools UI**, like the toolbar and the table structure editor. They
display the editor's state and mutate it by dispatching commands.
- **The Tiptap Editor object** manages the editor's state,
and exposes business logic as commands executed by the editing tools UI.
- **The Markdown serializer** transforms a Markdown source string into a ProseMirror
document and vice versa.
### Editing tools UI
The editing tools UI are Vue components that display the editor's state and
dispatch [commands](https://www.tiptap.dev/api/commands/#commands) to mutate it.
They are located in the `~/content_editor/components` directory. For example,
the **Bold** toolbar button displays the editor's state by becoming active when
the user selects bold text. This button also dispatches the `toggleBold` command
to format text as bold:
```mermaid
sequenceDiagram
participant A as Editing tools UI
participant B as Tiptap object
A->>B: queries state/dispatches commands
B--)A: notifies state changes
```
#### Node views
We implement [node views](https://www.tiptap.dev/guide/node-views/vue/#node-views-with-vue)
to provide inline editing tools for some content types, like tables and images. Node views
allow separating the presentation of a content type from its
[model](https://prosemirror.net/docs/guide/#doc.data_structures). Using a Vue component in
the presentation layer enables sophisticated editing experiences in the Content Editor.
Node views are located in `~/content_editor/components/wrappers`.
#### Dispatch commands
You can inject the Tiptap Editor object to Vue components to dispatch
commands.
NOTE:
Do not implement logic that changes the editor's
state in Vue components. Encapsulate this logic in commands, and dispatch
the command from the component's methods.
```html
<script>
export default {
inject: ['tiptapEditor'],
methods: {
execute() {
//Incorrect
const { state, view } = this.tiptapEditor.state;
const { tr, schema } = state;
tr.addMark(state.selection.from, state.selection.to, null, null, schema.mark('bold'));
// Correct
this.tiptapEditor.chain().toggleBold().focus().run();
},
}
};
</script>
<template>
```
#### Query editor's state
Use the `EditorStateObserver` renderless component to react to changes in the
editor's state, such as when the document or the selection changes. You can listen to
the following events:
- `docUpdate`
- `selectionUpdate`
- `transaction`
- `focus`
- `blur`
- `error`.
Learn more about these events in [Tiptap's event guide](https://www.tiptap.dev/api/events/).
```html
<script>
// Parts of the code has been hidden for efficiency
import EditorStateObserver from './editor_state_observer.vue';
export default {
components: {
EditorStateObserver,
},
data() {
return {
error: null,
};
},
methods: {
displayError({ message }) {
this.error = message;
},
dismissError() {
this.error = null;
},
},
};
</script>
<template>
<editor-state-observer @error="displayError">
<gl-alert v-if="error" class="gl-mb-6" variant="danger" @dismiss="dismissError">
{{ error }}
</gl-alert>
</editor-state-observer>
</template>
```
### The Tiptap editor object
The Tiptap [Editor](https://www.tiptap.dev/api/editor) class manages
the editor's state and encapsulates all the business logic that powers
the Content Editor. The Content Editor constructs a new instance of this class and
provides all the necessary extensions to support
[GitLab Flavored Markdown](../../user/markdown.md).
#### Implement new extensions
Extensions are the building blocks of the Content Editor. You can learn how to implement
new ones by reading [Tiptap's guide](https://www.tiptap.dev/guide/custom-extensions).
We recommend checking the list of built-in [nodes](https://www.tiptap.dev/api/nodes) and
[marks](https://www.tiptap.dev/api/marks) before implementing a new extension
from scratch.
Store the Content Editor extensions in the `~/content_editor/extensions` directory.
When using a Tiptap's built-in extension, wrap it in a ES6 module inside this directory:
```javascript
export { Bold as default } from '@tiptap/extension-bold';
```
Use the `extend` method to customize the Extension's behavior:
```javascript
import { HardBreak } from '@tiptap/extension-hard-break';
export default HardBreak.extend({
addKeyboardShortcuts() {
return {
'Shift-Enter': () => this.editor.commands.setHardBreak(),
};
},
});
```
#### Register extensions
Register the new extension in `~/content_editor/services/create_content_editor.js`. Import
the extension module and add it to the `builtInContentEditorExtensions` array:
```javascript
import Emoji from '../extensions/emoji';
const builtInContentEditorExtensions = [
Code,
CodeBlockHighlight,
Document,
Dropcursor,
Emoji,
// Other extensions
```
### The Markdown serializer
The Markdown Serializer transforms a Markdown String to a
[ProseMirror document](https://prosemirror.net/docs/guide/#doc) and vice versa.
#### Deserialization
Deserialization is the process of converting Markdown to a ProseMirror document.
We take advantage of ProseMirror's
[HTML parsing and serialization capabilities](https://prosemirror.net/docs/guide/#schema.serialization_and_parsing)
by first rendering the Markdown as HTML using the [Markdown API endpoint](../../api/markdown.md):
```mermaid
sequenceDiagram
participant A as Content Editor
participant E as Tiptap Object
participant B as Markdown Serializer
participant C as Markdown API
participant D as ProseMirror Parser
A->>B: deserialize(markdown)
B->>C: render(markdown)
C-->>B: html
B->>D: to document(html)
D-->>A: document
A->>E: setContent(document)
```
Deserializers live in the extension modules. Read Tiptap's
[parseHTML](https://www.tiptap.dev/guide/custom-extensions#parse-html) and
[addAttributes](https://www.tiptap.dev/guide/custom-extensions#attributes) documentation to
learn how to implement them. Titap's API is a wrapper around ProseMirror's
[schema spec API](https://prosemirror.net/docs/ref/#model.SchemaSpec).
#### Serialization
Serialization is the process of converting a ProseMirror document to Markdown. The Content
Editor uses [`prosemirror-markdown`](https://github.com/ProseMirror/prosemirror-markdown)
to serialize documents. We recommend reading the
[MarkdownSerializer](https://github.com/ProseMirror/prosemirror-markdown#class-markdownserializer)
and [MarkdownSerializerState](https://github.com/ProseMirror/prosemirror-markdown#class-markdownserializerstate)
classes documentation before implementing a serializer:
```mermaid
sequenceDiagram
participant A as Content Editor
participant B as Markdown Serializer
participant C as ProseMirror Markdown
A->>B: serialize(document)
B->>C: serialize(document, serializers)
C-->>A: markdown string
```
`prosemirror-markdown` requires implementing a serializer function for each content type supported
by the Content Editor. We implement serializers in `~/content_editor/services/markdown_serializer.js`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View file

@ -64,7 +64,7 @@ graph LR
click 1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8100542&udv=0" click 1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8100542&udv=0"
1-2["docs-lint markdown (1.5 minutes)"]; 1-2["docs-lint markdown (1.5 minutes)"];
click 1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=10224335&udv=0" click 1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=10224335&udv=0"
1-3["docs-lint links (6 minutes)"]; 1-3["docs-lint links (5 minutes)"];
click 1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356757&udv=0" click 1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356757&udv=0"
1-4["ui-docs-links lint (2.5 minutes)"]; 1-4["ui-docs-links lint (2.5 minutes)"];
click 1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=10823717&udv=1020379" click 1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=10823717&udv=1020379"
@ -104,7 +104,7 @@ graph RL;
1-18["kubesec-sast"]; 1-18["kubesec-sast"];
1-19["nodejs-scan-sast"]; 1-19["nodejs-scan-sast"];
1-20["secrets-sast"]; 1-20["secrets-sast"];
1-21["static-analysis (30 minutes)"]; 1-21["static-analysis (14 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0" click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
class 1-3 criticalPath; class 1-3 criticalPath;
@ -123,7 +123,7 @@ graph RL;
2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6; 2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6;
end end
2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (11 minutes)"]; 2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (7 minutes)"];
class 2_2-2 criticalPath; class 2_2-2 criticalPath;
click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0" click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0"
2_2-4["memory-on-boot (3.5 minutes)"]; 2_2-4["memory-on-boot (3.5 minutes)"];
@ -152,14 +152,14 @@ graph RL;
click 2_5-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations" click 2_5-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations"
end end
3_1-1["jest (16 minutes)"]; 3_1-1["jest (14.5 minutes)"];
class 3_1-1 criticalPath; class 3_1-1 criticalPath;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0" click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`"; subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
3_1-1 --> 2_2-2; 3_1-1 --> 2_2-2;
end end
3_2-1["rspec:coverage (5.3 minutes)"]; 3_2-1["rspec:coverage (4 minutes)"];
subgraph "Depends on `rspec` jobs"; subgraph "Depends on `rspec` jobs";
3_2-1 -.->|"(don't use needs because of limitations)"| 2_5-1; 3_2-1 -.->|"(don't use needs because of limitations)"| 2_5-1;
click 3_2-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7248745&udv=0" click 3_2-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7248745&udv=0"
@ -206,7 +206,7 @@ graph RL;
1-18["kubesec-sast"]; 1-18["kubesec-sast"];
1-19["nodejs-scan-sast"]; 1-19["nodejs-scan-sast"];
1-20["secrets-sast"]; 1-20["secrets-sast"];
1-21["static-analysis (30 minutes)"]; 1-21["static-analysis (14 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0" click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
class 1-3 criticalPath; class 1-3 criticalPath;
@ -226,7 +226,7 @@ graph RL;
2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6; 2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6;
end end
2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (11 minutes)"]; 2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (7 minutes)"];
class 2_2-2 criticalPath; class 2_2-2 criticalPath;
click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0" click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0"
2_2-4["memory-on-boot (3.5 minutes)"]; 2_2-4["memory-on-boot (3.5 minutes)"];
@ -263,14 +263,14 @@ graph RL;
click 2_6-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914314&udv=0" click 2_6-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914314&udv=0"
end end
3_1-1["jest (16 minutes)"]; 3_1-1["jest (14.5 minutes)"];
class 3_1-1 criticalPath; class 3_1-1 criticalPath;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0" click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`"; subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
3_1-1 --> 2_2-2; 3_1-1 --> 2_2-2;
end end
3_2-1["rspec:coverage (5.3 minutes)"]; 3_2-1["rspec:coverage (4 minutes)"];
subgraph "Depends on `rspec` jobs"; subgraph "Depends on `rspec` jobs";
3_2-1 -.->|"(don't use needs because of limitations)"| 2_5-1; 3_2-1 -.->|"(don't use needs because of limitations)"| 2_5-1;
click 3_2-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7248745&udv=0" click 3_2-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7248745&udv=0"
@ -283,7 +283,7 @@ graph RL;
click 4_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910777&udv=0" click 4_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910777&udv=0"
end end
3_3-1["review-deploy (10.5 minutes)"]; 3_3-1["review-deploy (9 minutes)"];
subgraph "Played by `review-build-cng`"; subgraph "Played by `review-build-cng`";
3_3-1 --> 2_6-1; 3_3-1 --> 2_6-1;
class 3_3-1 criticalPath; class 3_3-1 criticalPath;
@ -332,7 +332,7 @@ graph RL;
1-18["kubesec-sast"]; 1-18["kubesec-sast"];
1-19["nodejs-scan-sast"]; 1-19["nodejs-scan-sast"];
1-20["secrets-sast"]; 1-20["secrets-sast"];
1-21["static-analysis (30 minutes)"]; 1-21["static-analysis (14 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0" click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
class 1-5 criticalPath; class 1-5 criticalPath;
@ -350,7 +350,7 @@ graph RL;
class 2_3-1 criticalPath; class 2_3-1 criticalPath;
end end
2_4-1["package-and-qa (140 minutes)"]; 2_4-1["package-and-qa (113 minutes)"];
subgraph "Needs `build-qa-image` & `build-assets-image`"; subgraph "Needs `build-qa-image` & `build-assets-image`";
2_4-1 --> 1-2 & 2_3-1; 2_4-1 --> 1-2 & 2_3-1;
class 2_4-1 criticalPath; class 2_4-1 criticalPath;

View file

@ -0,0 +1,126 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Routing::PseudonymizationHelper do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
stub_feature_flags(mask_page_urls: true)
allow(helper).to receive(:group).and_return(group)
allow(helper).to receive(:project).and_return(project)
end
shared_examples 'masked url' do
it 'generates masked page url' do
expect(helper.masked_page_url).to eq(masked_url)
end
end
describe 'when url has params to mask' do
context 'with controller for MR' do
let(:masked_url) { "http://test.host/namespace:#{group.id}/project:#{project.id}/-/merge_requests/#{merge_request.id}" }
before do
allow(Rails.application.routes).to receive(:recognize_path).and_return({
controller: "projects/merge_requests",
action: "show",
namespace_id: group.name,
project_id: project.name,
id: merge_request.id.to_s
})
end
it_behaves_like 'masked url'
end
context 'with controller for issue' do
let(:masked_url) { "http://test.host/namespace:#{group.id}/project:#{project.id}/-/issues/#{issue.id}" }
before do
allow(Rails.application.routes).to receive(:recognize_path).and_return({
controller: "projects/issues",
action: "show",
namespace_id: group.name,
project_id: project.name,
id: issue.id.to_s
})
end
it_behaves_like 'masked url'
end
context 'with controller for groups with subgroups and project' do
let(:masked_url) { "http://test.host/namespace:#{subgroup.id}/project:#{project.id}/"}
before do
allow(helper).to receive(:group).and_return(subgroup)
allow(helper.project).to receive(:namespace).and_return(subgroup)
allow(Rails.application.routes).to receive(:recognize_path).and_return({
controller: 'projects',
action: 'show',
namespace_id: subgroup.name,
id: project.name
})
end
it_behaves_like 'masked url'
end
context 'with controller for groups and subgroups' do
let(:masked_url) { "http://test.host/namespace:#{subgroup.id}/"}
before do
allow(helper).to receive(:group).and_return(subgroup)
allow(Rails.application.routes).to receive(:recognize_path).and_return({
controller: 'groups',
action: 'show',
id: subgroup.name
})
end
it_behaves_like 'masked url'
end
context 'with controller for blob with file path' do
let(:masked_url) { "http://test.host/namespace:#{group.id}/project:#{project.id}/-/blob/:repository_path" }
before do
allow(Rails.application.routes).to receive(:recognize_path).and_return({
controller: 'projects/blob',
action: 'show',
namespace_id: group.name,
project_id: project.name,
id: 'master/README.md'
})
end
it_behaves_like 'masked url'
end
end
describe 'when url has no params to mask' do
let(:root_url) { 'http://test.host/' }
context 'returns root url' do
it 'masked_page_url' do
expect(helper.masked_page_url).to eq(root_url)
end
end
end
describe 'when feature flag is disabled' do
before do
stub_feature_flags(mask_page_urls: false)
end
it 'returns nil' do
expect(helper.masked_page_url).to be_nil
end
end
end