Skip to content
Snippets Groups Projects
Commit 4c464055 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 791785af
No related branches found
No related tags found
No related merge requests found
Showing
with 625 additions and 31 deletions
Loading
Loading
@@ -75,7 +75,7 @@ graphql-reference-verify:
- .default-cache
- .default-only
- .default-before_script
- .only:changes-graphql
- .only:changes-code-backstage-qa
- .use-pg9
stage: test
needs: ["setup-test-env"]
Loading
Loading
Loading
Loading
@@ -93,6 +93,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
 
.backstage-patterns: &backstage-patterns
- "Dangerfile"
Loading
Loading
@@ -111,11 +112,6 @@
- "doc/**/*"
- ".markdownlint.json"
 
.graphql-patterns: &graphql-patterns
- "{,ee/}app/graphql/**/*"
- "{,ee/}lib/gitlab/graphql/**/*"
- "doc/api/graphql/**/*"
.only:changes-code:
only:
changes: *code-patterns
Loading
Loading
@@ -128,10 +124,6 @@
only:
changes: *docs-patterns
 
.only:changes-graphql:
only:
changes: *graphql-patterns
.only:changes-code-backstage:
only:
changes:
Loading
Loading
@@ -147,6 +139,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
Loading
Loading
@@ -170,6 +163,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
# QA changes
- ".dockerignore"
- "qa/**/*"
Loading
Loading
@@ -189,6 +183,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
Loading
Loading
1.70.0
1.71.0
Loading
Loading
@@ -17,14 +17,7 @@ function MergeRequest(opts) {
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on(
'click',
(function(_this) {
return function() {
return _this.showAllCommits();
};
})(this),
);
this.$('.show-all-commits').on('click', () => this.showAllCommits());
 
this.initTabs();
this.initMRBtnListeners();
Loading
Loading
// /test_report is an alias for show
import '../show/index';
<script>
import { mapActions, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import TestSuiteTable from './test_suite_table.vue';
import TestSummary from './test_summary.vue';
import TestSummaryTable from './test_summary_table.vue';
import store from '~/pipelines/stores/test_reports';
export default {
name: 'TestReports',
components: {
GlLoadingIcon,
TestSuiteTable,
TestSummary,
TestSummaryTable,
},
store,
computed: {
...mapState(['isLoading', 'selectedSuite', 'testReports']),
showSuite() {
return this.selectedSuite.total_count > 0;
},
showTests() {
return this.testReports.total_count > 0;
},
},
methods: {
...mapActions(['setSelectedSuite', 'removeSelectedSuite']),
summaryBackClick() {
this.removeSelectedSuite();
},
summaryTableRowClick(suite) {
this.setSelectedSuite(suite);
},
beforeEnterTransition() {
document.documentElement.style.overflowX = 'hidden';
},
afterLeaveTransition() {
document.documentElement.style.overflowX = '';
},
},
};
</script>
<template>
<div v-if="isLoading">
<gl-loading-icon size="lg" class="prepend-top-default js-loading-spinner" />
</div>
<div
v-else-if="!isLoading && showTests"
ref="container"
class="tests-detail position-relative js-tests-detail"
>
<transition
name="slide"
@before-enter="beforeEnterTransition"
@after-leave="afterLeaveTransition"
>
<div v-if="showSuite" key="detail" class="w-100 position-absolute slide-enter-to-element">
<test-summary :report="selectedSuite" show-back @on-back-click="summaryBackClick" />
<test-suite-table />
</div>
<div v-else key="summary" class="w-100 position-absolute slide-enter-from-element">
<test-summary :report="testReports" />
<test-summary-table @row-click="summaryTableRowClick" />
</div>
</transition>
</div>
<div v-else>
<div class="row prepend-top-default">
<div class="col-12">
<p class="js-no-tests-to-show">{{ s__('TestReports|There are no tests to show.') }}</p>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
export default {
name: 'TestsSuiteTable',
components: {
Icon,
},
store,
props: {
heading: {
type: String,
required: false,
default: __('Tests'),
},
},
computed: {
...mapGetters(['getSuiteTests']),
hasSuites() {
return this.getSuiteTests.length > 0;
},
},
};
</script>
<template>
<div>
<div class="row prepend-top-default">
<div class="col-12">
<h4>{{ heading }}</h4>
</div>
</div>
<div v-if="hasSuites" class="test-reports-table js-test-cases-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
<div role="rowheader" class="table-section section-20">
{{ __('Class') }}
</div>
<div role="rowheader" class="table-section section-20">
{{ __('Name') }}
</div>
<div role="rowheader" class="table-section section-10 text-center">
{{ __('Status') }}
</div>
<div role="rowheader" class="table-section flex-grow-1">
{{ __('Trace'), }}
</div>
<div role="rowheader" class="table-section section-10 text-right">
{{ __('Duration') }}
</div>
</div>
<div
v-for="(testCase, index) in getSuiteTests"
:key="index"
class="gl-responsive-table-row rounded align-items-md-start mt-sm-3 js-case-row"
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
<div class="table-mobile-content pr-md-1">{{ testCase.classname }}</div>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
<div class="table-mobile-content">{{ testCase.name }}</div>
</div>
<div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div>
<div class="table-mobile-content text-center">
<div
class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center"
:class="`ci-status-icon-${testCase.status}`"
>
<icon :size="24" :name="testCase.icon" />
</div>
</div>
</div>
<div class="table-section flex-grow-1">
<div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div>
<div class="table-mobile-content">
<pre
v-if="testCase.system_output"
class="build-trace build-trace-rounded text-left"
><code class="bash p-0">{{testCase.system_output}}</code></pre>
</div>
</div>
<div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">
{{ __('Duration') }}
</div>
<div class="table-mobile-content text-right">
{{ testCase.formattedTime }}
</div>
</div>
</div>
</div>
<div v-else>
<p class="js-no-test-cases">{{ s__('TestReports|There are no test cases to display.') }}</p>
</div>
</div>
</template>
<script>
import { GlButton, GlLink, GlProgressBar } from '@gitlab/ui';
import { __ } from '~/locale';
import { formatTime } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'TestSummary',
components: {
GlButton,
GlLink,
GlProgressBar,
Icon,
},
props: {
report: {
type: Object,
required: true,
},
showBack: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
heading() {
return this.report.name || __('Summary');
},
successPercentage() {
return Math.round((this.report.success_count / this.report.total_count) * 100) || 0;
},
formattedDuration() {
return formatTime(this.report.total_time * 1000);
},
progressBarVariant() {
if (this.successPercentage < 33) {
return 'danger';
}
if (this.successPercentage >= 33 && this.successPercentage < 66) {
return 'warning';
}
if (this.successPercentage >= 66 && this.successPercentage < 90) {
return 'primary';
}
return 'success';
},
},
methods: {
onBackClick() {
this.$emit('on-back-click');
},
},
};
</script>
<template>
<div>
<div class="row">
<div class="col-12 d-flex prepend-top-8 align-items-center">
<gl-button
v-if="showBack"
size="sm"
class="append-right-default js-back-button"
@click="onBackClick"
>
<icon name="angle-left" />
</gl-button>
<h4>{{ heading }}</h4>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md">
<span class="js-total-tests">{{
sprintf(s__('TestReports|%{count} jobs'), { count: report.total_count })
}}</span>
</div>
<div class="col-4 col-md text-center text-md-center">
<span class="js-failed-tests">{{
sprintf(s__('TestReports|%{count} failures'), { count: report.failed_count })
}}</span>
</div>
<div class="col-4 col-md text-right text-md-center">
<span class="js-errored-tests">{{
sprintf(s__('TestReports|%{count} errors'), { count: report.error_count })
}}</span>
</div>
<div class="col-6 mt-3 col-md mt-md-0 text-md-center">
<span class="js-success-rate">{{
sprintf(s__('TestReports|%{rate}%{sign} success rate'), {
rate: successPercentage,
sign: '%',
})
}}</span>
</div>
<div class="col-6 mt-3 col-md mt-md-0 text-right">
<span class="js-duration">{{ formattedDuration }}</span>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<gl-progress-bar :value="successPercentage" :variant="progressBarVariant" height="10px" />
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import store from '~/pipelines/stores/test_reports';
export default {
name: 'TestsSummaryTable',
store,
props: {
heading: {
type: String,
required: false,
default: s__('TestReports|Test suites'),
},
},
computed: {
...mapGetters(['getTestSuites']),
hasSuites() {
return this.getTestSuites.length > 0;
},
},
methods: {
tableRowClick(suite) {
this.$emit('row-click', suite);
},
},
};
</script>
<template>
<div>
<div class="row prepend-top-default">
<div class="col-12">
<h4>{{ heading }}</h4>
</div>
</div>
<div v-if="hasSuites" class="test-reports-table js-test-suites-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold">
<div role="rowheader" class="table-section section-25 pl-3">
{{ __('Suite') }}
</div>
<div role="rowheader" class="table-section section-25">
{{ __('Duration') }}
</div>
<div role="rowheader" class="table-section section-10 text-center">
{{ __('Failed') }}
</div>
<div role="rowheader" class="table-section section-10 text-center">
{{ __('Errors'), }}
</div>
<div role="rowheader" class="table-section section-10 text-center">
{{ __('Skipped'), }}
</div>
<div role="rowheader" class="table-section section-10 text-center">
{{ __('Passed'), }}
</div>
<div role="rowheader" class="table-section section-10 pr-3 text-right">
{{ __('Total') }}
</div>
</div>
<div
v-for="(testSuite, index) in getTestSuites"
:key="index"
role="row"
class="gl-responsive-table-row test-reports-summary-row rounded cursor-pointer js-suite-row"
@click="tableRowClick(testSuite)"
>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Suite') }}
</div>
<div class="table-mobile-content test-reports-summary-suite cgray pl-3">
{{ testSuite.name }}
</div>
</div>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Duration') }}
</div>
<div class="table-mobile-content text-md-left">
{{ testSuite.formattedTime }}
</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Failed') }}
</div>
<div class="table-mobile-content">{{ testSuite.failed_count }}</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Errors') }}
</div>
<div class="table-mobile-content">{{ testSuite.error_count }}</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Skipped') }}
</div>
<div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Passed') }}
</div>
<div class="table-mobile-content">{{ testSuite.success_count }}</div>
</div>
<div class="table-section section-10 text-right pr-md-3">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Total') }}
</div>
<div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div>
</div>
</div>
<div v-else>
<p class="js-no-tests-suites">{{ s__('TestReports|There are no test suites to show.') }}</p>
</div>
</div>
</template>
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300;
export const TestStatus = {
FAILED: 'failed',
SKIPPED: 'skipped',
SUCCESS: 'success',
};
Loading
Loading
@@ -7,6 +7,8 @@ import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
import TestReports from './components/test_reports/test_reports.vue';
import testReportsStore from './stores/test_reports';
 
Vue.use(Translate);
 
Loading
Loading
@@ -17,7 +19,7 @@ export default () => {
 
mediator.fetchPipeline();
 
// eslint-disable-next-line
// eslint-disable-next-line no-new
new Vue({
el: '#js-pipeline-graph-vue',
components: {
Loading
Loading
@@ -47,7 +49,7 @@ export default () => {
},
});
 
// eslint-disable-next-line
// eslint-disable-next-line no-new
new Vue({
el: '#js-pipeline-header-vue',
components: {
Loading
Loading
@@ -81,4 +83,23 @@ export default () => {
});
},
});
const testReportsEnabled =
window.gon && window.gon.features && window.gon.features.junitPipelineView;
if (testReportsEnabled) {
testReportsStore.dispatch('setEndpoint', dataset.testReportEndpoint);
testReportsStore.dispatch('fetchReports');
// eslint-disable-next-line no-new
new Vue({
el: '#js-pipeline-tests-detail',
components: {
TestReports,
},
render(createElement) {
return createElement('test-reports');
},
});
}
};
import axios from '~/lib/utils/axios_utils';
import * as types from './mutation_types';
import createFlash from '~/flash';
import { s__ } from '~/locale';
export const setEndpoint = ({ commit }, data) => commit(types.SET_ENDPOINT, data);
export const fetchReports = ({ state, commit, dispatch }) => {
dispatch('toggleLoading');
return axios
.get(state.endpoint)
.then(response => {
const { data } = response;
commit(types.SET_REPORTS, data);
})
.catch(() => {
createFlash(s__('TestReports|There was an error fetching the test reports.'));
})
.finally(() => {
dispatch('toggleLoading');
});
};
export const setSelectedSuite = ({ commit }, data) => commit(types.SET_SELECTED_SUITE, data);
export const removeSelectedSuite = ({ commit }) => commit(types.SET_SELECTED_SUITE, {});
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import { addIconStatus, formattedTime, sortTestCases } from './utils';
export const getTestSuites = state => {
const { test_suites: testSuites = [] } = state.testReports;
return testSuites.map(suite => ({
...suite,
formattedTime: formattedTime(suite.total_time),
}));
};
export const getSuiteTests = state => {
const { selectedSuite } = state;
if (selectedSuite.test_cases) {
return selectedSuite.test_cases.sort(sortTestCases).map(addIconStatus);
}
return [];
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
actions,
getters,
mutations,
state,
});
export const SET_ENDPOINT = 'SET_ENDPOINT';
export const SET_REPORTS = 'SET_REPORTS';
export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
import * as types from './mutation_types';
export default {
[types.SET_ENDPOINT](state, endpoint) {
Object.assign(state, { endpoint });
},
[types.SET_REPORTS](state, testReports) {
Object.assign(state, { testReports });
},
[types.SET_SELECTED_SUITE](state, selectedSuite) {
Object.assign(state, { selectedSuite });
},
[types.TOGGLE_LOADING](state) {
Object.assign(state, { isLoading: !state.isLoading });
},
};
export default () => ({
endpoint: '',
testReports: {},
selectedSuite: {},
isLoading: false,
});
import { TestStatus } from '~/pipelines/constants';
import { formatTime } from '~/lib/utils/datetime_utility';
function iconForTestStatus(status) {
switch (status) {
case 'success':
return 'status_success_borderless';
case 'failed':
return 'status_failed_borderless';
default:
return 'status_skipped_borderless';
}
}
export const formattedTime = timeInSeconds => formatTime(timeInSeconds * 1000);
export const addIconStatus = testCase => ({
...testCase,
icon: iconForTestStatus(testCase.status),
formattedTime: formattedTime(testCase.execution_time),
});
export const sortTestCases = (a, b) => {
if (a.status === b.status) {
return 0;
}
switch (b.status) {
case TestStatus.SUCCESS:
return -1;
case TestStatus.FAILED:
return 1;
default:
return 0;
}
};
Loading
Loading
@@ -128,7 +128,7 @@ export default {
<div class="ci-status-link">
<gl-link
v-if="commit.latestPipeline"
v-gl-tooltip
v-gl-tooltip.left
:href="commit.latestPipeline.detailedStatus.detailsPath"
:title="statusTitle"
class="js-commit-pipeline"
Loading
Loading
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { GlSkeletonLoading } from '@gitlab/ui';
import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
Loading
Loading
@@ -13,7 +13,7 @@ const PAGE_SIZE = 100;
 
export default {
components: {
GlLoadingIcon,
GlSkeletonLoading,
TableHeader,
TableRow,
ParentRow,
Loading
Loading
@@ -44,6 +44,15 @@ export default {
},
computed: {
tableCaption() {
if (this.isLoadingFiles) {
return sprintf(
__(
'Loading files, directories, and submodules in the path %{path} for commit reference %{ref}',
),
{ path: this.path, ref: this.ref },
);
}
return sprintf(
__('Files, directories, and submodules in the path %{path} for commit reference %{ref}'),
{ path: this.path, ref: this.ref },
Loading
Loading
@@ -117,12 +126,7 @@ export default {
<template>
<div class="tree-content-holder">
<div class="table-holder bordered-box">
<table class="table tree-table qa-file-tree" aria-live="polite">
<caption class="sr-only">
{{
tableCaption
}}
</caption>
<table :aria-label="tableCaption" class="table tree-table qa-file-tree" aria-live="polite">
<table-header v-once />
<tbody>
<parent-row v-show="showParentRow" :commit-ref="ref" :path="path" />
Loading
Loading
@@ -141,9 +145,15 @@ export default {
:lfs-oid="entry.lfsOid"
/>
</template>
<template v-if="isLoadingFiles">
<tr v-for="i in 5" :key="i" aria-hidden="true">
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
<td><gl-skeleton-loading :lines="1" class="ml-auto h-auto w-50" /></td>
</tr>
</template>
</tbody>
</table>
<gl-loading-icon v-show="isLoadingFiles" class="my-3" size="md" />
</div>
</div>
</template>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment