提交 273d780f 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 ed621e68
......@@ -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"
......
<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"
......
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,
});
fragment EpicNode on Epic {
id
iid
title
state
reference
webUrl
createdAt
closedAt
}
<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) {
......
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
......
- 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 } })
= 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}"
---
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 ""
......
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);
});
});
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);
});
......
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),
}));
......
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');
});
});
});
});
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));
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册