Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ed621e6801
commit
273d780f9e
18 changed files with 200 additions and 53 deletions
|
@ -289,7 +289,6 @@ linters:
|
|||
- "app/views/shared/issuable/_search_bar.html.haml"
|
||||
- "app/views/shared/issuable/_sidebar.html.haml"
|
||||
- "app/views/shared/issuable/form/_default_templates.html.haml"
|
||||
- "app/views/shared/issuable/form/_issue_assignee.html.haml"
|
||||
- "app/views/shared/issuable/form/_template_selector.html.haml"
|
||||
- "app/views/shared/issuable/form/_title.html.haml"
|
||||
- "app/views/shared/labels/_form.html.haml"
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
|
||||
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BoardColumn,
|
||||
EpicsSwimlanes,
|
||||
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
|
||||
GlAlert,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
|
@ -42,7 +43,7 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isShowingEpicsSwimlanes', 'boardLists']),
|
||||
...mapState(['isShowingEpicsSwimlanes', 'boardLists', 'error']),
|
||||
isSwimlanesOn() {
|
||||
return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes;
|
||||
},
|
||||
|
@ -52,6 +53,9 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<gl-alert v-if="error" variant="danger" :dismissible="false">
|
||||
{{ error }}
|
||||
</gl-alert>
|
||||
<div
|
||||
v-if="!isSwimlanesOn"
|
||||
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as mutationTypes from './mutation_types';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
const notImplemented = () => {
|
||||
/* eslint-disable-next-line @gitlab/require-i18n-strings */
|
||||
|
@ -62,7 +63,7 @@ export default {
|
|||
},
|
||||
|
||||
[mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => {
|
||||
state.listIssueFetchFailure = true;
|
||||
state.error = __('An error occurred while fetching the board issues. Please reload the page.');
|
||||
state.isLoadingIssues = false;
|
||||
},
|
||||
|
||||
|
|
|
@ -5,7 +5,10 @@ export default () => ({
|
|||
boardType: null,
|
||||
isShowingLabels: true,
|
||||
activeId: inactiveId,
|
||||
boardLists: [],
|
||||
issuesByListId: {},
|
||||
isLoadingIssues: false,
|
||||
listIssueFetchFailure: false,
|
||||
error: undefined,
|
||||
// TODO: remove after ce/ee split of board_content.vue
|
||||
isShowingEpicsSwimlanes: false,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
fragment EpicNode on Epic {
|
||||
id
|
||||
iid
|
||||
title
|
||||
state
|
||||
reference
|
||||
webUrl
|
||||
createdAt
|
||||
closedAt
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
import { GlDeprecatedButton, GlProgressBar } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { formattedTime } from '../../stores/test_reports/utils';
|
||||
|
||||
export default {
|
||||
name: 'TestSummary',
|
||||
|
@ -39,7 +39,7 @@ export default {
|
|||
return 0;
|
||||
},
|
||||
formattedDuration() {
|
||||
return formatTime(secondsToMilliseconds(this.report.total_time));
|
||||
return formattedTime(this.report.total_time);
|
||||
},
|
||||
progressBarVariant() {
|
||||
if (this.successPercentage < 33) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TestStatus } from '~/pipelines/constants';
|
||||
import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
import { __, sprintf } from '../../../locale';
|
||||
|
||||
export function iconForTestStatus(status) {
|
||||
switch (status) {
|
||||
|
@ -12,7 +12,13 @@ export function iconForTestStatus(status) {
|
|||
}
|
||||
}
|
||||
|
||||
export const formattedTime = timeInSeconds => formatTime(secondsToMilliseconds(timeInSeconds));
|
||||
export const formattedTime = (seconds = 0) => {
|
||||
if (seconds < 1) {
|
||||
const milliseconds = seconds * 1000;
|
||||
return sprintf(__('%{milliseconds}ms'), { milliseconds: milliseconds.toFixed(2) });
|
||||
}
|
||||
return sprintf(__('%{seconds}s'), { seconds: seconds.toFixed(2) });
|
||||
};
|
||||
|
||||
export const addIconStatus = testCase => ({
|
||||
...testCase,
|
||||
|
|
|
@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
before_action :assign_endpoint_vars
|
||||
before_action do
|
||||
push_frontend_feature_flag(:multi_select_board, default_enabled: true)
|
||||
push_frontend_feature_flag(:boards_with_swimlanes, project, default_enabled: false)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
- issue = issuable
|
||||
- assignees = issue.assignees
|
||||
.block.assignee
|
||||
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee_list) }
|
||||
- if assignees.any?
|
||||
- assignees.each do |assignee|
|
||||
= link_to_member(@project, assignee, size: 24)
|
||||
- else
|
||||
= icon('user', 'aria-hidden': 'true')
|
||||
.title.hide-collapsed
|
||||
Assignee
|
||||
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
|
||||
- if can_edit_issuable
|
||||
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
|
||||
.value.hide-collapsed
|
||||
- if assignees.any?
|
||||
- assignees.each do |assignee|
|
||||
= link_to_member(@project, assignee, size: 32, extra_class: 'bold') do
|
||||
%span.username
|
||||
= assignee.to_reference
|
||||
- else
|
||||
%span.assign-yourself.no-value
|
||||
No assignee
|
||||
- if can_edit_issuable
|
||||
\-
|
||||
%a.js-assign-yourself{ href: '#' }
|
||||
assign yourself
|
||||
|
||||
.selectbox.hide-collapsed
|
||||
= f.hidden_field 'assignee_ids', value: issuable.assignee_ids, id: 'issue_assignee_ids'
|
||||
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
|
|
@ -1,8 +0,0 @@
|
|||
= form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-lg-4" : "col-sm-2"}"
|
||||
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
|
||||
.issuable-form-select-holder
|
||||
= form.hidden_field :assignee_id
|
||||
|
||||
= dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
|
||||
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
|
||||
= link_to 'Assign to me', '#', class: "assign-to-me-link qa-assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}"
|
5
changelogs/unreleased/213101-junit-report-times.yml
Normal file
5
changelogs/unreleased/213101-junit-report-times.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adjust format for JUnit report duration times
|
||||
merge_request: 39644
|
||||
author:
|
||||
type: changed
|
|
@ -551,6 +551,9 @@ msgstr ""
|
|||
msgid "%{milestone} (expired)"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{milliseconds}ms"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{mrText}, this issue will be closed automatically."
|
||||
msgstr ""
|
||||
|
||||
|
@ -663,6 +666,9 @@ msgstr ""
|
|||
msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{seconds}s"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{securityScanner} is not enabled for this project. %{linkStart}More information%{linkEnd}"
|
||||
msgid_plural "%{securityScanner} are not enabled for this project. %{linkStart}More information%{linkEnd}"
|
||||
msgstr[0] ""
|
||||
|
@ -2657,9 +2663,15 @@ msgstr ""
|
|||
msgid "An error occurred while fetching the Service Desk address."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while fetching the board issues. Please reload the page."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while fetching the board lists. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while fetching the board swimlanes. Please reload the page."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while fetching the builds."
|
||||
msgstr ""
|
||||
|
||||
|
|
66
spec/frontend/boards/components/board_content_spec.js
Normal file
66
spec/frontend/boards/components/board_content_spec.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
import Vuex from 'vuex';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
|
||||
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
|
||||
import { mockListsWithModel } from '../mock_data';
|
||||
import BoardContent from '~/boards/components/board_content.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('BoardContent', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultState = {
|
||||
isShowingEpicsSwimlanes: false,
|
||||
boardLists: mockListsWithModel,
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
const createStore = (state = defaultState) => {
|
||||
return new Vuex.Store({
|
||||
state,
|
||||
actions: {
|
||||
fetchIssuesForAllLists: () => {},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const createComponent = state => {
|
||||
const store = createStore({
|
||||
...defaultState,
|
||||
...state,
|
||||
});
|
||||
wrapper = shallowMount(BoardContent, {
|
||||
localVue,
|
||||
propsData: {
|
||||
lists: mockListsWithModel,
|
||||
canAdminList: true,
|
||||
groupId: 1,
|
||||
disabled: false,
|
||||
issueLinkBase: '/',
|
||||
rootPath: '/',
|
||||
boardId: '1',
|
||||
},
|
||||
store,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders a BoardColumn component per list', () => {
|
||||
expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length);
|
||||
});
|
||||
|
||||
it('does not display EpicsSwimlanes component', () => {
|
||||
expect(wrapper.contains(EpicsSwimlanes)).toBe(false);
|
||||
expect(wrapper.contains(GlAlert)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -1,3 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import List from '~/boards/models/list';
|
||||
import boardsStore from '~/boards/stores/boards_store';
|
||||
|
||||
export const boardObj = {
|
||||
|
@ -165,3 +167,36 @@ export const setMockEndpoints = (opts = {}) => {
|
|||
boardId,
|
||||
});
|
||||
};
|
||||
|
||||
export const mockLists = [
|
||||
{
|
||||
id: 'gid://gitlab/List/1',
|
||||
title: 'Backlog',
|
||||
position: null,
|
||||
listType: 'backlog',
|
||||
collapsed: false,
|
||||
label: null,
|
||||
assignee: null,
|
||||
milestone: null,
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/List/2',
|
||||
title: 'To Do',
|
||||
position: 0,
|
||||
listType: 'label',
|
||||
collapsed: false,
|
||||
label: {
|
||||
id: 'gid://gitlab/GroupLabel/121',
|
||||
title: 'To Do',
|
||||
color: '#F0AD4E',
|
||||
textColor: '#FFFFFF',
|
||||
description: null,
|
||||
},
|
||||
assignee: null,
|
||||
milestone: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockListsWithModel = mockLists.map(listMock =>
|
||||
Vue.observable(new List({ ...listMock, doNotFetchIssues: true })),
|
||||
);
|
||||
|
|
|
@ -113,6 +113,23 @@ describe('Board Store Mutations', () => {
|
|||
expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
|
||||
});
|
||||
|
||||
describe('RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE', () => {
|
||||
it('sets isLoadingIssues to false and sets error message', () => {
|
||||
state = {
|
||||
...state,
|
||||
isLoadingIssues: true,
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE(state);
|
||||
|
||||
expect(state.isLoadingIssues).toBe(false);
|
||||
expect(state.error).toEqual(
|
||||
'An error occurred while fetching the board issues. Please reload the page.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RECEIVE_ADD_ISSUE_SUCCESS', () => {
|
||||
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_SUCCESS);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import * as getters from '~/pipelines/stores/test_reports/getters';
|
||||
import { iconForTestStatus } from '~/pipelines/stores/test_reports/utils';
|
||||
import { iconForTestStatus, formattedTime } from '~/pipelines/stores/test_reports/utils';
|
||||
|
||||
describe('Getters TestReports Store', () => {
|
||||
let state;
|
||||
|
@ -34,7 +34,7 @@ describe('Getters TestReports Store', () => {
|
|||
const suites = getters.getTestSuites(state);
|
||||
const expected = testReports.test_suites.map(x => ({
|
||||
...x,
|
||||
formattedTime: '00:00:00',
|
||||
formattedTime: formattedTime(x.total_time),
|
||||
}));
|
||||
|
||||
expect(suites).toEqual(expected);
|
||||
|
@ -65,7 +65,7 @@ describe('Getters TestReports Store', () => {
|
|||
const cases = getters.getSuiteTests(state);
|
||||
const expected = testReports.test_suites[0].test_cases.map(x => ({
|
||||
...x,
|
||||
formattedTime: '00:00:00',
|
||||
formattedTime: formattedTime(x.execution_time),
|
||||
icon: iconForTestStatus(x.status),
|
||||
}));
|
||||
|
||||
|
|
26
spec/frontend/pipelines/test_reports/stores/utils_spec.js
Normal file
26
spec/frontend/pipelines/test_reports/stores/utils_spec.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { formattedTime } from '~/pipelines/stores/test_reports/utils';
|
||||
|
||||
describe('Test reports utils', () => {
|
||||
describe('formattedTime', () => {
|
||||
describe('when time is smaller than a second', () => {
|
||||
it('should return time in milliseconds fixed to 2 decimals', () => {
|
||||
const result = formattedTime(0.4815162342);
|
||||
expect(result).toBe('481.52ms');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when time is equal to a second', () => {
|
||||
it('should return time in seconds fixed to 2 decimals', () => {
|
||||
const result = formattedTime(1);
|
||||
expect(result).toBe('1.00s');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when time is greater than a second', () => {
|
||||
it('should return time in seconds fixed to 2 decimals', () => {
|
||||
const result = formattedTime(4.815162342);
|
||||
expect(result).toBe('4.82s');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import Summary from '~/pipelines/components/test_reports/test_summary.vue';
|
||||
import { formattedTime } from '~/pipelines/stores/test_reports/utils';
|
||||
|
||||
describe('Test reports summary', () => {
|
||||
let wrapper;
|
||||
|
@ -76,7 +77,7 @@ describe('Test reports summary', () => {
|
|||
});
|
||||
|
||||
it('displays the correctly formatted duration', () => {
|
||||
expect(duration().text()).toBe('00:00:00');
|
||||
expect(duration().text()).toBe(formattedTime(testSuite.total_time));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue