Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-22 15:09:51 +00:00
parent 5393d9eb26
commit d72be033db
34 changed files with 554 additions and 117 deletions

View File

@ -0,0 +1,48 @@
<script>
import { CI_CONFIG_STATUS_VALID } from '../../constants';
import CiLintResults from './ci_lint_results.vue';
export default {
components: {
CiLintResults,
},
inject: {
lintHelpPagePath: {
default: '',
},
},
props: {
ciConfig: {
type: Object,
required: true,
},
},
computed: {
isValid() {
return this.ciConfig?.status === CI_CONFIG_STATUS_VALID;
},
stages() {
return this.ciConfig?.stages || [];
},
jobs() {
return this.stages.reduce((acc, { groups, name: stageName }) => {
return acc.concat(
groups.map(({ name: groupName }) => ({
stage: stageName,
name: groupName,
})),
);
}, []);
},
},
};
</script>
<template>
<ci-lint-results
:valid="isValid"
:jobs="jobs"
:errors="ciConfig.errors"
:lint-help-page-path="lintHelpPagePath"
/>
</template>

View File

@ -10,11 +10,11 @@ const thBorderColor = 'gl-border-gray-100!';
export default {
correct: {
variant: 'success',
text: __('syntax is correct.'),
text: __('Syntax is correct.'),
},
incorrect: {
variant: 'danger',
text: __('syntax is incorrect.'),
text: __('Syntax is incorrect.'),
},
includesText: __(
'CI configuration validated, including all configuration added with the %{codeStart}includes%{codeEnd} keyword. %{link}',
@ -48,19 +48,23 @@ export default {
},
jobs: {
type: Array,
required: true,
required: false,
default: () => [],
},
errors: {
type: Array,
required: true,
required: false,
default: () => [],
},
warnings: {
type: Array,
required: true,
required: false,
default: () => [],
},
dryRun: {
type: Boolean,
required: true,
required: false,
default: false,
},
lintHelpPagePath: {
type: String,

View File

@ -14,7 +14,7 @@ export default {
},
computed: {
tagList() {
return this.item.tagList.join(', ');
return this.item.tagList?.join(', ');
},
onlyPolicy() {
return this.item.only ? this.item.only.refs.join(', ') : this.item.only;
@ -26,15 +26,15 @@ export default {
return {
beforeScript: {
show: !isEmpty(this.item.beforeScript),
content: this.item.beforeScript.join('\n'),
content: this.item.beforeScript?.join('\n'),
},
script: {
show: !isEmpty(this.item.script),
content: this.item.script.join('\n'),
content: this.item.script?.join('\n'),
},
afterScript: {
show: !isEmpty(this.item.afterScript),
content: this.item.afterScript.join('\n'),
content: this.item.afterScript?.join('\n'),
},
};
},
@ -43,7 +43,7 @@ export default {
</script>
<template>
<div>
<div data-testid="ci-lint-value">
<pre v-if="scripts.beforeScript.show" data-testid="ci-lint-before-script">{{
scripts.beforeScript.content
}}</pre>
@ -53,25 +53,25 @@ export default {
}}</pre>
<ul class="gl-list-style-none gl-pl-0 gl-mb-0">
<li>
<li v-if="tagList">
<b>{{ __('Tag list:') }}</b>
{{ tagList }}
</li>
<div v-if="!dryRun" data-testid="ci-lint-only-except">
<li>
<li v-if="onlyPolicy">
<b>{{ __('Only policy:') }}</b>
{{ onlyPolicy }}
</li>
<li>
<li v-if="exceptPolicy">
<b>{{ __('Except policy:') }}</b>
{{ exceptPolicy }}
</li>
</div>
<li>
<li v-if="item.environment">
<b>{{ __('Environment:') }}</b>
{{ item.environment }}
</li>
<li>
<li v-if="item.when">
<b>{{ __('When:') }}</b>
{{ item.when }}
<b v-if="item.allowFailure">{{ __('Allowed to fail') }}</b>

View File

@ -14,7 +14,14 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
return null;
}
const { ciConfigPath, commitSha, defaultBranch, newMergeRequestPath, projectPath } = el?.dataset;
const {
ciConfigPath,
commitSha,
defaultBranch,
newMergeRequestPath,
lintHelpPagePath,
projectPath,
} = el?.dataset;
Vue.use(VueApollo);
@ -25,6 +32,9 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
return new Vue({
el,
apolloProvider,
provide: {
lintHelpPagePath,
},
render(h) {
return h(PipelineEditorApp, {
props: {

View File

@ -5,6 +5,7 @@ import { mergeUrlParams, redirectTo, refreshCurrentPage } from '~/lib/utils/url_
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import CiLint from './components/lint/ci_lint.vue';
import CommitForm from './components/commit/commit_form.vue';
import TextEditor from './components/text_editor.vue';
@ -25,6 +26,7 @@ const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export default {
components: {
CommitForm,
CiLint,
GlAlert,
GlLoadingIcon,
GlTab,
@ -118,7 +120,7 @@ export default {
isBlobContentLoading() {
return this.$apollo.queries.content.loading;
},
isVisualizationTabLoading() {
isCiConfigDataLoading() {
return this.$apollo.queries.ciConfigData.loading;
},
isVisualizeTabActive() {
@ -161,6 +163,7 @@ export default {
defaultCommitMessage: __('Update %{sourcePath} file'),
tabEdit: s__('Pipelines|Write pipeline configuration'),
tabGraph: s__('Pipelines|Visualize'),
tabLint: s__('Pipelines|Lint'),
},
errorTexts: {
[LOAD_FAILURE_NO_REF]: s__(
@ -283,9 +286,14 @@ export default {
:lazy="!isVisualizeTabActive"
data-testid="visualization-tab"
>
<gl-loading-icon v-if="isVisualizationTabLoading" size="lg" class="gl-m-3" />
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<pipeline-graph v-else :pipeline-data="ciConfigData" />
</gl-tab>
<gl-tab :title="$options.i18n.tabLint">
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<ci-lint v-else :ci-config="ciConfigData" />
</gl-tab>
</gl-tabs>
</div>
<commit-form

View File

@ -97,4 +97,8 @@ export default {
text: s__('ProjectTemplates|GitLab Cluster Management'),
icon: '.template-option .icon-cluster_management',
},
kotlin_native_linux: {
text: s__('ProjectTemplates|Kotlin Native for Linux'),
icon: '.template-option .icon-gitlab_logo',
},
};

View File

@ -26,7 +26,7 @@ module Projects
end
def webhook_processor
::IncidentManagement::PagerDuty::ProcessWebhookService.new(project, nil, payload)
::IncidentManagement::PagerDuty::ProcessWebhookService.new(project, payload)
end
def payload

View File

@ -130,7 +130,7 @@ module Types
description: 'Users from whom a review has been requested.'
field :author, Types::UserType, null: true,
description: 'User who created this merge request'
field :participants, Types::UserType.connection_type, null: true, complexity: 5,
field :participants, Types::UserType.connection_type, null: true, complexity: 15,
description: 'Participants in the merge request. This includes the author, assignees, reviewers, and users mentioned in notes.'
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
description: 'Indicates if the currently logged in user is subscribed to this merge request'

View File

@ -2,7 +2,7 @@
module IncidentManagement
module PagerDuty
class ProcessWebhookService < BaseService
class ProcessWebhookService
include Gitlab::Utils::StrongMemoize
include IncidentManagement::Settings
@ -12,6 +12,11 @@ module IncidentManagement
# https://developer.pagerduty.com/docs/webhooks/v2-overview/#webhook-types
PAGER_DUTY_PROCESSABLE_EVENT_TYPES = %w(incident.trigger).freeze
def initialize(project, payload)
@project = project
@payload = payload
end
def execute(token)
return forbidden unless webhook_setting_active?
return unauthorized unless valid_token?(token)
@ -24,6 +29,8 @@ module IncidentManagement
private
attr_reader :project, :payload
def process_incidents
pager_duty_processable_events.each do |event|
::IncidentManagement::PagerDuty::ProcessIncidentWorker.perform_async(project.id, event['incident'])
@ -33,7 +40,7 @@ module IncidentManagement
def pager_duty_processable_events
strong_memoize(:pager_duty_processable_events) do
::PagerDuty::WebhookPayloadParser
.call(params.to_h)
.call(payload.to_h)
.filter { |msg| msg['event'].to_s.in?(PAGER_DUTY_PROCESSABLE_EVENT_TYPES) }
end
end
@ -47,7 +54,7 @@ module IncidentManagement
end
def valid_payload_size?
Gitlab::Utils::DeepSize.new(params, max_size: PAGER_DUTY_PAYLOAD_SIZE_LIMIT).valid?
Gitlab::Utils::DeepSize.new(payload, max_size: PAGER_DUTY_PAYLOAD_SIZE_LIMIT).valid?
end
def accepted

View File

@ -5,4 +5,5 @@
"default-branch" => @project.default_branch,
"commit-sha" => @project.commit ? @project.commit.sha : '',
"new-merge-request-path" => namespace_project_new_merge_request_path,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
} }

View File

@ -0,0 +1,5 @@
---
title: Project Template for Kotlin native
merge_request: 50162
author:
type: added

View File

@ -757,6 +757,43 @@ functional or does not have a leader, Patroni and by extension PostgreSQL will n
API which can be accessed via its [default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#patroni)
on each node.
### Selecting the appropriate Patroni replication method
[Review the Patroni documentation carefully](https://patroni.readthedocs.io/en/latest/SETTINGS.html#postgresql)
before making changes as **_some of the options carry a risk of potential data
loss if not fully understood_**. The [replication mode](https://patroni.readthedocs.io/en/latest/replication_modes.html)
configured determines the amount of tolerable data loss.
WARNING:
Replication is not a backup strategy! There is no replacement for a well-considered and tested backup solution.
Omnibus GitLab defaults [`synchronous_commit`](https://www.postgresql.org/docs/11/runtime-config-wal.html#GUC-SYNCHRONOUS-COMMIT) to `on`.
```ruby
postgresql['synchronous_commit'] = 'on'
gitlab['geo-postgresql']['synchronous_commit'] = 'on'
```
#### Customizing Patroni failover behavior
Omnibus GitLab exposes several options allowing more control over the [Patroni restoration process](#recovering-the-patroni-cluster).
Each option is shown below with its default value in `/etc/gitlab/gitlab.rb`.
```ruby
patroni['use_pg_rewind'] = true
patroni['remove_data_directory_on_rewind_failure'] = false
patroni['remove_data_directory_on_diverged_timelines'] = false
```
[The upstream documentation will always be more up to date](https://patroni.readthedocs.io/en/latest/SETTINGS.html#postgresql), but the table below should provide a minimal overview of functionality.
|Setting|Overview|
|-|-|
|`use_pg_rewind`|Try running `pg_rewind` on the former cluster leader before it rejoins the database cluster.|
|`remove_data_directory_on_rewind_failure`|If `pg_rewind` fails, remove the local PostgreSQL data directory and re-replicate from the current cluster leader.|
|`remove_data_directory_on_diverged_timelines`|If `pg_rewind` cannot be used and the former leader's timeline has diverged from the current one, then delete the local data directory and re-replicate from the current cluster leader.|
### Database authorization for Patroni
Patroni uses Unix socket to manage PostgreSQL instance. Therefore, the connection from the `local` socket must be trusted.

View File

@ -25731,6 +25731,11 @@ type Vulnerability implements Noteable {
"""
dismissedAt: Time
"""
The user that dismissed the vulnerability.
"""
dismissedBy: User
"""
List of external issue links related to the vulnerability
"""

View File

@ -74671,6 +74671,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dismissedBy",
"description": "The user that dismissed the vulnerability.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "externalIssueLinks",
"description": "List of external issue links related to the vulnerability",

View File

@ -3817,6 +3817,7 @@ Represents a vulnerability.
| `detectedAt` | Time! | Timestamp of when the vulnerability was first detected |
| `discussions` | DiscussionConnection! | All discussions on this noteable |
| `dismissedAt` | Time | Timestamp of when the vulnerability state was changed to dismissed |
| `dismissedBy` | User | The user that dismissed the vulnerability. |
| `externalIssueLinks` | VulnerabilityExternalIssueLinkConnection! | List of external issue links related to the vulnerability |
| `hasSolutions` | Boolean | Indicates whether there is a solution available for this vulnerability. |
| `id` | ID! | GraphQL ID of the vulnerability |

View File

@ -119,7 +119,8 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
## Naming
1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/34371)).
1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension
([#34371](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/34371)).
1. **Reference Naming**: Use PascalCase for their instances:
```javascript
@ -402,7 +403,8 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
</div>
```
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
1. When using `v-for` with `template` and there is more than one child element, the `:key` values
must be unique. It's advised to use `kebab-case` namespaces.
```html
<template v-for="(item, index) in items">
@ -468,9 +470,10 @@ Useful links:
## Vue testing
Over time, a number of programming patterns and style preferences have emerged in our efforts to effectively test Vue components.
The following guide describes some of these. **These are not strict guidelines**, but rather a collection of suggestions and
good practices that aim to provide insight into how we write Vue tests at GitLab.
Over time, a number of programming patterns and style preferences have emerged in our efforts to
effectively test Vue components. The following guide describes some of these.
**These are not strict guidelines**, but rather a collection of suggestions and good practices that
aim to provide insight into how we write Vue tests at GitLab.
### Mounting a component
@ -479,8 +482,10 @@ Typically, when testing a Vue component, the component should be "re-mounted" in
To achieve this:
1. Create a mutable `wrapper` variable inside the top-level `describe` block.
1. Mount the component using [`mount`](https://vue-test-utils.vuejs.org/api/#mount)/[`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount).
1. Reassign the resulting [`Wrapper`](https://vue-test-utils.vuejs.org/api/wrapper/#wrapper) instance to our `wrapper` variable.
1. Mount the component using [`mount`](https://vue-test-utils.vuejs.org/api/#mount)/
[`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount).
1. Reassign the resulting [`Wrapper`](https://vue-test-utils.vuejs.org/api/wrapper/#wrapper)
instance to our `wrapper` variable.
Creating a global, mutable wrapper provides a number of advantages, including the ability to:
@ -497,14 +502,16 @@ Creating a global, mutable wrapper provides a number of advantages, including th
})
```
- Use a `beforeEach` block to mount the component (see [the `createComponent` factory](#the-createcomponent-factory) for more information).
- Use a `beforeEach` block to mount the component (see
[the `createComponent` factory](#the-createcomponent-factory) for more information).
- Use an `afterEach` block to destroy the component, for example, `wrapper.destroy()`.
#### The `createComponent` factory
To avoid duplicating our mounting logic, it's useful to define a `createComponent` factory function
that we can reuse in each test block. This is a closure which should reassign our `wrapper` variable
to the result of [`mount`](https://vue-test-utils.vuejs.org/api/#mount) and [`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount):
to the result of [`mount`](https://vue-test-utils.vuejs.org/api/#mount) and
[`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount):
```javascript
import MyComponent from '~/path/to/my_component.vue';
@ -568,7 +575,8 @@ describe('MyComponent', () => {
1. Consider using a single (or a limited number of) object arguments over many arguments.
Defining single parameters for common data like `props` is okay,
but keep in mind our [JavaScript style guide](javascript.md#limit-number-of-parameters) and stay within the parameter number limit:
but keep in mind our [JavaScript style guide](javascript.md#limit-number-of-parameters) and
stay within the parameter number limit:
```javascript
// bad
@ -591,6 +599,19 @@ the mounting function (`mount` or `shallowMount`) to be used to mount the compon
function createComponent({ mountFn = shallowMount } = {}) { }
```
1. Wrap calls to `mount` and `shallowMount` in `extendedWrapper`, this exposes `wrapper.findByTestId()`:
```javascript
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { SomeComponent } from 'components/some_component.vue';
let wrapper;
const createWrapper = () => { wrapper = extendedWrapper(shallowMount(SomeComponent)); };
const someButton = () => wrapper.findByTestId('someButtonTestId');
```
### Setting component state
1. Avoid using [`setProps`](https://vue-test-utils.vuejs.org/api/wrapper/#setprops) to set
@ -609,12 +630,13 @@ component state wherever possible. Instead, set the component's
```
The exception here is when you wish to test component reactivity in some way.
For example, you may want to test the output of a component when after a particular watcher has executed.
Using `setProps` to test such behavior is okay.
For example, you may want to test the output of a component when after a particular watcher has
executed. Using `setProps` to test such behavior is okay.
### Accessing component state
1. When accessing props or attributes, prefer the `wrapper.props('myProp')` syntax over `wrapper.props().myProp`:
1. When accessing props or attributes, prefer the `wrapper.props('myProp')` syntax over
`wrapper.props().myProp` or `wrapper.vm.myProp`:
```javascript
// good
@ -626,7 +648,8 @@ component state wherever possible. Instead, set the component's
expect(wrapper.attributes('myAttr')).toBe(true);
```
1. When asserting multiple props, check the deep equality of the `props()` object with [`toEqual`](https://jestjs.io/docs/en/expect#toequalvalue):
1. When asserting multiple props, check the deep equality of the `props()` object with
[`toEqual`](https://jestjs.io/docs/en/expect#toequalvalue):
```javascript
// good
@ -642,8 +665,9 @@ component state wherever possible. Instead, set the component's
});
```
1. If you are only interested in some of the props, you can use [`toMatchObject`](https://jestjs.io/docs/en/expect#tomatchobjectobject).
Prefer `toMatchObject` over [`expect.objectContaining`](https://jestjs.io/docs/en/expect#expectobjectcontainingobject):
1. If you are only interested in some of the props, you can use
[`toMatchObject`](https://jestjs.io/docs/en/expect#tomatchobjectobject). Prefer `toMatchObject`
over [`expect.objectContaining`](https://jestjs.io/docs/en/expect#expectobjectcontainingobject):
```javascript
// good
@ -664,12 +688,24 @@ Prefer `toMatchObject` over [`expect.objectContaining`](https://jestjs.io/docs/e
The goal of this accord is to make sure we are all on the same page.
1. When writing Vue, you may not use jQuery in your application.
1. If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your application to grab data attributes using `dataset`. You can do this without jQuery.
1. If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your
application to grab data attributes using `dataset`. You can do this without jQuery.
1. You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html).
1. If an outside jQuery Event needs to be listen to inside the Vue application, you may use jQuery event listeners.
1. We avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit).
1. You may query the `window` object one time, while bootstrapping your application for application specific data (e.g. `scrollTo` is ok to access anytime). Do this access during the bootstrapping of your application.
1. You may have a temporary but immediate need to create technical debt by writing code that does not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in the first place. An issue should be created for that tech debt to evaluate it further and discuss. In the coming months you should fix that tech debt, with its priority to be determined by maintainers.
1. When creating tech debt you must write the tests for that code before hand and those tests may not be rewritten. e.g. jQuery tests rewritten to Vue tests.
1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you must use the *store pattern* which can be found in the [Vue.js documentation](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch).
1. Once you have chosen a centralized state-management solution you must use it for your entire application. i.e. Don't mix and match your state-management solutions.
1. If an outside jQuery Event needs to be listen to inside the Vue application, you may use
jQuery event listeners.
1. We avoid adding new jQuery events when they are not required. Instead of adding new jQuery
events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit).
1. You may query the `window` object one time, while bootstrapping your application for application
specific data (for example, `scrollTo` is ok to access anytime). Do this access during the
bootstrapping of your application.
1. You may have a temporary but immediate need to create technical debt by writing code that does
not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in
the first place. An issue should be created for that tech debt to evaluate it further and discuss.
In the coming months you should fix that tech debt, with its priority to be determined by maintainers.
1. When creating tech debt you must write the tests for that code before hand and those tests may
not be rewritten. For example, jQuery tests rewritten to Vue tests.
1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you
must use the *store pattern* which can be found in the
[Vue.js documentation](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch).
1. Once you have chosen a centralized state-management solution you must use it for your entire
application. Don't mix and match your state-management solutions.

View File

@ -22,7 +22,8 @@ All new features built with Vue.js must follow a [Flux architecture](https://fac
The main goal we are trying to achieve is to have only one data flow and only one data entry.
In order to achieve this goal we use [vuex](#vuex).
You can also read about this architecture in Vue docs about [state management](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch)
You can also read about this architecture in Vue docs about
[state management](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch)
and about [one way data flow](https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow).
### Components and Store
@ -62,14 +63,15 @@ Be sure to read about [page-specific JavaScript](performance.md#page-specific-ja
While mounting a Vue application, you might need to provide data from Rails to JavaScript.
To do that, you can use the `data` attributes in the HTML element and query them while mounting the application.
You should only do this while initializing the application, because the mounted element is replaced with a Vue-generated DOM.
You should only do this while initializing the application, because the mounted element is replaced
with a Vue-generated DOM.
The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function
instead of querying the DOM inside the main Vue component is avoiding the need to create a fixture or an HTML element in the unit test,
which makes the tests easier.
The advantage of providing data from the DOM to the Vue instance through `props` in the `render`
function instead of querying the DOM inside the main Vue component is avoiding the need to create a
fixture or an HTML element in the unit test, which makes the tests easier.
See the following example, also, please refer to our [Vue style guide](style/vue.md#basic-rules) for additional
information on why we explicitly declare the data being passed into the Vue app;
See the following example, also, please refer to our [Vue style guide](style/vue.md#basic-rules) for
additional information on why we explicitly declare the data being passed into the Vue app;
```javascript
// haml
@ -94,13 +96,15 @@ return new Vue({
});
```
> When adding an `id` attribute to mount a Vue application, please make sure this `id` is unique across the codebase
> When adding an `id` attribute to mount a Vue application, please make sure this `id` is unique
across the codebase.
#### Accessing the `gl` object
When we need to query the `gl` object for data that doesn't change during the application's life cycle, we should do it in the same place where we query the DOM.
By following this practice, we can avoid the need to mock the `gl` object, which makes tests easier.
It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component:
When we need to query the `gl` object for data that doesn't change during the application's life
cycle, we should do it in the same place where we query the DOM. By following this practice, we can
avoid the need to mock the `gl` object, which makes tests easier. It should be done while
initializing our Vue instance, and the data should be provided as `props` to the main component:
```javascript
return new Vue({
@ -192,13 +196,18 @@ Check this [page](vuex.md) for more details.
In the [Vue documentation](https://vuejs.org/v2/api/#Options-Data) the Data function/object is defined as follows:
> The data object for the Vue instance. Vue recursively converts its properties into getter/setters to make it “reactive”. The object must be plain: native objects such as browser API objects and prototype properties are ignored. A rule of thumb is that data should just be data - it is not recommended to observe objects with their own stateful behavior.
> The data object for the Vue instance. Vue recursively converts its properties into getter/setters
to make it “reactive”. The object must be plain: native objects such as browser API objects and
prototype properties are ignored. A rule of thumb is that data should just be data - it is not
recommended to observe objects with their own stateful behavior.
Based on the Vue guidance:
- **Do not** use or create a JavaScript class in your [data function](https://vuejs.org/v2/api/#data), such as `user: new User()`.
- **Do not** use or create a JavaScript class in your [data function](https://vuejs.org/v2/api/#data),
such as `user: new User()`.
- **Do not** add new JavaScript class implementations.
- **Do** use [GraphQL](../api_graphql_styleguide.md), [Vuex](vuex.md) or a set of components if cannot use simple primitives or objects.
- **Do** use [GraphQL](../api_graphql_styleguide.md), [Vuex](vuex.md) or a set of components if
cannot use simple primitives or objects.
- **Do** maintain existing implementations using such approaches.
- **Do** Migrate components to a pure object model when there are substantial changes to it.
- **Do** add business logic to helpers or utils, so you can test them separately from your component.
@ -209,7 +218,8 @@ There are additional reasons why having a JavaScript class presents maintainabil
- Once a class is created, it is easy to extend it in a way that can infringe Vue reactivity and best practices.
- A class adds a layer of abstraction, which makes the component API and its inner workings less clear.
- It makes it harder to test. Since the class is instantiated by the component data function, it is harder to 'manage' component and class separately.
- It makes it harder to test. Since the class is instantiated by the component data function, it is
harder to 'manage' component and class separately.
- Adding OOP to a functional codebase adds yet another way of writing code, reducing consistency and clarity.
## Style guide
@ -231,6 +241,7 @@ Here's an example of a well structured unit test for [this Vue component](#appen
```javascript
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { GlLoadingIcon } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
@ -263,19 +274,21 @@ describe('~/todos/app.vue', () => {
});
// It is very helpful to separate setting up the component from
// its collaborators (i.e. Vuex, axios, etc.)
// its collaborators (for example, Vuex and axios).
const createWrapper = (props = {}) => {
wrapper = shallowMount(App, {
propsData: {
path: TEST_TODO_PATH,
...props,
},
});
wrapper = extendedWrapper(
shallowMount(App, {
propsData: {
path: TEST_TODO_PATH,
...props,
},
})
);
};
// Helper methods greatly help test maintainability and readability.
const findLoader = () => wrapper.find(GlLoadingIcon);
const findAddButton = () => wrapper.find('[data-testid="add-button"]');
const findTextInput = () => wrapper.find('[data-testid="text-input"]');
const findAddButton = () => wrapper.findByTestId('add-button');
const findTextInput = () => wrapper.findByTestId('text-input');
const findTodoData = () => wrapper.findAll('[data-testid="todo-item"]').wrappers.map(wrapper => ({ text: wrapper.text() }));
describe('when mounted and loading', () => {
@ -323,11 +336,41 @@ describe('~/todos/app.vue', () => {
The main return value of a Vue component is the rendered output. In order to test the component we
need to test the rendered output. Visit the [Vue testing guide](https://vuejs.org/v2/guide/testing.html#Unit-Testing).
### Child components
1. Test any directive that defines if/how child component is rendered (for example, `v-if` and `v-for`).
1. Test any props we are passing to child components (especially if the prop is calculated in the
component under test, with the `computed` property, for example). Remember to use `.props()` and not `.vm.someProp`.
1. Test we react correctly to any events emitted from child components:
```javascript
const checkbox = wrapper.findByTestId('checkboxTestId');
expect(checkbox.attributes('disabled')).not.toBeDefined();
findChildComponent().vm.$emit('primary');
await nextTick();
expect(checkbox.attributes('disabled')).toBeDefined();
```
1. **Do not** test the internal implementation of the child components:
```javascript
// bad
expect(findChildComponent().find('.error-alert').exists()).toBe(false);
// good
expect(findChildComponent().props('withAlertContainer')).toBe(false);
```
### Events
We should test for events emitted in response to an action within our component, this is useful to verify the correct events are being fired with the correct arguments.
We should test for events emitted in response to an action within our component, this is useful to
verify the correct events are being fired with the correct arguments.
For any DOM events we should use [`trigger`](https://vue-test-utils.vuejs.org/api/wrapper/#trigger) to fire out event.
For any DOM events we should use [`trigger`](https://vue-test-utils.vuejs.org/api/wrapper/#trigger)
to fire out event.
```javascript
// Assuming SomeButton renders: <button>Some button</button>
@ -342,7 +385,8 @@ it('should fire the click event', () => {
})
```
When we need to fire a Vue event, we should use [`emit`](https://vuejs.org/v2/guide/components-custom-events.html) to fire our event.
When we need to fire a Vue event, we should use [`emit`](https://vuejs.org/v2/guide/components-custom-events.html)
to fire our event.
```javascript
wrapper = shallowMount(DropdownItem);
@ -355,7 +399,8 @@ it('should fire the itemClicked event', () => {
})
```
We should verify an event has been fired by asserting against the result of the [`emitted()`](https://vue-test-utils.vuejs.org/api/wrapper/#emitted) method
We should verify an event has been fired by asserting against the result of the
[`emitted()`](https://vue-test-utils.vuejs.org/api/wrapper/#emitted) method.
## Vue.js Expert Role
@ -371,7 +416,8 @@ You should only apply to be a Vue.js expert when your own merge requests and you
> This section is added temporarily to support the efforts to migrate the codebase from Vue 2.x to Vue 3.x
Currently, we recommend to minimize adding certain features to the codebase to prevent increasing the tech debt for the eventual migration:
Currently, we recommend to minimize adding certain features to the codebase to prevent increasing
the tech debt for the eventual migration:
- filters;
- event buses;
@ -382,7 +428,8 @@ You can find more details on [Migration to Vue 3](vue3_migration.md)
## Appendix - Vue component subject under test
This is the template for the example component which is tested in the [Testing Vue components](#testing-vue-components) section:
This is the template for the example component which is tested in the
[Testing Vue components](#testing-vue-components) section:
```html
<template>

View File

@ -105,6 +105,8 @@ create_table :user_configs, id: false do |t|
end
```
Setting `default: nil` will ensure a primary key sequence is not created, and since the primary key
will automatically get an index, we set `index: false` to avoid creating a duplicate.
You will also need to add the new primary key to the model:
```ruby

View File

@ -57,7 +57,7 @@ and edit labels.
### Project labels
> Showing all inherited labels [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241990) in 13.5.
> Showing all inherited labels [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241990) in GitLab 13.5.
To view the project labels list, navigate to the project and click **Issues > Labels**.
The list includes all labels that are defined at the project level, as well as all

View File

@ -8,7 +8,7 @@ type: reference, concepts
# Squash and merge
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1024) in [GitLab Starter](https://about.gitlab.com/pricing/) 8.17.
> - [Ported](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18956) to GitLab Core 11.0.
> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18956) from [GitLab Starter](https://about.gitlab.com/pricing/)to GitLab Core in 11.0.
With squash and merge you can combine all your merge request's commits into one
and retain a clean history.

View File

@ -100,6 +100,7 @@ module Gitlab
end
final_delay = 0
batch_counter = 0
model_class.each_batch(of: batch_size) do |relation, index|
start_id, end_id = relation.pluck(Arel.sql("MIN(#{primary_column_name}), MAX(#{primary_column_name})")).first
@ -112,8 +113,17 @@ module Gitlab
track_in_database(job_class_name, full_job_arguments) if track_jobs
migrate_in(final_delay, job_class_name, full_job_arguments)
batch_counter += 1
end
duration = initial_delay + delay_interval * batch_counter
say <<~SAY
Scheduled #{batch_counter} #{job_class_name} jobs with a maximum of #{batch_size} records per batch and an interval of #{delay_interval} seconds.
The migration is expected to take at least #{duration} seconds. Expect all jobs to have completed after #{Time.zone.now + duration}."
SAY
final_delay
end

View File

@ -63,7 +63,8 @@ module Gitlab
ProjectTemplate.new('salesforcedx', 'SalesforceDX', _('A project boilerplate for Salesforce App development with Salesforce Developer tools'), 'https://gitlab.com/gitlab-org/project-templates/salesforcedx'),
ProjectTemplate.new('serverless_framework', 'Serverless Framework/JS', _('A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages'), 'https://gitlab.com/gitlab-org/project-templates/serverless-framework', 'illustrations/logos/serverless_framework.svg'),
ProjectTemplate.new('jsonnet', 'Jsonnet for Dynamic Child Pipelines', _('An example showing how to use Jsonnet with GitLab dynamic child pipelines'), 'https://gitlab.com/gitlab-org/project-templates/jsonnet'),
ProjectTemplate.new('cluster_management', 'GitLab Cluster Management', _('An example project for managing Kubernetes clusters integrated with GitLab'), 'https://gitlab.com/gitlab-org/project-templates/cluster-management')
ProjectTemplate.new('cluster_management', 'GitLab Cluster Management', _('An example project for managing Kubernetes clusters integrated with GitLab'), 'https://gitlab.com/gitlab-org/project-templates/cluster-management'),
ProjectTemplate.new('kotlin_native_linux', 'Kotlin Native Linux', _('A basic template for developing Linux programs using Kotlin Native'), 'https://gitlab.com/gitlab-org/project-templates/kotlin-native-linux')
].freeze
end

View File

@ -1238,6 +1238,9 @@ msgstr ""
msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
msgstr ""
msgid "A basic template for developing Linux programs using Kotlin Native"
msgstr ""
msgid "A complete DevOps platform"
msgstr ""
@ -20485,6 +20488,9 @@ msgstr ""
msgid "Pipelines|Last Used"
msgstr ""
msgid "Pipelines|Lint"
msgstr ""
msgid "Pipelines|Loading Pipelines"
msgstr ""
@ -22093,6 +22099,9 @@ msgstr ""
msgid "ProjectTemplates|HIPAA Audit Protocol"
msgstr ""
msgid "ProjectTemplates|Kotlin Native for Linux"
msgstr ""
msgid "ProjectTemplates|Netlify/GitBook"
msgstr ""
@ -27038,6 +27047,12 @@ msgstr ""
msgid "Syncing…"
msgstr ""
msgid "Syntax is correct."
msgstr ""
msgid "Syntax is incorrect."
msgstr ""
msgid "System"
msgstr ""
@ -33810,12 +33825,6 @@ msgstr ""
msgid "suggestPipeline|Were adding a GitLab CI configuration file to add a pipeline to the project. You could create it manually, but we recommend that you start with a GitLab template that works out of the box."
msgstr ""
msgid "syntax is correct."
msgstr ""
msgid "syntax is incorrect."
msgstr ""
msgid "tag name"
msgstr ""

View File

@ -44,7 +44,7 @@
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.177.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "24.8.1",
"@gitlab/ui": "24.11.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-3",
"@rails/ujs": "^6.0.3-2",

View File

@ -34,7 +34,7 @@ RSpec.describe 'CI Lint', :js do
end
it 'parses Yaml and displays the jobs' do
expect(page).to have_content('Status: syntax is correct')
expect(page).to have_content('Status: Syntax is correct')
within "table" do
aggregate_failures do
@ -51,7 +51,7 @@ RSpec.describe 'CI Lint', :js do
let(:yaml_content) { 'value: cannot have :' }
it 'displays information about an error' do
expect(page).to have_content('Status: syntax is incorrect')
expect(page).to have_content('Status: Syntax is incorrect')
expect(page).to have_selector(content_selector, text: yaml_content)
end
end

View File

@ -33,6 +33,7 @@ describe('CI Lint Results', () => {
const findStatus = findByTestId('status');
const findOnlyExcept = findByTestId('only-except');
const findLintParameters = findAllByTestId('parameter');
const findLintValues = findAllByTestId('value');
const findBeforeScripts = findAllByTestId('before-script');
const findScripts = findAllByTestId('script');
const findAfterScripts = findAllByTestId('after-script');
@ -43,6 +44,31 @@ describe('CI Lint Results', () => {
wrapper = null;
});
describe('Empty results', () => {
it('renders with no jobs, errors or warnings defined', () => {
createComponent({ jobs: undefined, errors: undefined, warnings: undefined }, shallowMount);
expect(findTable().exists()).toBe(true);
});
it('renders when job has no properties defined', () => {
// job with no attributes such as `tagList` or `environment`
const job = {
stage: 'Stage Name',
name: 'test job',
};
createComponent({ jobs: [job] }, mount);
const param = findLintParameters().at(0);
const value = findLintValues().at(0);
expect(param.text()).toBe(`${job.stage} Job - ${job.name}`);
// This test should be updated once properties of each job are shown
// See https://gitlab.com/gitlab-org/gitlab/-/issues/291031
expect(value.text()).toBe('');
});
});
describe('Invalid results', () => {
beforeEach(() => {
createComponent({ valid: false, errors: mockErrors, warnings: mockWarnings }, mount);

View File

@ -0,0 +1,80 @@
import { shallowMount, mount } from '@vue/test-utils';
import { GlAlert, GlLink } from '@gitlab/ui';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
import { mockCiConfigQueryResponse, mockLintHelpPagePath } from '../../mock_data';
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
const getCiConfig = mergedConfig => {
const { ciConfig } = mockCiConfigQueryResponse.data;
return {
...ciConfig,
stages: unwrapStagesWithNeeds(ciConfig.stages.nodes),
...mergedConfig,
};
};
describe('~/pipeline_editor/components/lint/ci_lint.vue', () => {
let wrapper;
const createComponent = (props = {}, mountFn = shallowMount) => {
wrapper = mountFn(CiLint, {
provide: {
lintHelpPagePath: mockLintHelpPagePath,
},
propsData: {
ciConfig: getCiConfig(),
...props,
},
});
};
const findAllByTestId = selector => wrapper.findAll(`[data-testid="${selector}"]`);
const findAlert = () => wrapper.find(GlAlert);
const findLintParameters = () => findAllByTestId('ci-lint-parameter');
const findLintParameterAt = i => findLintParameters().at(i);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('Valid Results', () => {
beforeEach(() => {
createComponent({}, mount);
});
it('displays valid results', () => {
expect(findAlert().text()).toMatch('Status: Syntax is correct.');
});
it('displays link to the right help page', () => {
expect(
findAlert()
.find(GlLink)
.attributes('href'),
).toBe(mockLintHelpPagePath);
});
it('displays jobs', () => {
expect(findLintParameters()).toHaveLength(3);
expect(findLintParameterAt(0).text()).toBe('Test Job - job_test_1');
expect(findLintParameterAt(1).text()).toBe('Test Job - job_test_2');
expect(findLintParameterAt(2).text()).toBe('Build Job - job_build');
});
it('displays invalid results', () => {
createComponent(
{
ciConfig: getCiConfig({
status: CI_CONFIG_STATUS_INVALID,
}),
},
mount,
);
expect(findAlert().text()).toMatch('Status: Syntax is incorrect.');
});
});
});

View File

@ -1,24 +1,110 @@
import { CI_CONFIG_STATUS_VALID } from '~/pipeline_editor/constants';
export const mockProjectPath = 'user1/project1';
export const mockDefaultBranch = 'master';
export const mockNewMergeRequestPath = '/-/merge_requests/new';
export const mockCommitSha = 'aabbccdd';
export const mockCommitNextSha = 'eeffgghh';
export const mockLintHelpPagePath = '/-/lint-help';
export const mockCommitMessage = 'My commit message';
export const mockCiConfigPath = '.gitlab-ci.yml';
export const mockCiYml = `
job1:
stages:
- test
- build
job_test_1:
stage: test
script:
- echo "test 1"
job_test_2:
stage: test
script:
- echo "test 2"
job_build:
stage: build
script:
- echo 'test'
- echo "build"
needs: ["job_test_2"]
`;
export const mockCiConfigQueryResponse = {
data: {
ciConfig: {
errors: [],
stages: [],
status: '',
status: CI_CONFIG_STATUS_VALID,
stages: {
__typename: 'CiConfigStageConnection',
nodes: [
{
name: 'test',
groups: {
nodes: [
{
name: 'job_test_1',
jobs: {
nodes: [
{
name: 'job_test_1',
needs: { nodes: [], __typename: 'CiConfigNeedConnection' },
__typename: 'CiConfigJob',
},
],
__typename: 'CiConfigJobConnection',
},
__typename: 'CiConfigGroup',
},
{
name: 'job_test_2',
jobs: {
nodes: [
{
name: 'job_test_2',
needs: { nodes: [], __typename: 'CiConfigNeedConnection' },
__typename: 'CiConfigJob',
},
],
__typename: 'CiConfigJobConnection',
},
__typename: 'CiConfigGroup',
},
],
__typename: 'CiConfigGroupConnection',
},
__typename: 'CiConfigStage',
},
{
name: 'build',
groups: {
nodes: [
{
name: 'job_build',
jobs: {
nodes: [
{
name: 'job_build',
needs: {
nodes: [{ name: 'job_test_2', __typename: 'CiConfigNeed' }],
__typename: 'CiConfigNeedConnection',
},
__typename: 'CiConfigJob',
},
],
__typename: 'CiConfigJobConnection',
},
__typename: 'CiConfigGroup',
},
],
__typename: 'CiConfigGroupConnection',
},
__typename: 'CiConfigStage',
},
],
},
__typename: 'CiConfig',
},
},
};

View File

@ -27,7 +27,7 @@ import {
} from './mock_data';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import getCiConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
@ -112,7 +112,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
};
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
const handlers = [[getCiConfig, mockCiConfigData]];
const handlers = [[getCiConfigData, mockCiConfigData]];
const resolvers = {
Query: {
blobContent() {
@ -137,7 +137,6 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findAlert = () => wrapper.find(GlAlert);
const findBlobFailureAlert = () => wrapper.find(GlAlert);
const findTabAt = i => wrapper.findAll(GlTab).at(i);
const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
const findTextEditor = () => wrapper.find(TextEditor);
@ -227,10 +226,12 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
beforeEach(async () => {
createComponent({ mountFn: mount });
await wrapper.setData({
wrapper.setData({
content: mockCiYml,
contentModel: mockCiYml,
});
await waitForPromises();
});
it('displays content after the query loads', () => {
@ -352,7 +353,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
});
describe('when the commit fails', () => {
it('shows a the error message', async () => {
it('shows an error message', async () => {
mockMutate.mockRejectedValueOnce(new Error('commit failed'));
await submitCommit();
@ -401,7 +402,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
expect(findBlobFailureAlert().exists()).toBe(false);
expect(findAlert().exists()).toBe(false);
expect(findTextEditor().attributes('value')).toBe(mockCiYml);
});
@ -415,9 +416,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
expect(findBlobFailureAlert().text()).toBe(
'No CI file found in this repository, please add one.',
);
expect(findAlert().text()).toBe('No CI file found in this repository, please add one.');
});
it('shows a 400 error message', async () => {
@ -430,9 +429,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
expect(findBlobFailureAlert().text()).toBe(
'Repository does not have a default branch, please set one.',
);
expect(findAlert().text()).toBe('Repository does not have a default branch, please set one.');
});
it('shows a unkown error message', async () => {
@ -440,9 +437,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
createComponentWithApollo();
await waitForPromises();
expect(findBlobFailureAlert().text()).toBe(
'The CI configuration was not loaded, please try again.',
);
expect(findAlert().text()).toBe('The CI configuration was not loaded, please try again.');
});
});
});

View File

@ -11,6 +11,7 @@ RSpec.describe Gitlab::ProjectTemplate do
hexo sse_middleman gitpod_spring_petclinic nfhugo
nfjekyll nfplainhtml nfgitbook nfhexo salesforcedx
serverless_framework jsonnet cluster_management
kotlin_native_linux
]
expect(described_class.all).to be_an(Array)

View File

@ -23,7 +23,7 @@ RSpec.describe 'PagerDuty webhook' do
it 'calls PagerDuty webhook processor with correct parameters' do
make_request
expect(webhook_processor_class).to have_received(:new).with(project, nil, payload)
expect(webhook_processor_class).to have_received(:new).with(project, payload)
expect(webhook_processor).to have_received(:execute).with('VALID-TOKEN')
end

View File

@ -17,7 +17,7 @@ RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService do
let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
let(:token) { nil }
subject(:execute) { described_class.new(project, nil, webhook_payload).execute(token) }
subject(:execute) { described_class.new(project, webhook_payload).execute(token) }
context 'when PagerDuty webhook setting is active' do
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }

Binary file not shown.

View File

@ -871,16 +871,16 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@24.8.1":
version "24.8.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.8.1.tgz#eb674d19aedf9c91b9a14aa7a66397d54b199fb7"
integrity sha512-tiK1toaa8VqiGGLQCTiOJRemODusm773yVDZjWT5RtmfhsnwFXpBu6wuV3YJjPbeptuzCsX3q2a+thEswZkAZQ==
"@gitlab/ui@24.11.1":
version "24.11.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.11.1.tgz#d37dbe598288a5f85c88f1f6c8ba728f752b7d6c"
integrity sha512-7kIAqFXIc6b1sRs5+MqCGA40pJz5+y7JkAik+FA6xyxmNsIQHoM7o1L+37JQthW5xWWD68Bve62GytTtwSqSsw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
bootstrap-vue "2.13.1"
copy-to-clipboard "^3.0.8"
dompurify "^2.2.3"
dompurify "^2.2.6"
echarts "^4.2.1"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
@ -4186,7 +4186,7 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
dompurify@^2.2.3, dompurify@^2.2.6:
dompurify@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.6.tgz#54945dc5c0b45ce5ae228705777e8e59d7b2edc4"
integrity sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ==