diff options
Diffstat (limited to 'app/assets/javascripts/sidebar')
23 files changed, 163 insertions, 33 deletions
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue index ef40de82d01..c20dd3b677d 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue @@ -1,6 +1,7 @@ <script> import { GlTooltipDirective, GlLink } from '@gitlab/ui'; import { IssuableType } from '~/issues/constants'; +import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { __ } from '~/locale'; import { isUserBusy } from '~/set_status_modal/utils'; import AssigneeAvatar from './assignee_avatar.vue'; @@ -94,6 +95,9 @@ export default { assigneeUrl() { return this.user.web_url || this.user.webUrl; }, + assigneeId() { + return isGid(this.user.id) ? getIdFromGraphQLId(this.user.id) : this.user.id; + }, }, }; </script> @@ -103,7 +107,7 @@ export default { <gl-link :href="assigneeUrl" :title="tooltipTitle" - :data-user-id="user.id" + :data-user-id="assigneeId" data-placement="left" class="gl-display-inline-block js-user-link" > diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue index bdd014163a0..3602b5ec4f6 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue @@ -55,7 +55,12 @@ export default { {{ __('None') }} <template v-if="editable"> - - <button type="button" class="btn-link" data-testid="assign-yourself" @click="assignSelf"> + <button + type="button" + class="gl-button btn-link gl-reset-color!" + data-testid="assign-yourself" + @click="assignSelf" + > {{ __('assign yourself') }} </button> </template> diff --git a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue index af4227fa48d..46bda26c327 100644 --- a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue +++ b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue @@ -26,7 +26,7 @@ export default { }; </script> <template> - <button type="button" class="btn-link"> + <button type="button" class="gl-button btn-link"> <assignee-avatar :user="user" :img-size="24" :issuable-type="issuableType" /> <user-name-with-status :name="user.name" diff --git a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue index 50b1955abcc..f894ef0c42d 100644 --- a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue +++ b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue @@ -123,7 +123,7 @@ export default { :user="user" :issuable-type="issuableType" /> - <button v-if="hasMoreThanTwoAssignees" class="btn-link" type="button"> + <button v-if="hasMoreThanTwoAssignees" class="btn-link gl-button" type="button"> <span class="avatar-counter sidebar-avatar-counter gl-display-flex gl-align-items-center gl-pl-3" > diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue index 01d29da5486..b6260418837 100644 --- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue +++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue @@ -120,7 +120,7 @@ export default { <div v-if="renderShowMoreSection" class="user-list-more gl-hover-text-blue-800"> <button type="button" - class="btn-link" + class="btn-link gl-button gl-reset-color!" data-qa-selector="more_assignees_link" @click="toggleShowLess" > diff --git a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue index 031de669489..974ad189f32 100644 --- a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue +++ b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue @@ -49,14 +49,14 @@ export default { }, request() { const state = { - variant: 'default', + selected: false, icon: 'attention', direction: 'add', }; if (this.user.attention_requested) { Object.assign(state, { - variant: 'warning', + selected: true, icon: 'attention-solid', direction: 'remove', }); @@ -92,7 +92,7 @@ export default { > <gl-button :loading="loading" - :variant="request.variant" + :selected="request.selected" :icon="request.icon" :aria-label="tooltipTitle" :class="{ 'gl-pointer-events-none': !user.can_update_merge_request }" diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue index 71e40fde77d..c44ce8b0057 100644 --- a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue +++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue @@ -8,7 +8,7 @@ import { confidentialityQueries } from '~/sidebar/constants'; export default { i18n: { confidentialityOnWarning: __( - 'You are going to turn on confidentiality. Only %{context} members with %{strongStart}at least Reporter role%{strongEnd} can view or be notified about this %{issuableType}.', + 'You are going to turn on confidentiality. Only %{context} members with %{strongStart}%{permissions}%{strongEnd} can view or be notified about this %{issuableType}.', ), confidentialityOffWarning: __( 'You are going to turn off the confidentiality. This means %{strongStart}everyone%{strongEnd} will be able to see and leave a comment on this %{issuableType}.', @@ -65,6 +65,11 @@ export default { groupPath: this.fullPath, }; }, + permissions() { + return this.issuableType === IssuableType.Issue + ? __('at least the Reporter role, the author, and assignees') + : __('at least the Reporter role'); + }, }, methods: { submitForm() { @@ -120,7 +125,11 @@ export default { <p data-testid="warning-message"> <gl-sprintf :message="warningMessage"> <template #strong="{ content }"> - <strong>{{ content }}</strong> + <strong> + <gl-sprintf :message="content"> + <template #permissions>{{ permissions }}</template> + </gl-sprintf> + </strong> </template> <template #context>{{ context }}</template> <template #issuableType>{{ issuableType }}</template> diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue index be7a89c2869..ef99d540c86 100644 --- a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue +++ b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue @@ -274,7 +274,7 @@ export default { <template #collapsed> <div v-gl-tooltip.viewport.left :title="dateLabel" class="sidebar-collapsed-icon"> <gl-icon :size="16" name="calendar" /> - <span class="collapse-truncated-title">{{ formattedDate }}</span> + <span class="gl-pt-2 gl-px-3 gl-font-sm">{{ formattedDate }}</span> </div> <sidebar-inherit-date v-if="canInherit && !initialLoading" diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue index 2ab46a7a655..8145506f32c 100644 --- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue +++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue @@ -74,7 +74,7 @@ export default { <gl-button data-testid="lock-toggle" category="secondary" - variant="warning" + variant="confirm" :disabled="isLoading" :loading="isLoading" @click.prevent="submitForm" diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue index 77e41648e9b..b8804de653f 100644 --- a/app/assets/javascripts/sidebar/components/participants/participants.vue +++ b/app/assets/javascripts/sidebar/components/participants/participants.vue @@ -99,7 +99,9 @@ export default { > <gl-icon name="users" /> <gl-loading-icon v-if="loading" size="sm" /> - <span v-else data-testid="collapsed-count"> {{ participantCount }} </span> + <span v-else data-testid="collapsed-count" class="gl-pt-2 gl-px-3 gl-font-sm"> + {{ participantCount }} + </span> </div> <div v-if="showParticipantLabel" diff --git a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer.vue b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer.vue index 6de926e0ff9..2ea7c125a85 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer.vue @@ -17,7 +17,7 @@ export default { </script> <template> - <button type="button" class="btn-link"> + <button type="button" class="btn-link gl-button"> <reviewer-avatar :user="user" :img-size="24" /> <span class="author"> {{ user.name }} </span> </button> diff --git a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue index e09b5d913f7..9502b2e78b3 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue @@ -95,7 +95,7 @@ export default { > <gl-icon v-if="hasNoUsers" name="user" :aria-label="__('None')" /> <collapsed-reviewer v-for="user in collapsedUsers" :key="user.id" :user="user" /> - <button v-if="hasMoreThanTwoReviewers" class="btn-link" type="button"> + <button v-if="hasMoreThanTwoReviewers" class="btn-link gl-button" type="button"> <span class="avatar-counter sidebar-avatar-counter gl-display-flex gl-align-items-center gl-pl-3" > diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue index 897cab45fe4..3d8a2cd847c 100644 --- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue +++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue @@ -322,7 +322,7 @@ export default { class="sidebar-collapsed-icon" > <gl-icon :aria-label="attributeTypeTitle" :name="attributeTypeIcon" /> - <span class="collapse-truncated-title"> + <span class="collapse-truncated-title gl-pt-2 gl-px-3 gl-font-sm"> {{ attributeTitle }} </span> </div> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue index 7b67c34ded6..465f971717f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue @@ -58,7 +58,7 @@ export default { } else if (this.showEstimateOnlyState || this.showSpentOnlyState) { return 'bold'; } else if (this.showNoTimeTrackingState) { - return 'no-value'; + return 'no-value collapse-truncated-title gl-pt-2 gl-px-3 gl-font-sm'; } return ''; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js b/app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js new file mode 100644 index 00000000000..70177d84b1b --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js @@ -0,0 +1,20 @@ +import produce from 'immer'; + +export function removeTimelogFromStore(store, deletedTimelogId, query, variables) { + const sourceData = store.readQuery({ + query, + variables, + }); + + const data = produce(sourceData, (draftData) => { + draftData.issuable.timelogs.nodes = draftData.issuable.timelogs.nodes.filter( + ({ id }) => id !== deletedTimelogId, + ); + }); + + store.writeQuery({ + query, + variables, + data, + }); +} diff --git a/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql b/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql new file mode 100644 index 00000000000..17bbad1acb1 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql @@ -0,0 +1,5 @@ +mutation deleteTimelog($input: TimelogDeleteInput!) { + timelogDelete(input: $input) { + errors + } +} diff --git a/app/assets/javascripts/sidebar/components/time_tracking/report.vue b/app/assets/javascripts/sidebar/components/time_tracking/report.vue index d9797961d40..79ef5a32474 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/report.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/report.vue @@ -1,11 +1,13 @@ <script> -import { GlLoadingIcon, GlTableLite } from '@gitlab/ui'; +import { GlLoadingIcon, GlTableLite, GlButton, GlTooltipDirective } from '@gitlab/ui'; import createFlash from '~/flash'; import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { formatDate, parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import { timelogQueries } from '~/sidebar/constants'; +import deleteTimelogMutation from './graphql/mutations/delete_timelog.mutation.graphql'; +import { removeTimelogFromStore } from './graphql/cache_update'; const TIME_DATE_FORMAT = 'mmmm d, yyyy, HH:MM ("UTC:" o)'; @@ -13,6 +15,10 @@ export default { components: { GlLoadingIcon, GlTableLite, + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, }, inject: ['issuableType'], props: { @@ -27,7 +33,7 @@ export default { }, }, data() { - return { report: [], isLoading: true }; + return { report: [], isLoading: true, removingIds: [] }; }, apollo: { report: { @@ -35,9 +41,7 @@ export default { return timelogQueries[this.issuableType].query; }, variables() { - return { - id: convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId), - }; + return this.getQueryVariables(); }, update(data) { this.isLoading = false; @@ -48,10 +52,23 @@ export default { }, }, }, + computed: { + deleteButtonTooltip() { + return s__('TimeTracking|Delete time spent'); + }, + }, methods: { + isDeletingTimelog(timelogId) { + return this.removingIds.includes(timelogId); + }, isIssue() { return this.issuableType === 'issue'; }, + getQueryVariables() { + return { + id: convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId), + }; + }, getGraphQLEntityType() { return this.isIssue() ? TYPE_ISSUE : TYPE_MERGE_REQUEST; }, @@ -76,19 +93,51 @@ export default { stringifyTime(parseSeconds(seconds, { limitToHours: this.limitToHours })) ); }, + deleteTimelog(timelogId) { + this.removingIds.push(timelogId); + this.$apollo + .mutate({ + mutation: deleteTimelogMutation, + variables: { input: { id: timelogId } }, + update: (store) => { + removeTimelogFromStore( + store, + timelogId, + timelogQueries[this.issuableType].query, + this.getQueryVariables(), + ); + }, + }) + .then(({ data }) => { + if (data.timelogDelete?.errors?.length) { + throw new Error(data.timelogDelete.errors[0]); + } + }) + .catch((error) => { + createFlash({ + message: s__('TimeTracking|An error occurred while removing the timelog.'), + captureError: true, + error, + }); + }) + .finally(() => { + this.removingIds.splice(this.removingIds.indexOf(timelogId), 1); + }); + }, }, fields: [ - { key: 'spentAt', label: __('Spent At'), sortable: true, tdClass: 'gl-w-quarter' }, + { key: 'spentAt', label: __('Spent at'), sortable: true, tdClass: 'gl-w-quarter' }, { key: 'user', label: __('User'), sortable: true }, - { key: 'timeSpent', label: __('Time Spent'), sortable: true, tdClass: 'gl-w-15' }, - { key: 'summary', label: __('Summary / Note'), sortable: true }, + { key: 'timeSpent', label: __('Time spent'), sortable: true, tdClass: 'gl-w-15' }, + { key: 'summary', label: __('Summary / note'), sortable: true }, + { key: 'actions', label: '', tdClass: 'gl-w-10' }, ], }; </script> <template> <div> - <div v-if="isLoading"><gl-loading-icon size="md" /></div> + <div v-if="isLoading"><gl-loading-icon size="lg" /></div> <gl-table-lite v-else :items="report" :fields="$options.fields" foot-clone> <template #cell(spentAt)="{ item: { spentAt } }"> <div>{{ formatDate(spentAt) }}</div> @@ -110,7 +159,28 @@ export default { <template #cell(summary)="{ item: { summary, note } }"> <div>{{ getSummary(summary, note) }}</div> </template> - <template #foot(note)> </template> + <template #foot(summary)> </template> + + <template + #cell(actions)="{ + item: { + id, + userPermissions: { adminTimelog }, + }, + }" + > + <div v-if="adminTimelog"> + <gl-button + v-gl-tooltip="{ title: deleteButtonTooltip }" + category="secondary" + icon="remove" + data-testid="deleteButton" + :loading="isDeletingTimelog(id)" + @click="deleteTimelog(id)" + /> + </div> + </template> + <template #foot(actions)> </template> </gl-table-lite> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index 057bb9f0100..e39d9f9fb49 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -252,6 +252,7 @@ export default { size="lg" :title="__('Time tracking report')" :hide-footer="true" + @hide="refresh" > <time-tracking-report :limit-to-hours="limitToHours" :issuable-id="issuableId" /> </gl-modal> diff --git a/app/assets/javascripts/sidebar/graphql.js b/app/assets/javascripts/sidebar/graphql.js index 034bdc71122..ff3fb4aae6b 100644 --- a/app/assets/javascripts/sidebar/graphql.js +++ b/app/assets/javascripts/sidebar/graphql.js @@ -2,6 +2,7 @@ import produce from 'immer'; import VueApollo from 'vue-apollo'; import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql'; import createDefaultClient from '~/lib/graphql'; +import { temporaryConfig } from '~/work_items/graphql/provider'; const resolvers = { Mutation: { @@ -15,7 +16,12 @@ const resolvers = { }, }; -export const defaultClient = createDefaultClient(resolvers); +export const defaultClient = createDefaultClient( + resolvers, + // should be removed with the rollout of work item assignees FF + // https://gitlab.com/gitlab-org/gitlab/-/issues/363030 + temporaryConfig, +); export const apolloProvider = new VueApollo({ defaultClient, diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index 351bb50d941..bb40ac14438 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -119,7 +119,7 @@ function mountAssigneesComponentDeprecated(mediator) { issuableIid: String(iid), projectPath: fullPath, field: el.dataset.field, - signedIn: el.hasAttribute('data-signed-in'), + signedIn: Object.prototype.hasOwnProperty.call(el.dataset, 'signedIn'), issuableType: isInIssuePage() || isInIncidentPage() || isInDesignPage() ? IssuableType.Issue @@ -149,7 +149,10 @@ function mountAssigneesComponent() { }, provide: { canUpdate: editable, - directlyInviteMembers: el.hasAttribute('data-directly-invite-members'), + directlyInviteMembers: Object.prototype.hasOwnProperty.call( + el.dataset, + 'directlyInviteMembers', + ), }, render: (createElement) => createElement('sidebar-assignees-widget', { diff --git a/app/assets/javascripts/sidebar/queries/escalation_status.fragment.graphql b/app/assets/javascripts/sidebar/queries/escalation_status.fragment.graphql new file mode 100644 index 00000000000..6021570557e --- /dev/null +++ b/app/assets/javascripts/sidebar/queries/escalation_status.fragment.graphql @@ -0,0 +1,4 @@ +fragment EscalationStatusFragment on Issue { + id + escalationStatus +} diff --git a/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql index a4aff7968df..d271ae5ff3e 100644 --- a/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql +++ b/app/assets/javascripts/sidebar/queries/update_escalation_status.mutation.graphql @@ -1,10 +1,12 @@ +#import "ee_else_ce/sidebar/queries/escalation_status.fragment.graphql" + mutation updateEscalationStatus($projectPath: ID!, $status: IssueEscalationStatus!, $iid: String!) { issueSetEscalationStatus(input: { projectPath: $projectPath, status: $status, iid: $iid }) { errors clientMutationId issue { id - escalationStatus + ...EscalationStatusFragment } } } diff --git a/app/assets/javascripts/sidebar/utils.js b/app/assets/javascripts/sidebar/utils.js deleted file mode 100644 index 20cd4ce9d99..00000000000 --- a/app/assets/javascripts/sidebar/utils.js +++ /dev/null @@ -1 +0,0 @@ -export const toLabelGid = (id) => `gid://gitlab/Label/${id}`; |