Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-04 00:10:16 +00:00
parent 1cfa0679b4
commit 36545041d6
24 changed files with 213 additions and 304 deletions

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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();

View file

@ -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 [

View file

@ -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"
>

View file

@ -186,3 +186,5 @@ module MembershipActions
end
end
end
MembershipActions.prepend_if_ee('EE::MembershipActions')

View file

@ -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

View file

@ -0,0 +1,5 @@
---
title: Remove feature flag for pipeline status in pipeline editor
merge_request: 60463
author:
type: other

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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.

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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)

View file

@ -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);
});
});
});

View file

@ -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>"
`;

View file

@ -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();
});
});
});
});
});

View file

@ -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();
});
});
});
});
});

View file

@ -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]);
});
});

View file

@ -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

View file

@ -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"