Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a544d1da18
commit
146abb4a88
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { omit, throttle } from 'lodash';
|
||||
import { isEmpty, omit, throttle } from 'lodash';
|
||||
import { GlLink, GlDeprecatedButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
|
||||
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
|
||||
import { s__ } from '~/locale';
|
||||
|
@ -45,6 +45,11 @@ export default {
|
|||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
timeRange: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
seriesConfig: {
|
||||
type: Object,
|
||||
required: false,
|
||||
|
@ -174,10 +179,17 @@ export default {
|
|||
chartOptions() {
|
||||
const { yAxis, xAxis } = this.option;
|
||||
const option = omit(this.option, ['series', 'yAxis', 'xAxis']);
|
||||
const xAxisBounds = isEmpty(this.timeRange)
|
||||
? {}
|
||||
: {
|
||||
min: this.timeRange.start,
|
||||
max: this.timeRange.end,
|
||||
};
|
||||
|
||||
const timeXAxis = {
|
||||
...getTimeAxisOptions({ timezone: this.timezone }),
|
||||
...xAxis,
|
||||
...xAxisBounds,
|
||||
};
|
||||
|
||||
const dataYAxis = {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { mapState } from 'vuex';
|
||||
import { pickBy } from 'lodash';
|
||||
import invalidUrl from '~/lib/utils/invalid_url';
|
||||
import { convertToFixedRange } from '~/lib/utils/datetime_range';
|
||||
import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
GlResizeObserverDirective,
|
||||
|
@ -130,6 +131,15 @@ export default {
|
|||
return getters[`${this.namespace}/selectedDashboard`];
|
||||
},
|
||||
}),
|
||||
fixedCurrentTimeRange() {
|
||||
// convertToFixedRange throws an error if the time range
|
||||
// is not properly set.
|
||||
try {
|
||||
return convertToFixedRange(this.timeRange);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
title() {
|
||||
return this.graphData?.title || '';
|
||||
},
|
||||
|
@ -468,6 +478,7 @@ export default {
|
|||
:thresholds="getGraphAlertValues(graphData.metrics)"
|
||||
:group-id="groupId"
|
||||
:timezone="dashboardTimezone"
|
||||
:time-range="fixedCurrentTimeRange"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@datazoom="onDatazoom"
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'FormFieldContainer',
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-lg-5 col-xl-4 gl-display-flex gl-flex-direction-column">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -2,10 +2,11 @@
|
|||
import { mapState } from 'vuex';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import FormFieldContainer from './form_field_container.vue';
|
||||
|
||||
export default {
|
||||
name: 'TagFieldExisting',
|
||||
components: { GlFormGroup, GlFormInput, GlSprintf, GlLink },
|
||||
components: { GlFormGroup, GlFormInput, GlSprintf, GlLink, FormFieldContainer },
|
||||
computed: {
|
||||
...mapState('detail', ['release', 'updateReleaseApiDocsPath']),
|
||||
inputId() {
|
||||
|
@ -19,18 +20,16 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<gl-form-group :label="__('Tag name')" :label-for="inputId">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-lg-5 col-xl-4">
|
||||
<gl-form-input
|
||||
:id="inputId"
|
||||
:value="release.tagName"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:aria-describedby="helpId"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<form-field-container>
|
||||
<gl-form-input
|
||||
:id="inputId"
|
||||
:value="release.tagName"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:aria-describedby="helpId"
|
||||
disabled
|
||||
/>
|
||||
</form-field-container>
|
||||
<template #description>
|
||||
<div :id="helpId" data-testid="tag-name-help">
|
||||
<gl-sprintf
|
||||
|
|
|
@ -1,8 +1,72 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import FormFieldContainer from './form_field_container.vue';
|
||||
|
||||
export default {
|
||||
name: 'TagFieldNew',
|
||||
components: { GlFormGroup, GlFormInput, RefSelector, FormFieldContainer },
|
||||
computed: {
|
||||
...mapState('detail', ['projectId', 'release', 'createFrom']),
|
||||
tagName: {
|
||||
get() {
|
||||
return this.release.tagName;
|
||||
},
|
||||
set(tagName) {
|
||||
this.updateReleaseTagName(tagName);
|
||||
},
|
||||
},
|
||||
createFromModel: {
|
||||
get() {
|
||||
return this.createFrom;
|
||||
},
|
||||
set(createFrom) {
|
||||
this.updateCreateFrom(createFrom);
|
||||
},
|
||||
},
|
||||
tagNameInputId() {
|
||||
return uniqueId('tag-name-input-');
|
||||
},
|
||||
createFromSelectorId() {
|
||||
return uniqueId('create-from-selector-');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('detail', ['updateReleaseTagName', 'updateCreateFrom']),
|
||||
},
|
||||
translations: {
|
||||
noRefSelected: __('No source selected'),
|
||||
searchPlaceholder: __('Search branches, tags, and commits'),
|
||||
dropdownHeader: __('Select source'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<gl-form-group :label="__('Tag name')" :label-for="tagNameInputId" data-testid="tag-name-field">
|
||||
<form-field-container>
|
||||
<gl-form-input :id="tagNameInputId" v-model="tagName" type="text" class="form-control" />
|
||||
</form-field-container>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
:label="__('Create from')"
|
||||
:label-for="createFromSelectorId"
|
||||
data-testid="create-from-field"
|
||||
>
|
||||
<form-field-container>
|
||||
<ref-selector
|
||||
:id="createFromSelectorId"
|
||||
v-model="createFromModel"
|
||||
:project-id="projectId"
|
||||
:translations="$options.translations"
|
||||
/>
|
||||
</form-field-container>
|
||||
<template #description>
|
||||
{{ __('Existing branch name, tag, or commit SHA') }}
|
||||
</template>
|
||||
</gl-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -34,6 +34,10 @@ export const fetchRelease = ({ dispatch, state }) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const updateReleaseTagName = ({ commit }, tagName) =>
|
||||
commit(types.UPDATE_RELEASE_TAG_NAME, tagName);
|
||||
export const updateCreateFrom = ({ commit }, createFrom) =>
|
||||
commit(types.UPDATE_CREATE_FROM, createFrom);
|
||||
export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title);
|
||||
export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
|
||||
export const updateReleaseMilestones = ({ commit }, milestones) =>
|
||||
|
|
|
@ -2,6 +2,8 @@ export const REQUEST_RELEASE = 'REQUEST_RELEASE';
|
|||
export const RECEIVE_RELEASE_SUCCESS = 'RECEIVE_RELEASE_SUCCESS';
|
||||
export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
|
||||
|
||||
export const UPDATE_RELEASE_TAG_NAME = 'UPDATE_RELEASE_TAG_NAME';
|
||||
export const UPDATE_CREATE_FROM = 'UPDATE_CREATE_FROM';
|
||||
export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
|
||||
export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
|
||||
export const UPDATE_RELEASE_MILESTONES = 'UPDATE_RELEASE_MILESTONES';
|
||||
|
|
|
@ -22,6 +22,12 @@ export default {
|
|||
state.release = undefined;
|
||||
},
|
||||
|
||||
[types.UPDATE_RELEASE_TAG_NAME](state, tagName) {
|
||||
state.release.tagName = tagName;
|
||||
},
|
||||
[types.UPDATE_CREATE_FROM](state, createFrom) {
|
||||
state.createFrom = createFrom;
|
||||
},
|
||||
[types.UPDATE_RELEASE_TITLE](state, title) {
|
||||
state.release.name = title;
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ export default ({
|
|||
|
||||
releasesPagePath,
|
||||
defaultBranch,
|
||||
createFrom: defaultBranch,
|
||||
|
||||
/** The Release object */
|
||||
release: null,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show full time range in metrics dashboard charts
|
||||
merge_request: 37243
|
||||
author:
|
||||
type: added
|
|
@ -7024,6 +7024,9 @@ msgstr ""
|
|||
msgid "Create file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create from"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9884,6 +9887,9 @@ msgstr ""
|
|||
msgid "Excluding merge commits. Limited to 6,000 commits."
|
||||
msgstr ""
|
||||
|
||||
msgid "Existing branch name, tag, or commit SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "Existing members and groups"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16325,6 +16331,9 @@ msgstr ""
|
|||
msgid "No schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "No source selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "No stack trace for this error"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21100,6 +21109,9 @@ msgstr ""
|
|||
msgid "Search branches and tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search branches, tags, and commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search by Git revision"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21729,6 +21741,9 @@ msgstr ""
|
|||
msgid "Select shards to replicate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select source"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select source branch"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -12,7 +12,12 @@ import {
|
|||
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
|
||||
import { panelTypes, chartHeight } from '~/monitoring/constants';
|
||||
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
|
||||
import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data';
|
||||
import {
|
||||
deploymentData,
|
||||
mockProjectDir,
|
||||
annotationsData,
|
||||
mockFixedTimeRange,
|
||||
} from '../../mock_data';
|
||||
|
||||
import { timeSeriesGraphData } from '../../graph_data';
|
||||
|
||||
|
@ -42,6 +47,7 @@ describe('Time series component', () => {
|
|||
deploymentData,
|
||||
annotations: annotationsData,
|
||||
projectPath: `${TEST_HOST}${mockProjectDir}`,
|
||||
timeRange: mockFixedTimeRange,
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
|
@ -382,6 +388,25 @@ describe('Time series component', () => {
|
|||
});
|
||||
|
||||
describe('chartOptions', () => {
|
||||
describe('x-Axis bounds', () => {
|
||||
it('is set to the time range bounds', () => {
|
||||
expect(getChartOptions().xAxis).toMatchObject({
|
||||
min: mockFixedTimeRange.start,
|
||||
max: mockFixedTimeRange.end,
|
||||
});
|
||||
});
|
||||
|
||||
it('is not set if time range is not set or incorrectly set', () => {
|
||||
wrapper.setProps({
|
||||
timeRange: {},
|
||||
});
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(getChartOptions().xAxis).not.toHaveProperty('min');
|
||||
expect(getChartOptions().xAxis).not.toHaveProperty('max');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataZoom', () => {
|
||||
it('renders with scroll handle icons', () => {
|
||||
expect(getChartOptions().dataZoom).toHaveLength(1);
|
||||
|
|
|
@ -254,6 +254,35 @@ describe('Dashboard Panel', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('fixedCurrentTimeRange', () => {
|
||||
it('returns fixed time for valid time range', () => {
|
||||
state.timeRange = mockTimeRange;
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findTimeChart().props('timeRange')).toEqual(
|
||||
expect.objectContaining({
|
||||
start: expect.any(String),
|
||||
end: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
input | output
|
||||
${''} | ${{}}
|
||||
${undefined} | ${{}}
|
||||
${null} | ${{}}
|
||||
${'2020-12-03'} | ${{}}
|
||||
`('returns $output for invalid input like $input', ({ input, output }) => {
|
||||
state.timeRange = input;
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findTimeChart().props('timeRange')).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit custom metric dropdown item', () => {
|
||||
|
|
|
@ -343,6 +343,11 @@ export const mockNamespaces = [`${baseNamespace}/1`, `${baseNamespace}/2`];
|
|||
|
||||
export const mockTimeRange = { duration: { seconds: 120 } };
|
||||
|
||||
export const mockFixedTimeRange = {
|
||||
start: '2020-06-17T19:59:08.659Z',
|
||||
end: '2020-07-17T19:59:08.659Z',
|
||||
};
|
||||
|
||||
export const mockNamespacedData = {
|
||||
mockDeploymentData: ['mockDeploymentData'],
|
||||
mockProjectPath: '/mockProjectPath',
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlFormInput } from '@gitlab/ui';
|
||||
import TagFieldNew from '~/releases/components/tag_field_new.vue';
|
||||
import createStore from '~/releases/stores';
|
||||
import createDetailModule from '~/releases/stores/modules/detail';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
|
||||
const TEST_TAG_NAME = 'test-tag-name';
|
||||
const TEST_PROJECT_ID = '1234';
|
||||
const TEST_CREATE_FROM = 'test-create-from';
|
||||
|
||||
describe('releases/components/tag_field_new', () => {
|
||||
let store;
|
||||
|
@ -16,9 +22,17 @@ describe('releases/components/tag_field_new', () => {
|
|||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
detail: createDetailModule({}),
|
||||
detail: createDetailModule({
|
||||
projectId: TEST_PROJECT_ID,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
store.state.detail.createFrom = TEST_CREATE_FROM;
|
||||
|
||||
store.state.detail.release = {
|
||||
tagName: TEST_TAG_NAME,
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -26,9 +40,47 @@ describe('releases/components/tag_field_new', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
it('renders a placeholder component', () => {
|
||||
createComponent();
|
||||
const findTagNameFormGroup = () => wrapper.find('[data-testid="tag-name-field"]');
|
||||
const findTagNameGlInput = () => findTagNameFormGroup().find(GlFormInput);
|
||||
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
const findCreateFromFormGroup = () => wrapper.find('[data-testid="create-from-field"]');
|
||||
const findCreateFromDropdown = () => findCreateFromFormGroup().find(RefSelector);
|
||||
|
||||
describe('"Tag name" field', () => {
|
||||
beforeEach(createComponent);
|
||||
|
||||
it('renders a label', () => {
|
||||
expect(findTagNameFormGroup().attributes().label).toBe('Tag name');
|
||||
});
|
||||
|
||||
describe('when the user updates the field', () => {
|
||||
it("updates the store's release.tagName property", () => {
|
||||
const updatedTagName = 'updated-tag-name';
|
||||
findTagNameGlInput().vm.$emit('input', updatedTagName);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(store.state.detail.release.tagName).toBe(updatedTagName);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"Create from" field', () => {
|
||||
beforeEach(createComponent);
|
||||
|
||||
it('renders a label', () => {
|
||||
expect(findCreateFromFormGroup().attributes().label).toBe('Create from');
|
||||
});
|
||||
|
||||
describe('when the user selects a git ref', () => {
|
||||
it("updates the store's createFrom property", () => {
|
||||
const updatedCreateFrom = 'update-create-from';
|
||||
findCreateFromDropdown().vm.$emit('input', updatedCreateFrom);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(store.state.detail.createFrom).toBe(updatedCreateFrom);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -113,6 +113,24 @@ describe('Release detail actions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('updateReleaseTagName', () => {
|
||||
it(`commits ${types.UPDATE_RELEASE_TAG_NAME} with the updated tag name`, () => {
|
||||
const newTag = 'updated-tag-name';
|
||||
return testAction(actions.updateReleaseTagName, newTag, state, [
|
||||
{ type: types.UPDATE_RELEASE_TAG_NAME, payload: newTag },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCreateFrom', () => {
|
||||
it(`commits ${types.UPDATE_CREATE_FROM} with the updated ref`, () => {
|
||||
const newRef = 'my-feature-branch';
|
||||
return testAction(actions.updateCreateFrom, newRef, state, [
|
||||
{ type: types.UPDATE_CREATE_FROM, payload: newRef },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateReleaseTitle', () => {
|
||||
it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => {
|
||||
const newTitle = 'The new release title';
|
||||
|
|
|
@ -56,6 +56,26 @@ describe('Release detail mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(`${types.UPDATE_RELEASE_TAG_NAME}`, () => {
|
||||
it("updates the release's tag name", () => {
|
||||
state.release = release;
|
||||
const newTag = 'updated-tag-name';
|
||||
mutations[types.UPDATE_RELEASE_TAG_NAME](state, newTag);
|
||||
|
||||
expect(state.release.tagName).toBe(newTag);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.UPDATE_CREATE_FROM}`, () => {
|
||||
it('updates the ref that the ref will be created from', () => {
|
||||
state.createFrom = 'main';
|
||||
const newRef = 'my-feature-branch';
|
||||
mutations[types.UPDATE_CREATE_FROM](state, newRef);
|
||||
|
||||
expect(state.createFrom).toBe(newRef);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.UPDATE_RELEASE_TITLE}`, () => {
|
||||
it("updates the release's title", () => {
|
||||
state.release = release;
|
||||
|
|
Loading…
Reference in New Issue