Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5393d9eb26
commit
d72be033db
|
|
@ -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>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Project Template for Kotlin native
|
||||
merge_request: 50162
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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|We’re 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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
12
yarn.lock
12
yarn.lock
|
|
@ -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==
|
||||
|
|
|
|||
Loading…
Reference in New Issue