Commit 9fc9b1b4 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 93f9815e d2171720
<script>
import { GlAvatar, GlAvatarLink, GlLink, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { escape } from 'lodash';
export default {
components: {
GlAvatar,
GlAvatarLink,
GlLink,
},
directives: {
GlTooltip,
},
props: {
commit: {
required: true,
type: Object,
},
},
computed: {
commitMessage() {
return this.commit?.message;
},
commitAuthorPath() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return this.commit?.author?.path || `mailto:${escape(this.commit?.authorEmail)}`;
},
commitAuthorAvatar() {
return this.commit?.author?.avatarUrl || this.commit?.authorGravatarUrl;
},
commitAuthor() {
return this.commit?.author?.name || this.commit?.authorName;
},
commitPath() {
return this.commit?.commitPath;
},
},
};
</script>
<template>
<div data-testid="deployment-commit" class="gl-display-flex gl-align-items-center">
<gl-avatar-link v-gl-tooltip :title="commitAuthor" :href="commitAuthorPath">
<gl-avatar :size="16" :src="commitAuthorAvatar" />
</gl-avatar-link>
<gl-link
v-gl-tooltip
:title="commitMessage"
:href="commitPath"
class="gl-ml-3 gl-str-truncated"
>
{{ commitMessage }}
</gl-link>
</div>
</template>
......@@ -5,10 +5,12 @@ import { __, s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import DeploymentStatusBadge from './deployment_status_badge.vue';
import Commit from './commit.vue';
export default {
components: {
ClipboardButton,
Commit,
DeploymentStatusBadge,
GlBadge,
GlButton,
......@@ -41,7 +43,7 @@ export default {
return this.deployment?.iid;
},
shortSha() {
return this.deployment?.commit?.shortId;
return this.commit?.shortId;
},
createdAt() {
return this.deployment?.createdAt;
......@@ -57,6 +59,9 @@ export default {
detailsButtonClasses() {
return this.isMobile ? 'gl-sr-only' : '';
},
commit() {
return this.deployment?.commit;
},
},
methods: {
toggleCollapse() {
......@@ -143,6 +148,7 @@ export default {
{{ detailsButton.text }}
</gl-button>
</div>
<commit v-if="commit" :commit="commit" class="gl-mt-3" />
<gl-collapse :visible="visible" />
</div>
</template>
......@@ -4,7 +4,6 @@ import { mapState, mapActions } from 'vuex';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DATE_RANGES } from '../constants';
import EpicsListEmpty from './epics_list_empty.vue';
import RoadmapFilters from './roadmap_filters.vue';
import RoadmapSettings from './roadmap_settings.vue';
......@@ -20,15 +19,6 @@ export default {
},
mixins: [glFeatureFlagMixin()],
props: {
timeframeRangeType: {
type: String,
required: false,
default: DATE_RANGES.CURRENT_QUARTER,
},
presetType: {
type: String,
required: true,
},
emptyStateIllustrationPath: {
type: String,
required: true,
......@@ -52,6 +42,8 @@ export default {
'isChildEpics',
'hasFiltersApplied',
'filterParams',
'presetType',
'timeframeRangeType',
]),
showFilteredSearchbar() {
if (this.epicsFetchResultEmpty) {
......
<script>
import { GlFormGroup, GlFormRadioGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { mapState } from 'vuex';
import { mapActions, mapState } from 'vuex';
import { visitUrl, mergeUrlParams } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import { PRESET_TYPES, DATE_RANGES } from '../constants';
......@@ -59,6 +58,7 @@ export default {
},
},
methods: {
...mapActions(['setDaterange', 'fetchEpics', 'fetchMilestones']),
handleDaterangeSelect(value) {
this.selectedDaterange = value;
},
......@@ -67,24 +67,19 @@ export default {
},
handleDaterangeDropdownClose() {
if (this.initialSelectedDaterange !== this.selectedDaterange) {
visitUrl(
mergeUrlParams(
{
timeframe_range_type: this.selectedDaterange,
layout: getPresetTypeForTimeframeRangeType(this.selectedDaterange),
},
window.location.href,
),
);
this.setDaterange({
timeframeRangeType: this.selectedDaterange,
presetType: getPresetTypeForTimeframeRangeType(this.selectedDaterange),
});
this.fetchEpics();
this.fetchMilestones();
}
},
handleRoadmapLayoutChange(presetType) {
visitUrl(
mergeUrlParams(
{ timeframe_range_type: this.selectedDaterange, layout: presetType },
window.location.href,
),
);
if (presetType !== this.presetType) {
this.setDaterange({ timeframeRangeType: this.selectedDaterange, presetType });
this.fetchEpics();
}
},
},
i18n: {
......
......@@ -129,8 +129,6 @@ export default () => {
render(createElement) {
return createElement('roadmap-app', {
props: {
timeframeRangeType: this.timeframeRangeType,
presetType: this.presetType,
emptyStateIllustrationPath: this.emptyStateIllustrationPath,
},
});
......
......@@ -319,6 +319,9 @@ export const setBufferSize = ({ commit }, bufferSize) => commit(types.SET_BUFFER
export const setEpicsState = ({ commit }, epicsState) => commit(types.SET_EPICS_STATE, epicsState);
export const setDaterange = ({ commit }, { timeframeRangeType, presetType }) =>
commit(types.SET_DATERANGE, { timeframeRangeType, presetType });
export const setFilterParams = ({ commit }, filterParams) =>
commit(types.SET_FILTER_PARAMS, filterParams);
......
......@@ -29,5 +29,6 @@ export const RECEIVE_MILESTONES_FAILURE = 'RECEIVE_MILESTONES_FAILURE';
export const SET_BUFFER_SIZE = 'SET_BUFFER_SIZE';
export const SET_EPICS_STATE = 'SET_EPICS_STATE';
export const SET_DATERANGE = 'SET_DATERANGE';
export const SET_FILTER_PARAMS = 'SET_FILTER_PARAMS';
export const SET_SORTED_BY = 'SET_SORTED_BY';
import Vue from 'vue';
import { getTimeframeForRangeType } from '../utils/roadmap_utils';
import * as types from './mutation_types';
const resetEpics = (state) => {
......@@ -93,6 +94,8 @@ export default {
},
[types.REQUEST_MILESTONES](state) {
state.milestonesFetchInProgress = true;
state.milestones = [];
state.milestoneIds = [];
},
[types.RECEIVE_MILESTONES_SUCCESS](state, milestones) {
state.milestonesFetchInProgress = false;
......@@ -122,6 +125,16 @@ export default {
resetEpics(state);
},
[types.SET_DATERANGE](state, { timeframeRangeType, presetType }) {
state.timeframeRangeType = timeframeRangeType;
state.presetType = presetType;
state.timeframe = getTimeframeForRangeType({
timeframeRangeType,
presetType,
});
resetEpics(state);
},
[types.SET_SORTED_BY](state, sortedBy) {
state.childrenEpics = {};
state.sortedBy = sortedBy;
......
......@@ -21,6 +21,11 @@ export default {
required: false,
default: () => [],
},
selectedBranchesNames: {
type: Array,
required: false,
default: () => [],
},
isInvalid: {
type: Boolean,
required: false,
......@@ -33,9 +38,20 @@ export default {
initialLoading: false,
searching: false,
searchTerm: '',
selected: this.selectedBranches[0] || ALL_BRANCHES,
selected: null,
};
},
computed: {
selectedBranch() {
const idsOnly = this.selectedBranches.map((branch) => branch.id);
const selectedById = this.branches.find((branch) => idsOnly.includes(branch.id));
const selectedByName = this.branches.find((branch) =>
this.selectedBranchesNames.includes(branch.name),
);
return selectedById || selectedByName || this.selected || ALL_BRANCHES;
},
},
mounted() {
this.initialLoading = true;
this.fetchBranches()
......@@ -67,7 +83,7 @@ export default {
this.fetchBranches(this.searchTerm);
}, BRANCH_FETCH_DELAY),
isSelectedBranch(id) {
return this.selected.id === id;
return this.selectedBranch.id === id;
},
onSelect(branch) {
this.selected = branch;
......@@ -89,7 +105,7 @@ export default {
<gl-dropdown
:class="{ 'is-invalid': isInvalid }"
class="gl-w-full gl-dropdown-menu-full-width"
:text="selected.name"
:text="selectedBranch.name"
:loading="initialLoading"
:header-text="$options.i18n.header"
>
......
......@@ -167,8 +167,6 @@ RSpec.describe 'group epic roadmap', :js do
page.find('[data-testid="daterange-dropdown"] button.dropdown-toggle').click
click_button(range_type)
end
open_settings_sidebar
end
it 'renders daterange filtering dropdown with "This quarter" selected by default no layout presets available', :aggregate_failures do
......
......@@ -30,6 +30,7 @@ describe('RoadmapApp', () => {
const epics = [mockFormattedEpic];
const hasFiltersApplied = true;
const presetType = PRESET_TYPES.MONTHS;
const timeframeRangeType = DATE_RANGES.CURRENT_YEAR;
const timeframe = getTimeframeForRangeType({
timeframeRangeType: DATE_RANGES.CURRENT_YEAR,
presetType: PRESET_TYPES.MONTHS,
......@@ -40,7 +41,6 @@ describe('RoadmapApp', () => {
return shallowMountExtended(RoadmapApp, {
propsData: {
emptyStateIllustrationPath,
presetType,
},
provide: {
groupFullPath: 'gitlab-org',
......@@ -64,6 +64,7 @@ describe('RoadmapApp', () => {
hasFiltersApplied,
filterQueryString: '',
basePath,
timeframeRangeType,
});
});
......
......@@ -487,14 +487,14 @@ describe('Roadmap Vuex Actions', () => {
beforeEach(() => {
mockState = {
fullPath: 'gitlab-org',
milestonessState: 'active',
milestonesState: 'active',
presetType: PRESET_TYPES.MONTHS,
timeframe: mockTimeframeMonths,
};
expectedVariables = {
fullPath: 'gitlab-org',
state: mockState.milestonessState,
state: mockState.milestonesState,
timeframe: {
start: '2018-01-01',
end: '2018-12-31',
......@@ -644,4 +644,21 @@ describe('Roadmap Vuex Actions', () => {
);
});
});
describe('setDaterange', () => {
it('should set epicsState in store state', () => {
return testAction(
actions.setDaterange,
{ timeframeRangeType: 'CURRENT_YEAR', presetType: 'MONTHS' },
state,
[
{
type: types.SET_DATERANGE,
payload: { timeframeRangeType: 'CURRENT_YEAR', presetType: 'MONTHS' },
},
],
[],
);
});
});
});
......@@ -2,6 +2,7 @@ import * as types from 'ee/roadmap/store/mutation_types';
import mutations from 'ee/roadmap/store/mutations';
import defaultState from 'ee/roadmap/store/state';
import { getTimeframeForRangeType } from 'ee/roadmap/utils/roadmap_utils';
import {
mockGroupId,
......@@ -303,6 +304,28 @@ describe('Roadmap Store Mutations', () => {
});
});
describe('SET_DATERANGE', () => {
it('Should set `timeframeRangeType`, `presetType` and `timeframe` to the state and reset existing epics', () => {
const timeframeRangeType = 'CURRENT_YEAR';
const presetType = 'MONTHS';
setEpicMockData(state);
mutations[types.SET_DATERANGE](state, { timeframeRangeType, presetType });
expect(state).toMatchObject({
timeframeRangeType,
presetType,
timeframe: getTimeframeForRangeType({
timeframeRangeType,
presetType,
}),
epics: [],
childrenFlags: {},
epicIds: [],
});
});
});
describe('SET_SORTED_BY', () => {
it('Should set `sortedBy` to the state and reset existing parent epics and children epics', () => {
const sortedBy = 'start_date_asc';
......
......@@ -56,25 +56,29 @@ describe('Protected Branches Selector', () => {
expect(findDropdown().classes('is-invalid')).toBe(true);
});
it('sets the initially selected item', async () => {
createComponent({
selectedBranches: [
{
id: 1,
name: 'main',
},
],
});
await waitForPromises();
expect(findDropdown().props('text')).toBe('main');
expect(
findDropdownItems()
.filter((item) => item.text() === 'main')
.at(0)
.props('isChecked'),
).toBe(true);
});
it.each`
selectedBranches | selectedBranchesNames | branchName
${[]} | ${['development']} | ${'development'}
${[{ id: 1, name: 'main' }]} | ${[]} | ${'main'}
${[{ id: 1, name: 'main' }]} | ${['development']} | ${'main'}
`(
'with selectedBranches and selectedBranchesNames set to $selectedBranches and $selectedBranchesNames the item checked is: $branchName',
async ({ selectedBranches, selectedBranchesNames, branchName }) => {
createComponent({
selectedBranches,
selectedBranchesNames,
});
await waitForPromises();
expect(findDropdown().props('text')).toBe(branchName);
expect(
findDropdownItems()
.filter((item) => item.text() === branchName)
.at(0)
.props('isChecked'),
).toBe(true);
},
);
it('displays all the protected branches and all branches', async () => {
createComponent();
......
import { mountExtended } from 'helpers/vue_test_utils_helper';
import Commit from '~/environments/components/commit.vue';
import { resolvedEnvironment } from './graphql/mock_data';
describe('~/environments/components/commit.vue', () => {
let commit;
let wrapper;
beforeEach(() => {
commit = resolvedEnvironment.lastDeployment.commit;
});
const createWrapper = ({ propsData = {} } = {}) =>
mountExtended(Commit, {
propsData: {
commit,
...propsData,
},
});
afterEach(() => {
wrapper?.destroy();
});
describe('with gitlab user', () => {
beforeEach(() => {
wrapper = createWrapper();
});
it('links to the user profile', () => {
const link = wrapper.findByRole('link', { name: commit.author.name });
expect(link.attributes('href')).toBe(commit.author.path);
});
it('displays the user avatar', () => {
const avatar = wrapper.findByRole('img', { name: 'avatar' });
expect(avatar.attributes('src')).toBe(commit.author.avatarUrl);
});
it('links the commit message to the commit', () => {
const message = wrapper.findByRole('link', { name: commit.message });
expect(message.attributes('href')).toBe(commit.commitPath);
});
});
describe('without gitlab user', () => {
beforeEach(() => {
commit = {
...commit,
author: null,
};
wrapper = createWrapper();
});
it('links to the user profile', () => {
const link = wrapper.findByRole('link', { name: commit.authorName });
expect(link.attributes('href')).toBe(`mailto:${commit.authorEmail}`);
});
it('displays the user avatar', () => {
const avatar = wrapper.findByRole('img', { name: 'avatar' });
expect(avatar.attributes('src')).toBe(commit.authorGravatarUrl);
});
it('displays the commit message', () => {
const message = wrapper.findByRole('link', { name: commit.message });
expect(message.attributes('href')).toBe(commit.commitPath);
});
});
});
......@@ -6,15 +6,20 @@ import { formatDate } from '~/lib/utils/datetime_utility';
import { __, s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Deployment from '~/environments/components/deployment.vue';
import Commit from '~/environments/components/commit.vue';
import DeploymentStatusBadge from '~/environments/components/deployment_status_badge.vue';
import { resolvedEnvironment } from './graphql/mock_data';
describe('~/environments/components/deployment.vue', () => {
useFakeDate(2022, 0, 8, 16);
const deployment = resolvedEnvironment.lastDeployment;
let deployment;
let wrapper;
beforeEach(() => {
deployment = resolvedEnvironment.lastDeployment;
});
const createWrapper = ({ propsData = {} } = {}) =>
mountExtended(Deployment, {
propsData: {
......@@ -152,6 +157,32 @@ describe('~/environments/components/deployment.vue', () => {
});
});
describe('commit message', () => {
describe('with commit', () => {
beforeEach(() => {
wrapper = createWrapper();
});
it('shows the commit component', () => {
const commit = wrapper.findComponent(Commit);
expect(commit.props('commit')).toBe(deployment.commit);
});
});
describe('without a commit', () => {
it('displays nothing', () => {
const noCommit = {
...deployment,
commit: null,
};
wrapper = createWrapper({ propsData: { deployment: noCommit } });
const commit = wrapper.findComponent(Commit);
expect(commit.exists()).toBe(false);
});
});
});
describe('collapse', () => {
let collapse;
let button;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment