Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1cfa0679b4
commit
36545041d6
24 changed files with 213 additions and 304 deletions
|
@ -88,7 +88,7 @@ For more information please see the [architecture](https://docs.gitlab.com/ee/de
|
|||
|
||||
## UX design
|
||||
|
||||
Please adhere to the [UX Guide](doc/development/ux_guide/index.md) when creating designs and implementing code.
|
||||
Please adhere to the [UX Guide](https://design.gitlab.com/) when creating designs and implementing code.
|
||||
|
||||
## Third-party applications
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ export default {
|
|||
<input name="service[active]" type="hidden" :value="activated || false" />
|
||||
<gl-form-checkbox
|
||||
v-model="activated"
|
||||
name="service[active]"
|
||||
class="gl-display-block"
|
||||
:disabled="isInheriting"
|
||||
@change="onChange"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import PipelineStatus from './pipeline_status.vue';
|
||||
import ValidationSegment from './validation_segment.vue';
|
||||
|
||||
|
@ -29,7 +28,6 @@ export default {
|
|||
PipelineStatus,
|
||||
ValidationSegment,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
ciConfigData: {
|
||||
type: Object,
|
||||
|
@ -42,7 +40,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
showPipelineStatus() {
|
||||
return this.glFeatures.pipelineStatusForPipelineEditor && !this.isNewCiConfigFile;
|
||||
return !this.isNewCiConfigFile;
|
||||
},
|
||||
// make sure corners are rounded correctly depending on if
|
||||
// pipeline status is rendered
|
||||
|
|
|
@ -15,6 +15,7 @@ export const createUniqueLinkId = (stageName, jobName) => `${stageName}-${jobNam
|
|||
|
||||
export const generateLinksData = ({ links }, containerID, modifier = '') => {
|
||||
const containerEl = document.getElementById(containerID);
|
||||
|
||||
return links.map((link) => {
|
||||
const path = d3.path();
|
||||
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
<script>
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
|
||||
PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
|
||||
PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
|
||||
PIPELINES_DETAIL_LINK_DURATION,
|
||||
PIPELINES_DETAIL_LINKS_TOTAL,
|
||||
PIPELINES_DETAIL_LINKS_JOB_RATIO,
|
||||
} from '~/performance/constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
import { DRAW_FAILURE } from '../../constants';
|
||||
import { createJobsHash, generateJobNeedsDict, reportToSentry } from '../../utils';
|
||||
import { STAGE_VIEW } from '../graph/constants';
|
||||
import { parseData } from '../parsing_utils';
|
||||
import { reportPerformance } from './api';
|
||||
import { generateLinksData } from './drawing_utils';
|
||||
|
||||
export default {
|
||||
|
@ -28,6 +17,10 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
parsedData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
pipelineId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
@ -36,15 +29,6 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
totalGroups: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
metricsConfig: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
defaultLinkColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -65,13 +49,9 @@ export default {
|
|||
return {
|
||||
links: [],
|
||||
needsObject: null,
|
||||
parsedData: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldCollectMetrics() {
|
||||
return this.metricsConfig.collectMetrics && this.metricsConfig.path;
|
||||
},
|
||||
hasHighlightedJob() {
|
||||
return Boolean(this.highlightedJob);
|
||||
},
|
||||
|
@ -115,13 +95,16 @@ export default {
|
|||
highlightedJobs(jobs) {
|
||||
this.$emit('highlightedJobsChange', jobs);
|
||||
},
|
||||
parsedData() {
|
||||
this.calculateLinkData();
|
||||
},
|
||||
viewType() {
|
||||
/*
|
||||
We need to wait a tick so that the layout reflows
|
||||
before the links refresh.
|
||||
*/
|
||||
this.$nextTick(() => {
|
||||
this.refreshLinks();
|
||||
this.calculateLinkData();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -129,69 +112,21 @@ export default {
|
|||
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
|
||||
},
|
||||
mounted() {
|
||||
if (!isEmpty(this.pipelineData)) {
|
||||
this.prepareLinkData();
|
||||
if (!isEmpty(this.parsedData)) {
|
||||
this.calculateLinkData();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beginPerfMeasure() {
|
||||
if (this.shouldCollectMetrics) {
|
||||
performanceMarkAndMeasure({ mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START });
|
||||
}
|
||||
},
|
||||
finishPerfMeasureAndSend() {
|
||||
if (this.shouldCollectMetrics) {
|
||||
performanceMarkAndMeasure({
|
||||
mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
|
||||
measures: [
|
||||
{
|
||||
name: PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
|
||||
start: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
const duration = window.performance.getEntriesByName(
|
||||
PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
|
||||
)[0]?.duration;
|
||||
|
||||
if (!duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
histograms: [
|
||||
{ name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
|
||||
{ name: PIPELINES_DETAIL_LINKS_TOTAL, value: this.links.length },
|
||||
{
|
||||
name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
|
||||
value: this.links.length / this.totalGroups,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
reportPerformance(this.metricsConfig.path, data);
|
||||
});
|
||||
},
|
||||
isLinkHighlighted(linkRef) {
|
||||
return this.highlightedLinks.includes(linkRef);
|
||||
},
|
||||
prepareLinkData() {
|
||||
this.beginPerfMeasure();
|
||||
calculateLinkData() {
|
||||
try {
|
||||
const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups);
|
||||
this.parsedData = parseData(arrayOfJobs);
|
||||
this.refreshLinks();
|
||||
this.links = generateLinksData(this.parsedData, this.containerId, `-${this.pipelineId}`);
|
||||
} catch (err) {
|
||||
this.$emit('error', { type: DRAW_FAILURE, reportToSentry: false });
|
||||
reportToSentry(this.$options.name, err);
|
||||
}
|
||||
this.finishPerfMeasureAndSend();
|
||||
},
|
||||
refreshLinks() {
|
||||
this.links = generateLinksData(this.parsedData, this.containerId, `-${this.pipelineId}`);
|
||||
},
|
||||
getLinkClasses(link) {
|
||||
return [
|
||||
|
|
|
@ -43,6 +43,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
alertDismissed: false,
|
||||
parsedData: {},
|
||||
showLinksOverride: false,
|
||||
};
|
||||
},
|
||||
|
@ -72,14 +73,7 @@ export default {
|
|||
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
|
||||
},
|
||||
mounted() {
|
||||
/*
|
||||
This is code to get metrics for the graph (to observe links performance).
|
||||
It is currently here because we want values for links without drawing them.
|
||||
It can be removed when https://gitlab.com/gitlab-org/gitlab/-/issues/298930
|
||||
is closed and functionality is enabled by default.
|
||||
*/
|
||||
|
||||
if (!this.showLinks && !isEmpty(this.pipelineData)) {
|
||||
if (!isEmpty(this.pipelineData)) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.prepareLinkData();
|
||||
});
|
||||
|
@ -132,7 +126,8 @@ export default {
|
|||
let numLinks;
|
||||
try {
|
||||
const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups);
|
||||
numLinks = parseData(arrayOfJobs).links.length;
|
||||
this.parsedData = parseData(arrayOfJobs);
|
||||
numLinks = this.parsedData.links.length;
|
||||
} catch (err) {
|
||||
reportToSentry(this.$options.name, err);
|
||||
}
|
||||
|
@ -145,9 +140,9 @@ export default {
|
|||
<links-inner
|
||||
v-if="showLinkedLayers"
|
||||
:container-measurements="containerMeasurements"
|
||||
:parsed-data="parsedData"
|
||||
:pipeline-data="pipelineData"
|
||||
:total-groups="numGroups"
|
||||
:metrics-config="metricsConfig"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
|
|
|
@ -186,3 +186,5 @@ module MembershipActions
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
MembershipActions.prepend_if_ee('EE::MembershipActions')
|
||||
|
|
|
@ -4,7 +4,6 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
|
|||
before_action :check_can_collaborate!
|
||||
before_action do
|
||||
push_frontend_feature_flag(:ci_config_merged_tab, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:pipeline_status_for_pipeline_editor, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:pipeline_editor_empty_state_action, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:pipeline_editor_branch_switcher, @project, default_enabled: :yaml)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove feature flag for pipeline status in pipeline editor
|
||||
merge_request: 60463
|
||||
author:
|
||||
type: other
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: pipeline_status_for_pipeline_editor
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53797
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321518
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: true
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: block_password_auth_for_saml_users
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone: '13.11'
|
||||
type: ops
|
||||
group: group::access
|
||||
default_enabled: false
|
|
@ -210,7 +210,7 @@ end
|
|||
|
||||
Behind the scenes, `be_signed_in` is a
|
||||
[predicate matcher](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/predicate-matchers)
|
||||
that [implements checking the user avatar](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/page/main/menu.rb#L74).
|
||||
that [implements checking the user avatar](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/page/main/menu.rb#L92).
|
||||
|
||||
## De-duplicate your code
|
||||
|
||||
|
@ -339,11 +339,12 @@ Before running the spec, make sure that:
|
|||
- No additional [RSpec metadata tags](rspec_metadata_tests.md) have been applied.
|
||||
- Your working directory is `qa/` within your GDK GitLab installation.
|
||||
- Your GitLab instance-level settings are default. If you changed the default settings, some tests might have unexpected results.
|
||||
- Because the GDK requires a password change on first login, you must include the GDK password for `root` user
|
||||
|
||||
To run the spec, run the following command:
|
||||
|
||||
```ruby
|
||||
bundle exec bin/qa Test::Instance::All http://localhost:3000 -- <test_file>
|
||||
GITLAB_PASSWORD=<GDK root password> bundle exec bin/qa Test::Instance::All http://localhost:3000 -- <test_file>
|
||||
```
|
||||
|
||||
Where `<test_file>` is:
|
||||
|
|
|
@ -471,7 +471,7 @@ include:
|
|||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DAST_API_SPECIFICATION: http://my.api/api-specification.yml
|
||||
DAST_API_OPENAPI: http://my.api/api-specification.yml
|
||||
```
|
||||
|
||||
#### Import API specification from a file
|
||||
|
@ -486,7 +486,7 @@ dast:
|
|||
- cp api-specification.yml /zap/wrk/api-specification.yml
|
||||
variables:
|
||||
GIT_STRATEGY: fetch
|
||||
DAST_API_SPECIFICATION: api-specification.yml
|
||||
DAST_API_OPENAPI: api-specification.yml
|
||||
```
|
||||
|
||||
#### Full API scan
|
||||
|
@ -522,7 +522,7 @@ include:
|
|||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DAST_API_SPECIFICATION: http://api-test.host.com/api-specification.yml
|
||||
DAST_API_OPENAPI: http://api-test.host.com/api-specification.yml
|
||||
DAST_API_HOST_OVERRIDE: api-test.host.com
|
||||
```
|
||||
|
||||
|
@ -537,7 +537,7 @@ include:
|
|||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DAST_API_SPECIFICATION: http://api-test.api.com/api-specification.yml
|
||||
DAST_API_OPENAPI: http://api-test.api.com/api-specification.yml
|
||||
DAST_REQUEST_HEADERS: "Authorization: Bearer my.token"
|
||||
```
|
||||
|
||||
|
@ -673,8 +673,9 @@ DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
|
|||
| CI/CD variable | Type | Description |
|
||||
|------------------------------| --------|-------------|
|
||||
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
|
||||
| `DAST_WEBSITE` | URL | The URL of the website to scan. `DAST_API_SPECIFICATION` must be specified if this is omitted. |
|
||||
| `DAST_API_SPECIFICATION` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_WEBSITE` | URL | The URL of the website to scan. `DAST_API_OPENAPI` must be specified if this is omitted. |
|
||||
| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_API_SPECIFICATION` | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
|
||||
| `DAST_AUTH_URL` | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. |
|
||||
| `DAST_AUTH_VERIFICATION_URL` | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST will exit if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8.
|
||||
|
|
|
@ -115,6 +115,8 @@ module Gitlab
|
|||
log.info "Correct LDAP account has been found. identity to user: #{gl_user.username}."
|
||||
gl_user.identities.build(provider: ldap_person.provider, extern_uid: ldap_person.dn)
|
||||
end
|
||||
|
||||
identity
|
||||
end
|
||||
|
||||
def find_or_build_ldap_user
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
"codesandbox-api": "0.0.23",
|
||||
"compression-webpack-plugin": "^5.0.2",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"core-js": "^3.11.1",
|
||||
"core-js": "^3.11.2",
|
||||
"cron-validator": "^1.1.1",
|
||||
"cropper": "^2.3.0",
|
||||
"css-loader": "^2.1.1",
|
||||
|
|
|
@ -54,9 +54,7 @@ the browser to use. You will need to have Chrome (or Chromium) and
|
|||
|
||||
### Run the end-to-end tests in a local development environment
|
||||
|
||||
Follow the GDK instructions to [prepare](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/prepare.md)
|
||||
and [install](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/set-up-gdk.md)
|
||||
your local GitLab development environment.
|
||||
Follow the GDK instructions to [install](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/index.md) your local GitLab development environment.
|
||||
|
||||
Once you have GDK running, switch to the `qa` directory. E.g., if you setup
|
||||
GDK to develop in the main `gitlab-ce` repo, the GitLab source code will be
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Group' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
|
|
@ -7,16 +7,10 @@ import { mockCiYml, mockLintResponse } from '../../mock_data';
|
|||
|
||||
describe('Pipeline editor header', () => {
|
||||
let wrapper;
|
||||
const mockProvide = {
|
||||
glFeatures: {
|
||||
pipelineStatusForPipelineEditor: true,
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = ({ provide = {}, props = {} } = {}) => {
|
||||
wrapper = shallowMount(PipelineEditorHeader, {
|
||||
provide: {
|
||||
...mockProvide,
|
||||
...provide,
|
||||
},
|
||||
propsData: {
|
||||
|
@ -56,18 +50,4 @@ describe('Pipeline editor header', () => {
|
|||
expect(findValidationSegment().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pipeline status feature flag off', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { pipelineStatusForPipelineEditor: false },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render the pipeline status', () => {
|
||||
expect(findPipelineStatus().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Links Inner component with a large number of needs matches snapshot and has expected path 1`] = `
|
||||
"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
|
||||
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
|
||||
<path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
|
||||
<path d=\\"M202,118L52,118C82,118,82,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
|
||||
<path d=\\"M222,138L62,138C92,138,92,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
|
||||
|
@ -11,13 +11,13 @@ exports[`Links Inner component with a large number of needs matches snapshot and
|
|||
`;
|
||||
|
||||
exports[`Links Inner component with a parallel need matches snapshot and has expected path 1`] = `
|
||||
"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
|
||||
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
|
||||
<path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
|
||||
</svg> </div>"
|
||||
`;
|
||||
|
||||
exports[`Links Inner component with one need matches snapshot and has expected path 1`] = `
|
||||
"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
|
||||
"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\">
|
||||
<path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
|
||||
</svg> </div>"
|
||||
`;
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
PIPELINES_DETAIL_LINK_DURATION,
|
||||
PIPELINES_DETAIL_LINKS_TOTAL,
|
||||
PIPELINES_DETAIL_LINKS_JOB_RATIO,
|
||||
} from '~/performance/constants';
|
||||
import * as perfUtils from '~/performance/utils';
|
||||
import * as Api from '~/pipelines/components/graph_shared/api';
|
||||
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
|
||||
import * as sentryUtils from '~/pipelines/utils';
|
||||
import { parseData } from '~/pipelines/components/parsing_utils';
|
||||
import { createJobsHash } from '~/pipelines/utils';
|
||||
import {
|
||||
jobRect,
|
||||
|
@ -34,8 +25,13 @@ describe('Links Inner component', () => {
|
|||
let wrapper;
|
||||
|
||||
const createComponent = (props) => {
|
||||
const currentPipelineData = props?.pipelineData || defaultProps.pipelineData;
|
||||
wrapper = shallowMount(LinksInner, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
parsedData: parseData(currentPipelineData.flatMap(({ groups }) => groups)),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -206,141 +202,4 @@ describe('Links Inner component', () => {
|
|||
expect(firstLink.classes(hoverColorClass)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance metrics', () => {
|
||||
let markAndMeasure;
|
||||
let reportToSentry;
|
||||
let reportPerformance;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
|
||||
markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure');
|
||||
reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry');
|
||||
reportPerformance = jest.spyOn(Api, 'reportPerformance');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('with no metrics config object', () => {
|
||||
beforeEach(() => {
|
||||
setFixtures(pipelineData);
|
||||
createComponent({
|
||||
pipelineData: pipelineData.stages,
|
||||
});
|
||||
});
|
||||
|
||||
it('is not called', () => {
|
||||
expect(markAndMeasure).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with metrics config set to false', () => {
|
||||
beforeEach(() => {
|
||||
setFixtures(pipelineData);
|
||||
createComponent({
|
||||
pipelineData: pipelineData.stages,
|
||||
metricsConfig: {
|
||||
collectMetrics: false,
|
||||
metricsPath: '/path/to/metrics',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('is not called', () => {
|
||||
expect(markAndMeasure).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no metrics path', () => {
|
||||
beforeEach(() => {
|
||||
setFixtures(pipelineData);
|
||||
createComponent({
|
||||
pipelineData: pipelineData.stages,
|
||||
metricsConfig: {
|
||||
collectMetrics: true,
|
||||
metricsPath: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('is not called', () => {
|
||||
expect(markAndMeasure).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with metrics path and collect set to true', () => {
|
||||
const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
|
||||
const duration = 0.0478;
|
||||
const numLinks = 1;
|
||||
const metricsData = {
|
||||
histograms: [
|
||||
{ name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
|
||||
{ name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
|
||||
{
|
||||
name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
|
||||
value: numLinks / defaultProps.totalGroups,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('when no duration is obtained', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
setFixtures(pipelineData);
|
||||
|
||||
createComponent({
|
||||
pipelineData: pipelineData.stages,
|
||||
metricsConfig: {
|
||||
collectMetrics: true,
|
||||
path: metricsPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('attempts to collect metrics', () => {
|
||||
expect(markAndMeasure).toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with duration and no error', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
|
||||
return [{ duration }];
|
||||
});
|
||||
|
||||
setFixtures(pipelineData);
|
||||
|
||||
createComponent({
|
||||
pipelineData: pipelineData.stages,
|
||||
metricsConfig: {
|
||||
collectMetrics: true,
|
||||
path: metricsPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('it calls reportPerformance with expected arguments', () => {
|
||||
expect(markAndMeasure).toHaveBeenCalled();
|
||||
expect(reportPerformance).toHaveBeenCalled();
|
||||
expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData);
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
PIPELINES_DETAIL_LINK_DURATION,
|
||||
PIPELINES_DETAIL_LINKS_TOTAL,
|
||||
PIPELINES_DETAIL_LINKS_JOB_RATIO,
|
||||
} from '~/performance/constants';
|
||||
import * as perfUtils from '~/performance/utils';
|
||||
import * as Api from '~/pipelines/components/graph_shared/api';
|
||||
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
|
||||
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
|
||||
import * as sentryUtils from '~/pipelines/utils';
|
||||
import { generateResponse, mockPipelineResponse } from '../graph/mock_data';
|
||||
|
||||
describe('links layer component', () => {
|
||||
|
@ -37,7 +47,6 @@ describe('links layer component', () => {
|
|||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('with show links off', () => {
|
||||
|
@ -85,4 +94,137 @@ describe('links layer component', () => {
|
|||
expect(findLinksInner().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance metrics', () => {
|
||||
let markAndMeasure;
|
||||
let reportToSentry;
|
||||
let reportPerformance;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
|
||||
markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure');
|
||||
reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry');
|
||||
reportPerformance = jest.spyOn(Api, 'reportPerformance');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('with no metrics config object', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('is not called', () => {
|
||||
expect(markAndMeasure).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with metrics config set to false', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
metricsConfig: {
|
||||
collectMetrics: false,
|
||||
metricsPath: '/path/to/metrics',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('is not called', () => {
|
||||
expect(markAndMeasure).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no metrics path', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
metricsConfig: {
|
||||
collectMetrics: true,
|
||||
metricsPath: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('is not called', () => {
|
||||
expect(markAndMeasure).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with metrics path and collect set to true', () => {
|
||||
const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
|
||||
const duration = 875;
|
||||
const numLinks = 7;
|
||||
const totalGroups = 8;
|
||||
const metricsData = {
|
||||
histograms: [
|
||||
{ name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
|
||||
{ name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
|
||||
{
|
||||
name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
|
||||
value: numLinks / totalGroups,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('when no duration is obtained', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
createComponent({
|
||||
props: {
|
||||
metricsConfig: {
|
||||
collectMetrics: true,
|
||||
path: metricsPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('attempts to collect metrics', () => {
|
||||
expect(markAndMeasure).toHaveBeenCalled();
|
||||
expect(reportPerformance).not.toHaveBeenCalled();
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with duration and no error', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
|
||||
return [{ duration }];
|
||||
});
|
||||
|
||||
createComponent({
|
||||
props: {
|
||||
metricsConfig: {
|
||||
collectMetrics: true,
|
||||
path: metricsPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('it calls reportPerformance with expected arguments', () => {
|
||||
expect(markAndMeasure).toHaveBeenCalled();
|
||||
expect(reportPerformance).toHaveBeenCalled();
|
||||
expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData);
|
||||
expect(reportToSentry).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
import { CI_CONFIG_STATUS_VALID } from '~/pipeline_editor/constants';
|
||||
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
|
||||
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
|
||||
import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue';
|
||||
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
|
||||
import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue';
|
||||
import { DRAW_FAILURE } from '~/pipelines/constants';
|
||||
import { invalidNeedsData, pipelineData, singleStageData } from './mock_data';
|
||||
import { pipelineData, singleStageData } from './mock_data';
|
||||
|
||||
describe('pipeline graph component', () => {
|
||||
const defaultProps = { pipelineData };
|
||||
let wrapper;
|
||||
|
||||
const containerId = 'pipeline-graph-container-0';
|
||||
setHTMLFixture(`<div id="${containerId}"></div>`);
|
||||
|
||||
const createComponent = (props = defaultProps) => {
|
||||
return shallowMount(PipelineGraph, {
|
||||
propsData: {
|
||||
|
@ -55,18 +58,7 @@ describe('pipeline graph component', () => {
|
|||
it('renders the graph with no status error', () => {
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
expect(findPipelineGraph().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with error while rendering the links with needs', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent({ pipelineData: invalidNeedsData });
|
||||
});
|
||||
|
||||
it('renders the error that link could not be drawn', () => {
|
||||
expect(findLinksLayer().exists()).toBe(true);
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[DRAW_FAILURE]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ RSpec.shared_context 'project service activation' do
|
|||
end
|
||||
|
||||
def click_active_checkbox
|
||||
find('input[name="service[active]"]').click
|
||||
find('label', text: 'Active').click
|
||||
end
|
||||
|
||||
def click_save_integration
|
||||
|
|
|
@ -3605,10 +3605,10 @@ core-js-pure@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
|
||||
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
|
||||
|
||||
core-js@^3.1.3, core-js@^3.11.1:
|
||||
version "3.11.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.1.tgz#f920392bf8ed63a0ec8e4e729857bfa3d121c525"
|
||||
integrity sha512-k93Isqg7e4txZWMGNYwevZL9MiogLk8pd1PtwrmFmi8IBq4GXqUaVW/a33Llt6amSI36uSjd0GWwc9pTT9ALlQ==
|
||||
core-js@^3.1.3, core-js@^3.11.2:
|
||||
version "3.11.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.2.tgz#af087a43373fc6e72942917c4a4c3de43ed574d6"
|
||||
integrity sha512-3tfrrO1JpJSYGKnd9LKTBPqgUES/UYiCzMKeqwR1+jF16q4kD1BY2NvqkfuzXwQ6+CIWm55V9cjD7PQd+hijdw==
|
||||
|
||||
core-js@~2.3.0:
|
||||
version "2.3.0"
|
||||
|
|
Loading…
Reference in a new issue