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 {
updateStoreAfterRotationAdd,
updateStoreAfterRotationEdit,
} 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 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 AddEditRotationForm from './add_edit_rotation_form.vue';
......@@ -114,24 +114,26 @@ export default {
};
},
rotationVariables() {
const {
name,
rotationLength,
participants,
startsAt: { date, time },
} = this.form;
return {
projectPath: this.projectPath,
scheduleIid: this.schedule.iid,
name: this.form.name,
name,
startsAt: {
...this.form.startsAt,
time: format24HourTimeStringFromInt(this.form.startsAt.time),
date: formatDate(date, 'yyyy-mm-dd'),
time: format24HourTimeStringFromInt(time),
},
rotationLength: {
...this.form.rotationLength,
length: parseInt(this.form.rotationLength.length, 10),
},
participants: this.form.participants.map(({ username }) => ({
username,
// eslint-disable-next-line @gitlab/require-i18n-strings
colorWeight: 'WEIGHT_500',
colorPalette: 'BLUE',
})),
...rotationLength,
length: parseInt(rotationLength.length, 10),
},
participants: getParticipantsForSave(participants),
};
},
isFormValid() {
......
......@@ -36,6 +36,10 @@ export default {
startsAt: formatDate(this.rotationAssigneeStartsAt, 'mmmm d, yyyy, h:MMtt Z'),
});
},
rotationAssigneeUniqueID() {
const { _uid } = this;
return `${this.assignee.user.id}-${_uid}`;
},
endsAt() {
return sprintf(__('Ends: %{endsAt}'), {
endsAt: formatDate(this.rotationAssigneeEndsAt, 'mmmm d, yyyy, h:MMtt Z'),
......@@ -51,7 +55,7 @@ export default {
:style="rotationAssigneeStyle"
>
<gl-token
:id="assignee.user.id"
:id="rotationAssigneeUniqueID"
class="gl-w-full gl-h-6 gl-align-items-center"
:class="chevronClass"
:view-only="true"
......@@ -65,10 +69,10 @@ export default {
/>
</gl-token>
<gl-popover
:target="assignee.user.id"
:target="rotationAssigneeUniqueID"
:title="assignee.user.username"
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-ends-at">{{ endsAt }}</p>
......
......@@ -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'];
/**
* 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 HOURS_IN_DAY = 24;
......
......@@ -213,7 +213,9 @@ export const updateStoreAfterScheduleEdit = (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);
}
};
......
import { sprintf, __ } from '~/locale';
import { ASSIGNEE_COLORS_COMBO } from '../constants';
/**
* Returns formatted timezone string, e.g. (UTC-09:00) AKST Alaska
......@@ -27,3 +28,27 @@ export const getFormattedTimezone = (tz) => {
export const isNameFieldValid = (nameField) => {
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';
describe('getFormattedTimezone', () => {
......@@ -8,3 +12,18 @@ describe('getFormattedTimezone', () => {
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 @@
"colorWeight": "500",
"colorPalette": "blue",
"user": {
"id": "1",
"username": "nora.schaden"
}
},
......@@ -37,6 +38,7 @@
"colorWeight": "500",
"colorPalette": "orange",
"user": {
"id": "2",
"username": "racheal.loving"
}
},
......
......@@ -53,7 +53,9 @@ describe('RotationAssignee', () => {
});
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(findEndsAt().text()).toContain(formattedDate(assignee.endsAt));
});
......
......@@ -82,6 +82,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
>
<span
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
class="gl-token-content"
......@@ -152,6 +153,7 @@ exports[`RotationsListSectionComponent when the timeframe includes today renders
>
<span
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
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