Commit 3f364316 authored by David O'Regan's avatar David O'Regan Committed by Illya Klymov

Fix(oncallschedules): allow rotations to assign color

Allow rotations to be
assigned a color when a
rotation is created
parent 91701a2a
...@@ -9,10 +9,10 @@ import { ...@@ -9,10 +9,10 @@ import {
updateStoreAfterRotationAdd, updateStoreAfterRotationAdd,
updateStoreAfterRotationEdit, updateStoreAfterRotationEdit,
} from 'ee/oncall_schedules/utils/cache_updates'; } from 'ee/oncall_schedules/utils/cache_updates';
import { isNameFieldValid } from 'ee/oncall_schedules/utils/common_utils'; import { isNameFieldValid, getParticipantsForSave } from 'ee/oncall_schedules/utils/common_utils';
import createFlash, { FLASH_TYPES } from '~/flash'; import createFlash, { FLASH_TYPES } from '~/flash';
import usersSearchQuery from '~/graphql_shared/queries/users_search.query.graphql'; import usersSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
import { format24HourTimeStringFromInt } from '~/lib/utils/datetime_utility'; import { format24HourTimeStringFromInt, formatDate } from '~/lib/utils/datetime_utility';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import AddEditRotationForm from './add_edit_rotation_form.vue'; import AddEditRotationForm from './add_edit_rotation_form.vue';
...@@ -114,24 +114,26 @@ export default { ...@@ -114,24 +114,26 @@ export default {
}; };
}, },
rotationVariables() { rotationVariables() {
const {
name,
rotationLength,
participants,
startsAt: { date, time },
} = this.form;
return { return {
projectPath: this.projectPath, projectPath: this.projectPath,
scheduleIid: this.schedule.iid, scheduleIid: this.schedule.iid,
name: this.form.name, name,
startsAt: { startsAt: {
...this.form.startsAt, date: formatDate(date, 'yyyy-mm-dd'),
time: format24HourTimeStringFromInt(this.form.startsAt.time), time: format24HourTimeStringFromInt(time),
}, },
rotationLength: { rotationLength: {
...this.form.rotationLength, ...rotationLength,
length: parseInt(this.form.rotationLength.length, 10), length: parseInt(rotationLength.length, 10),
}, },
participants: this.form.participants.map(({ username }) => ({ participants: getParticipantsForSave(participants),
username,
// eslint-disable-next-line @gitlab/require-i18n-strings
colorWeight: 'WEIGHT_500',
colorPalette: 'BLUE',
})),
}; };
}, },
isFormValid() { isFormValid() {
......
...@@ -36,6 +36,10 @@ export default { ...@@ -36,6 +36,10 @@ export default {
startsAt: formatDate(this.rotationAssigneeStartsAt, 'mmmm d, yyyy, h:MMtt Z'), startsAt: formatDate(this.rotationAssigneeStartsAt, 'mmmm d, yyyy, h:MMtt Z'),
}); });
}, },
rotationAssigneeUniqueID() {
const { _uid } = this;
return `${this.assignee.user.id}-${_uid}`;
},
endsAt() { endsAt() {
return sprintf(__('Ends: %{endsAt}'), { return sprintf(__('Ends: %{endsAt}'), {
endsAt: formatDate(this.rotationAssigneeEndsAt, 'mmmm d, yyyy, h:MMtt Z'), endsAt: formatDate(this.rotationAssigneeEndsAt, 'mmmm d, yyyy, h:MMtt Z'),
...@@ -51,7 +55,7 @@ export default { ...@@ -51,7 +55,7 @@ export default {
:style="rotationAssigneeStyle" :style="rotationAssigneeStyle"
> >
<gl-token <gl-token
:id="assignee.user.id" :id="rotationAssigneeUniqueID"
class="gl-w-full gl-h-6 gl-align-items-center" class="gl-w-full gl-h-6 gl-align-items-center"
:class="chevronClass" :class="chevronClass"
:view-only="true" :view-only="true"
...@@ -65,10 +69,10 @@ export default { ...@@ -65,10 +69,10 @@ export default {
/> />
</gl-token> </gl-token>
<gl-popover <gl-popover
:target="assignee.user.id" :target="rotationAssigneeUniqueID"
:title="assignee.user.username" :title="assignee.user.username"
triggers="hover" triggers="hover"
placement="top" placement="left"
> >
<p class="gl-m-0" data-testid="rotation-assignee-starts-at">{{ startsAt }}</p> <p class="gl-m-0" data-testid="rotation-assignee-starts-at">{{ startsAt }}</p>
<p class="gl-m-0" data-testid="rotation-assignee-ends-at">{{ endsAt }}</p> <p class="gl-m-0" data-testid="rotation-assignee-ends-at">{{ endsAt }}</p>
......
...@@ -8,6 +8,19 @@ export const CHEVRON_SKIPPING_SHADE_ENUM = ['500', '600', '700', '800', '900', ' ...@@ -8,6 +8,19 @@ export const CHEVRON_SKIPPING_SHADE_ENUM = ['500', '600', '700', '800', '900', '
export const CHEVRON_SKIPPING_PALETTE_ENUM = ['blue', 'orange', 'aqua', 'green', 'magenta']; export const CHEVRON_SKIPPING_PALETTE_ENUM = ['blue', 'orange', 'aqua', 'green', 'magenta'];
/**
* an Array of Objects that represent the 30 possible
* color combinations for assignees
* @type {{colorWeight: string, colorPalette: string}[]}
*/
export const ASSIGNEE_COLORS_COMBO = CHEVRON_SKIPPING_SHADE_ENUM.map((shade) =>
CHEVRON_SKIPPING_PALETTE_ENUM.map((color) => ({
// eslint-disable-next-line @gitlab/require-i18n-strings
colorWeight: `WEIGHT_${shade.toUpperCase()}`,
colorPalette: color.toUpperCase(),
})),
).flat();
export const DAYS_IN_WEEK = 7; export const DAYS_IN_WEEK = 7;
export const HOURS_IN_DAY = 24; export const HOURS_IN_DAY = 24;
......
...@@ -213,7 +213,9 @@ export const updateStoreAfterScheduleEdit = (store, query, data, variables) => { ...@@ -213,7 +213,9 @@ export const updateStoreAfterScheduleEdit = (store, query, data, variables) => {
}; };
export const updateStoreAfterRotationAdd = (store, query, data, variables) => { export const updateStoreAfterRotationAdd = (store, query, data, variables) => {
if (!hasErrors(data)) { if (hasErrors(data)) {
onError(data, UPDATE_SCHEDULE_ERROR);
} else {
addRotationToStore(store, query, data, variables); addRotationToStore(store, query, data, variables);
} }
}; };
......
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import { ASSIGNEE_COLORS_COMBO } from '../constants';
/** /**
* Returns formatted timezone string, e.g. (UTC-09:00) AKST Alaska * Returns formatted timezone string, e.g. (UTC-09:00) AKST Alaska
...@@ -27,3 +28,27 @@ export const getFormattedTimezone = (tz) => { ...@@ -27,3 +28,27 @@ export const getFormattedTimezone = (tz) => {
export const isNameFieldValid = (nameField) => { export const isNameFieldValid = (nameField) => {
return Boolean(nameField?.length); return Boolean(nameField?.length);
}; };
/**
* Returns a Array of Objects that represent the shift participant
* with his/her username and unique shift color values
*
* @param {Object[]} participants
* @param {string} participants[].username - The username of the participant.
*
* @returns {Object[]} A list of values to save each participant
* @property {string} username
* @property {string} colorWeight
* @property {string} colorPalette
*/
export const getParticipantsForSave = (participants) =>
participants.map(({ username }, index) => {
const colorIndex = index % ASSIGNEE_COLORS_COMBO.length;
const { colorWeight, colorPalette } = ASSIGNEE_COLORS_COMBO[colorIndex];
return {
username,
colorWeight,
colorPalette,
};
});
import { getFormattedTimezone } from 'ee/oncall_schedules/utils/common_utils'; import { ASSIGNEE_COLORS_COMBO } from 'ee/oncall_schedules/constants';
import {
getFormattedTimezone,
getParticipantsForSave,
} from 'ee/oncall_schedules/utils/common_utils';
import mockTimezones from './mocks/mockTimezones.json'; import mockTimezones from './mocks/mockTimezones.json';
describe('getFormattedTimezone', () => { describe('getFormattedTimezone', () => {
...@@ -8,3 +12,18 @@ describe('getFormattedTimezone', () => { ...@@ -8,3 +12,18 @@ describe('getFormattedTimezone', () => {
expect(getFormattedTimezone(tz)).toBe(expectedValue); expect(getFormattedTimezone(tz)).toBe(expectedValue);
}); });
}); });
describe('getParticipantsForSave', () => {
it('returns participant shift color data along with the username', () => {
const participants = [{ username: 'user1' }, { username: 'user2' }, { username: 'user3' }];
const result = getParticipantsForSave(participants);
expect(result.length).toBe(participants.length);
result.forEach((participant, index) => {
const { colorWeight, colorPalette } = ASSIGNEE_COLORS_COMBO[index];
const { username } = participants[index];
expect(participant).toEqual({ username, colorWeight, colorPalette });
});
});
});
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "blue", "colorPalette": "blue",
"user": { "user": {
"id": "1",
"username": "nora.schaden" "username": "nora.schaden"
} }
}, },
...@@ -37,6 +38,7 @@ ...@@ -37,6 +38,7 @@
"colorWeight": "500", "colorWeight": "500",
"colorPalette": "orange", "colorPalette": "orange",
"user": { "user": {
"id": "2",
"username": "racheal.loving" "username": "racheal.loving"
} }
}, },
......
...@@ -53,7 +53,9 @@ describe('RotationAssignee', () => { ...@@ -53,7 +53,9 @@ describe('RotationAssignee', () => {
}); });
it('should render an assignee schedule and rotation information in a popover', () => { it('should render an assignee schedule and rotation information in a popover', () => {
expect(findPopOver().attributes('target')).toBe(assignee.id); // eslint-disable-next-line no-underscore-dangle
const UID = wrapper.vm._uid;
expect(findPopOver().attributes('target')).toBe(`${assignee.participant.user.id}-${UID}`);
expect(findStartsAt().text()).toContain(formattedDate(assignee.startsAt)); expect(findStartsAt().text()).toContain(formattedDate(assignee.startsAt));
expect(findEndsAt().text()).toContain(formattedDate(assignee.endsAt)); expect(findEndsAt().text()).toContain(formattedDate(assignee.endsAt));
}); });
......
...@@ -82,6 +82,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders ...@@ -82,6 +82,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
> >
<span <span
class="gl-w-full gl-h-6 gl-align-items-center gl-token gl-token-default-variant gl-bg-data-viz-blue-500" class="gl-w-full gl-h-6 gl-align-items-center gl-token gl-token-default-variant gl-bg-data-viz-blue-500"
id="1-12"
> >
<span <span
class="gl-token-content" class="gl-token-content"
...@@ -152,6 +153,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders ...@@ -152,6 +153,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
> >
<span <span
class="gl-w-full gl-h-6 gl-align-items-center gl-token gl-token-default-variant gl-bg-data-viz-orange-500" class="gl-w-full gl-h-6 gl-align-items-center gl-token gl-token-default-variant gl-bg-data-viz-orange-500"
id="2-18"
> >
<span <span
class="gl-token-content" class="gl-token-content"
......
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