Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-28 12:10:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-28 12:10:13 +0300
commitd6024427e8036c93ccf04759a3725167ec6c02f4 (patch)
treef722805d09e3cf62c76f384bbaae9dccfa6f755e /app
parentc7836133e0d9287e147e9e19099e058a37e87a9a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/header.js4
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue293
-rw-r--r--app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue56
-rw-r--r--app/assets/javascripts/sidebar/components/date/sidebar_inherit_date.vue110
-rw-r--r--app/assets/javascripts/sidebar/components/due_date/sidebar_due_date_widget.vue203
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_editable_item.vue1
-rw-r--r--app/assets/javascripts/sidebar/constants.js37
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js6
-rw-r--r--app/assets/javascripts/sidebar/queries/epic_due_date.query.graphql13
-rw-r--r--app/assets/javascripts/sidebar/queries/epic_start_date.query.graphql13
-rw-r--r--app/assets/javascripts/sidebar/queries/update_epic_due_date.mutation.graphql11
-rw-r--r--app/assets/javascripts/sidebar/queries/update_epic_start_date.mutation.graphql11
-rw-r--r--app/assets/javascripts/sidebar/queries/update_issue_due_date.mutation.graphql2
-rw-r--r--app/assets/javascripts/tracking.js15
-rw-r--r--app/graphql/types/release_assets_type.rb2
-rw-r--r--app/helpers/page_layout_helper.rb1
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/release.rb10
-rw-r--r--app/presenters/project_presenter.rb8
-rw-r--r--app/views/profiles/show.html.haml7
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/packages/debian/process_changes_worker.rb49
23 files changed, 636 insertions, 232 deletions
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 4fed7f555f6..c2ef6414716 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -45,7 +45,6 @@ function initStatusTriggers() {
defaultEmoji,
currentMessage,
currentAvailability,
- canSetUserAvailability,
currentClearStatusAfter,
} = setStatusModalWrapperEl.dataset;
@@ -54,7 +53,6 @@ function initStatusTriggers() {
defaultEmoji,
currentMessage,
currentAvailability,
- canSetUserAvailability,
currentClearStatusAfter,
};
},
@@ -64,7 +62,6 @@ function initStatusTriggers() {
defaultEmoji,
currentMessage,
currentAvailability,
- canSetUserAvailability,
currentClearStatusAfter,
} = this;
@@ -74,7 +71,6 @@ function initStatusTriggers() {
defaultEmoji,
currentMessage,
currentAvailability,
- canSetUserAvailability,
currentClearStatusAfter,
},
});
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index bff90254c04..c754af5c7de 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -67,11 +67,6 @@ export default {
required: false,
default: '',
},
- canSetUserAvailability: {
- type: Boolean,
- required: false,
- default: false,
- },
currentClearStatusAfter: {
type: String,
required: false,
@@ -292,7 +287,7 @@ export default {
</button>
</span>
</div>
- <div v-if="canSetUserAvailability" class="form-group">
+ <div class="form-group">
<div class="gl-display-flex">
<gl-form-checkbox
v-model="availability"
diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
new file mode 100644
index 00000000000..f2f3671c753
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
@@ -0,0 +1,293 @@
+<script>
+import { GlIcon, GlDatepicker, GlTooltipDirective, GlLink, GlPopover } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { IssuableType } from '~/issue_show/constants';
+import { dateInWords, formatDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
+import { __, sprintf } from '~/locale';
+import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
+import { dateFields, dateTypes, dueDateQueries, startDateQueries } from '~/sidebar/constants';
+import SidebarFormattedDate from './sidebar_formatted_date.vue';
+import SidebarInheritDate from './sidebar_inherit_date.vue';
+
+const hideDropdownEvent = new CustomEvent('hiddenGlDropdown', {
+ bubbles: true,
+});
+
+export default {
+ tracking: {
+ event: 'click_edit_button',
+ label: 'right_sidebar',
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ components: {
+ GlIcon,
+ GlDatepicker,
+ GlLink,
+ GlPopover,
+ SidebarEditableItem,
+ SidebarFormattedDate,
+ SidebarInheritDate,
+ },
+ inject: ['canUpdate'],
+ props: {
+ iid: {
+ type: String,
+ required: true,
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ dateType: {
+ type: String,
+ required: false,
+ default: dateTypes.due,
+ },
+ issuableType: {
+ required: true,
+ type: String,
+ },
+ canInherit: {
+ required: false,
+ type: Boolean,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ issuable: {},
+ loading: false,
+ tracking: {
+ ...this.$options.tracking,
+ property: this.dateType === dateTypes.start ? 'startDate' : 'dueDate',
+ },
+ };
+ },
+ apollo: {
+ issuable: {
+ query() {
+ return this.dateQueries[this.issuableType].query;
+ },
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ iid: String(this.iid),
+ };
+ },
+ update(data) {
+ return data.workspace?.issuable || {};
+ },
+ result({ data }) {
+ this.$emit(`${this.dateType}Updated`, data.workspace?.issuable?.[this.dateType]);
+ },
+ error() {
+ createFlash({
+ message: sprintf(
+ __('Something went wrong while setting %{issuableType} %{dateType} date.'),
+ {
+ issuableType: this.issuableType,
+ dateType: this.dateType === dateTypes.start ? 'start' : 'due',
+ },
+ ),
+ });
+ },
+ },
+ },
+ computed: {
+ dateQueries() {
+ return this.dateType === dateTypes.start ? startDateQueries : dueDateQueries;
+ },
+ dateLabel() {
+ return this.dateType === dateTypes.start
+ ? this.$options.i18n.startDate
+ : this.$options.i18n.dueDate;
+ },
+ removeDateLabel() {
+ return this.dateType === dateTypes.start
+ ? this.$options.i18n.removeStartDate
+ : this.$options.i18n.removeDueDate;
+ },
+ dateValue() {
+ return this.issuable?.[this.dateType] || null;
+ },
+ isLoading() {
+ return this.$apollo.queries.issuable.loading || this.loading;
+ },
+ hasDate() {
+ return this.dateValue !== null;
+ },
+ parsedDate() {
+ if (!this.hasDate) {
+ return null;
+ }
+
+ return parsePikadayDate(this.dateValue);
+ },
+ formattedDate() {
+ if (!this.hasDate) {
+ return this.$options.i18n.noDate;
+ }
+
+ return dateInWords(this.parsedDate, true);
+ },
+ workspacePath() {
+ return this.issuableType === IssuableType.Issue
+ ? {
+ projectPath: this.fullPath,
+ }
+ : {
+ groupPath: this.fullPath,
+ };
+ },
+ dataTestId() {
+ return this.dateType === dateTypes.start ? 'start-date' : 'due-date';
+ },
+ },
+ methods: {
+ closeForm() {
+ this.$refs.editable.collapse();
+ this.$el.dispatchEvent(hideDropdownEvent);
+ this.$emit('closeForm');
+ },
+ openDatePicker() {
+ this.$refs.datePicker.calendar.show();
+ },
+ setFixedDate(isFixed) {
+ const date = this.issuable[dateFields[this.dateType].dateFixed];
+ this.setDate(date, isFixed);
+ },
+ setDate(date, isFixed = true) {
+ const formattedDate = date ? formatDate(date, 'yyyy-mm-dd') : null;
+ this.loading = true;
+ this.$refs.editable.collapse();
+ this.$apollo
+ .mutate({
+ mutation: this.dateQueries[this.issuableType].mutation,
+ variables: {
+ input: {
+ ...this.workspacePath,
+ iid: this.iid,
+ ...(this.canInherit
+ ? {
+ [dateFields[this.dateType].dateFixed]: isFixed ? formattedDate : undefined,
+ [dateFields[this.dateType].isDateFixed]: isFixed,
+ }
+ : {
+ [this.dateType]: formattedDate,
+ }),
+ },
+ },
+ })
+ .then(
+ ({
+ data: {
+ issuableSetDate: { errors },
+ },
+ }) => {
+ if (errors.length) {
+ createFlash({
+ message: errors[0],
+ });
+ } else {
+ this.$emit('closeForm');
+ }
+ },
+ )
+ .catch(() => {
+ createFlash({
+ message: sprintf(
+ __('Something went wrong while setting %{issuableType} %{dateType} date.'),
+ {
+ issuableType: this.issuableType,
+ dateType: this.dateType === dateTypes.start ? 'start' : 'due',
+ },
+ ),
+ });
+ })
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+ },
+ i18n: {
+ dueDate: __('Due date'),
+ startDate: __('Start date'),
+ noDate: __('None'),
+ removeDueDate: __('remove due date'),
+ removeStartDate: __('remove start date'),
+ dateHelpValidMessage: __(
+ 'These dates affect how your epics appear in the roadmap. Set a fixed date or one inherited from the milestones assigned to issues in this epic.',
+ ),
+ help: __('Help'),
+ learnMore: __('Learn more'),
+ },
+ dateHelpUrl: '/help/user/group/epics/index.md#start-date-and-due-date',
+};
+</script>
+
+<template>
+ <sidebar-editable-item
+ ref="editable"
+ :title="dateLabel"
+ :tracking="tracking"
+ :loading="isLoading"
+ class="block"
+ :data-testid="dataTestId"
+ @open="openDatePicker"
+ >
+ <template #title-extra>
+ <gl-icon
+ ref="epicDatePopover"
+ name="question-o"
+ class="gl-ml-3 gl-cursor-pointer gl-text-blue-600"
+ tabindex="0"
+ :aria-label="$options.i18n.help"
+ />
+ <gl-popover
+ :target="() => $refs.epicDatePopover.$el"
+ triggers="focus"
+ placement="left"
+ boundary="viewport"
+ >
+ <p>{{ $options.i18n.dateHelpValidMessage }}</p>
+ <gl-link :href="$options.dateHelpUrl" target="_blank">{{
+ $options.i18n.learnMore
+ }}</gl-link>
+ </gl-popover>
+ </template>
+ <template #collapsed>
+ <div v-gl-tooltip :title="dateLabel" class="sidebar-collapsed-icon">
+ <gl-icon :size="16" name="calendar" />
+ <span class="collapse-truncated-title">{{ formattedDate }}</span>
+ </div>
+ <sidebar-inherit-date
+ v-if="canInherit"
+ :issuable="issuable"
+ :is-loading="isLoading"
+ :date-type="dateType"
+ @reset-date="setDate(null)"
+ @set-date="setFixedDate"
+ />
+ <sidebar-formatted-date
+ v-else
+ :has-date="hasDate"
+ :formatted-date="formattedDate"
+ :reset-text="removeDateLabel"
+ :is-loading="isLoading"
+ @reset-date="setDate(null)"
+ />
+ </template>
+ <template #default>
+ <gl-datepicker
+ ref="datePicker"
+ class="gl-relative"
+ :value="parsedDate"
+ show-clear-button
+ @input="setDate"
+ @clear="setDate(null)"
+ />
+ </template>
+ </sidebar-editable-item>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue b/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue
new file mode 100644
index 00000000000..87cf1c29fb0
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlButton,
+ },
+ inject: ['canUpdate'],
+ props: {
+ formattedDate: {
+ required: true,
+ type: String,
+ },
+ hasDate: {
+ required: true,
+ type: Boolean,
+ },
+ resetText: {
+ required: true,
+ type: String,
+ },
+ isLoading: {
+ required: true,
+ type: Boolean,
+ },
+ canDelete: {
+ required: false,
+ type: Boolean,
+ default: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center hide-collapsed">
+ <span
+ :class="hasDate ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'"
+ data-testid="sidebar-date-value"
+ >
+ {{ formattedDate }}
+ </span>
+ <div v-if="hasDate && canUpdate && canDelete" class="gl-display-flex">
+ <span class="gl-px-2">-</span>
+ <gl-button
+ variant="link"
+ class="gl-text-gray-500!"
+ data-testid="reset-button"
+ :disabled="isLoading"
+ @click="$emit('reset-date', $event)"
+ >
+ {{ resetText }}
+ </gl-button>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_inherit_date.vue b/app/assets/javascripts/sidebar/components/date/sidebar_inherit_date.vue
new file mode 100644
index 00000000000..b6bfacb2e47
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/date/sidebar_inherit_date.vue
@@ -0,0 +1,110 @@
+<script>
+import { GlFormRadio } from '@gitlab/ui';
+import { dateInWords, parsePikadayDate } from '~/lib/utils/datetime_utility';
+import { __ } from '~/locale';
+import { dateFields } from '../../constants';
+import SidebarFormattedDate from './sidebar_formatted_date.vue';
+
+export default {
+ components: {
+ GlFormRadio,
+ SidebarFormattedDate,
+ },
+ inject: ['canUpdate'],
+ props: {
+ issuable: {
+ required: true,
+ type: Object,
+ },
+ isLoading: {
+ required: true,
+ type: Boolean,
+ },
+ dateType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ dateIsFixed: {
+ get() {
+ return this.issuable?.[dateFields[this.dateType].isDateFixed] || false;
+ },
+ set(fixed) {
+ this.$emit('set-date', fixed);
+ },
+ },
+ hasFixedDate() {
+ return this.issuable[dateFields[this.dateType].dateFixed] !== null;
+ },
+ formattedFixedDate() {
+ const dateFixed = this.issuable[dateFields[this.dateType].dateFixed];
+ if (!dateFixed) {
+ return this.$options.i18n.noDate;
+ }
+
+ return dateInWords(parsePikadayDate(dateFixed), true);
+ },
+ formattedInheritedDate() {
+ const dateFromMilestones = this.issuable[dateFields[this.dateType].dateFromMilestones];
+ if (!dateFromMilestones) {
+ return this.$options.i18n.noDate;
+ }
+
+ return dateInWords(parsePikadayDate(dateFromMilestones), true);
+ },
+ },
+ i18n: {
+ fixed: __('Fixed:'),
+ inherited: __('Inherited:'),
+ remove: __('remove'),
+ noDate: __('None'),
+ },
+};
+</script>
+
+<template>
+ <div class="hide-collapsed gl-mt-3">
+ <div class="gl-display-flex gl-align-items-baseline" data-testid="sidebar-fixed-date">
+ <gl-form-radio
+ v-model="dateIsFixed"
+ :value="true"
+ :disabled="!canUpdate || isLoading"
+ class="gl-pr-2"
+ >
+ <span :class="dateIsFixed ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'">
+ {{ $options.i18n.fixed }}
+ </span>
+ </gl-form-radio>
+ <sidebar-formatted-date
+ :has-date="dateIsFixed"
+ :formatted-date="formattedFixedDate"
+ :reset-text="$options.i18n.remove"
+ :is-loading="isLoading"
+ :can-delete="dateIsFixed && hasFixedDate"
+ class="gl-line-height-normal"
+ @reset-date="$emit('reset-date', $event)"
+ />
+ </div>
+ <div class="gl-display-flex gl-align-items-baseline" data-testid="sidebar-inherited-date">
+ <gl-form-radio
+ v-model="dateIsFixed"
+ :value="false"
+ :disabled="!canUpdate || isLoading"
+ class="gl-pr-2"
+ >
+ <span :class="!dateIsFixed ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'">
+ {{ $options.i18n.inherited }}
+ </span>
+ </gl-form-radio>
+ <sidebar-formatted-date
+ :has-date="!dateIsFixed"
+ :formatted-date="formattedInheritedDate"
+ :reset-text="$options.i18n.remove"
+ :is-loading="isLoading"
+ :can-delete="false"
+ class="gl-line-height-normal"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/due_date/sidebar_due_date_widget.vue b/app/assets/javascripts/sidebar/components/due_date/sidebar_due_date_widget.vue
deleted file mode 100644
index 141c2b3aae9..00000000000
--- a/app/assets/javascripts/sidebar/components/due_date/sidebar_due_date_widget.vue
+++ /dev/null
@@ -1,203 +0,0 @@
-<script>
-import { GlButton, GlIcon, GlDatepicker, GlTooltipDirective } from '@gitlab/ui';
-import createFlash from '~/flash';
-import { IssuableType } from '~/issue_show/constants';
-import { dateInWords, formatDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
-import { __, sprintf } from '~/locale';
-import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
-import { dueDateQueries } from '~/sidebar/constants';
-
-const hideDropdownEvent = new CustomEvent('hiddenGlDropdown', {
- bubbles: true,
-});
-
-export default {
- tracking: {
- event: 'click_edit_button',
- label: 'right_sidebar',
- property: 'dueDate',
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- components: {
- GlButton,
- GlIcon,
- GlDatepicker,
- SidebarEditableItem,
- },
- inject: ['fullPath', 'iid', 'canUpdate'],
- props: {
- issuableType: {
- required: true,
- type: String,
- },
- },
- data() {
- return {
- dueDate: null,
- loading: false,
- };
- },
- apollo: {
- dueDate: {
- query() {
- return dueDateQueries[this.issuableType].query;
- },
- variables() {
- return {
- fullPath: this.fullPath,
- iid: String(this.iid),
- };
- },
- update(data) {
- return data.workspace?.issuable?.dueDate || null;
- },
- result({ data }) {
- this.$emit('dueDateUpdated', data.workspace?.issuable?.dueDate);
- },
- error() {
- createFlash({
- message: sprintf(__('Something went wrong while setting %{issuableType} due date.'), {
- issuableType: this.issuableType,
- }),
- });
- },
- },
- },
- computed: {
- isLoading() {
- return this.$apollo.queries.dueDate.loading || this.loading;
- },
- hasDueDate() {
- return this.dueDate !== null;
- },
- parsedDueDate() {
- if (!this.hasDueDate) {
- return null;
- }
-
- return parsePikadayDate(this.dueDate);
- },
- formattedDueDate() {
- if (!this.hasDueDate) {
- return this.$options.i18n.noDueDate;
- }
-
- return dateInWords(this.parsedDueDate, true);
- },
- workspacePath() {
- return this.issuableType === IssuableType.Issue
- ? {
- projectPath: this.fullPath,
- }
- : {
- groupPath: this.fullPath,
- };
- },
- },
- methods: {
- closeForm() {
- this.$refs.editable.collapse();
- this.$el.dispatchEvent(hideDropdownEvent);
- this.$emit('closeForm');
- },
- openDatePicker() {
- this.$refs.datePicker.calendar.show();
- },
- setDueDate(date) {
- this.loading = true;
- this.$refs.editable.collapse();
- this.$apollo
- .mutate({
- mutation: dueDateQueries[this.issuableType].mutation,
- variables: {
- input: {
- ...this.workspacePath,
- iid: this.iid,
- dueDate: date ? formatDate(date, 'yyyy-mm-dd') : null,
- },
- },
- })
- .then(
- ({
- data: {
- issuableSetDueDate: { errors },
- },
- }) => {
- if (errors.length) {
- createFlash({
- message: errors[0],
- });
- } else {
- this.$emit('closeForm');
- }
- },
- )
- .catch(() => {
- createFlash({
- message: sprintf(__('Something went wrong while setting %{issuableType} due date.'), {
- issuableType: this.issuableType,
- }),
- });
- })
- .finally(() => {
- this.loading = false;
- });
- },
- },
- i18n: {
- dueDate: __('Due date'),
- noDueDate: __('None'),
- removeDueDate: __('remove due date'),
- },
-};
-</script>
-
-<template>
- <sidebar-editable-item
- ref="editable"
- :title="$options.i18n.dueDate"
- :tracking="$options.tracking"
- :loading="isLoading"
- class="block"
- data-testid="due-date"
- @open="openDatePicker"
- >
- <template #collapsed>
- <div v-gl-tooltip :title="$options.i18n.dueDate" class="sidebar-collapsed-icon">
- <gl-icon :size="16" name="calendar" />
- <span class="collapse-truncated-title">{{ formattedDueDate }}</span>
- </div>
- <div class="gl-display-flex gl-align-items-center hide-collapsed">
- <span
- :class="hasDueDate ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'"
- data-testid="sidebar-duedate-value"
- >
- {{ formattedDueDate }}
- </span>
- <div v-if="hasDueDate && canUpdate" class="gl-display-flex">
- <span class="gl-px-2">-</span>
- <gl-button
- variant="link"
- class="gl-text-gray-500!"
- data-testid="reset-button"
- :disabled="isLoading"
- @click="setDueDate(null)"
- >
- {{ $options.i18n.removeDueDate }}
- </gl-button>
- </div>
- </div>
- </template>
- <template #default>
- <gl-datepicker
- ref="datePicker"
- :value="parsedDueDate"
- show-clear-button
- @input="setDueDate"
- @clear="setDueDate(null)"
- />
- </template>
- </sidebar-editable-item>
-</template>
diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
index caf1c92c28a..809dd1b2dba 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
@@ -103,6 +103,7 @@ export default {
<div>
<div class="gl-display-flex gl-align-items-center" @click.self="collapse">
<span class="hide-collapsed" data-testid="title" @click="collapse">{{ title }}</span>
+ <slot name="title-extra"></slot>
<gl-loading-icon v-if="loading || initialLoading" inline class="gl-ml-2 hide-collapsed" />
<gl-loading-icon
v-if="loading && isClassicSidebar"
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index 58e4b0348ca..82cad094d6b 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -1,11 +1,15 @@
import { IssuableType } from '~/issue_show/constants';
import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql';
+import epicDueDateQuery from '~/sidebar/queries/epic_due_date.query.graphql';
+import epicStartDateQuery from '~/sidebar/queries/epic_start_date.query.graphql';
import issuableAssigneesSubscription from '~/sidebar/queries/issuable_assignees.subscription.graphql';
import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql';
import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
-import updateEpicMutation from '~/sidebar/queries/update_epic_confidential.mutation.graphql';
+import updateEpicConfidentialMutation from '~/sidebar/queries/update_epic_confidential.mutation.graphql';
+import updateEpicDueDateMutation from '~/sidebar/queries/update_epic_due_date.mutation.graphql';
+import updateEpicStartDateMutation from '~/sidebar/queries/update_epic_start_date.mutation.graphql';
import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql';
import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql';
@@ -34,7 +38,7 @@ export const confidentialityQueries = {
},
[IssuableType.Epic]: {
query: epicConfidentialQuery,
- mutation: updateEpicMutation,
+ mutation: updateEpicConfidentialMutation,
},
};
@@ -47,9 +51,38 @@ export const referenceQueries = {
},
};
+export const dateTypes = {
+ start: 'startDate',
+ due: 'dueDate',
+};
+
+export const dateFields = {
+ [dateTypes.start]: {
+ isDateFixed: 'startDateIsFixed',
+ dateFixed: 'startDateFixed',
+ dateFromMilestones: 'startDateFromMilestones',
+ },
+ [dateTypes.due]: {
+ isDateFixed: 'dueDateIsFixed',
+ dateFixed: 'dueDateFixed',
+ dateFromMilestones: 'dueDateFromMilestones',
+ },
+};
+
export const dueDateQueries = {
[IssuableType.Issue]: {
query: issueDueDateQuery,
mutation: updateIssueDueDateMutation,
},
+ [IssuableType.Epic]: {
+ query: epicDueDateQuery,
+ mutation: updateEpicDueDateMutation,
+ },
+};
+
+export const startDateQueries = {
+ [IssuableType.Epic]: {
+ query: epicStartDateQuery,
+ mutation: updateEpicStartDateMutation,
+ },
};
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 52a1efa04e4..9115c3562d3 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -13,7 +13,7 @@ import { __ } from '~/locale';
import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
-import SidebarDueDateWidget from '~/sidebar/components/due_date/sidebar_due_date_widget.vue';
+import SidebarDueDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import { apolloProvider } from '~/sidebar/graphql';
import Translate from '../vue_shared/translate';
@@ -225,14 +225,14 @@ function mountDueDateComponent() {
SidebarDueDateWidget,
},
provide: {
- iid: String(iid),
- fullPath,
canUpdate: editable,
},
render: (createElement) =>
createElement('sidebar-due-date-widget', {
props: {
+ iid: String(iid),
+ fullPath,
issuableType: IssuableType.Issue,
},
}),
diff --git a/app/assets/javascripts/sidebar/queries/epic_due_date.query.graphql b/app/assets/javascripts/sidebar/queries/epic_due_date.query.graphql
new file mode 100644
index 00000000000..f60f44abebd
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/epic_due_date.query.graphql
@@ -0,0 +1,13 @@
+query epicDueDate($fullPath: ID!, $iid: ID) {
+ workspace: group(fullPath: $fullPath) {
+ __typename
+ issuable: epic(iid: $iid) {
+ __typename
+ id
+ dueDate
+ dueDateIsFixed
+ dueDateFixed
+ dueDateFromMilestones
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/epic_start_date.query.graphql b/app/assets/javascripts/sidebar/queries/epic_start_date.query.graphql
new file mode 100644
index 00000000000..c6c24fd3d95
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/epic_start_date.query.graphql
@@ -0,0 +1,13 @@
+query epicStartDate($fullPath: ID!, $iid: ID) {
+ workspace: group(fullPath: $fullPath) {
+ __typename
+ issuable: epic(iid: $iid) {
+ __typename
+ id
+ startDate
+ startDateIsFixed
+ startDateFixed
+ startDateFromMilestones
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/update_epic_due_date.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_epic_due_date.mutation.graphql
new file mode 100644
index 00000000000..9b0a8b4a8f7
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/update_epic_due_date.mutation.graphql
@@ -0,0 +1,11 @@
+mutation updateEpicDueDate($input: UpdateEpicInput!) {
+ issuableSetDate: updateEpic(input: $input) {
+ issuable: epic {
+ id
+ dueDateIsFixed
+ dueDateFixed
+ dueDateFromMilestones
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/update_epic_start_date.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_epic_start_date.mutation.graphql
new file mode 100644
index 00000000000..9b4bb9159c3
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/update_epic_start_date.mutation.graphql
@@ -0,0 +1,11 @@
+mutation updateEpicStartDate($input: UpdateEpicInput!) {
+ issuableSetDate: updateEpic(input: $input) {
+ issuable: epic {
+ id
+ startDateIsFixed
+ startDateFixed
+ startDateFromMilestones
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/update_issue_due_date.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_issue_due_date.mutation.graphql
index cf7eccd61c7..4765b0b08cc 100644
--- a/app/assets/javascripts/sidebar/queries/update_issue_due_date.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/update_issue_due_date.mutation.graphql
@@ -1,5 +1,5 @@
mutation updateIssueDueDate($input: UpdateIssueInput!) {
- issuableSetDueDate: updateIssue(input: $input) {
+ issuableSetDate: updateIssue(input: $input) {
issuable: issue {
id
dueDate
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
index cdfecceb78a..d2e69bc06cf 100644
--- a/app/assets/javascripts/tracking.js
+++ b/app/assets/javascripts/tracking.js
@@ -153,6 +153,21 @@ export default class Tracking {
return loadEvents;
}
+ static enableFormTracking(config, contexts = []) {
+ if (!this.enabled()) return;
+
+ if (!config?.forms?.whitelist?.length && !config?.fields?.whitelist?.length) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('Unable to enable form event tracking without whitelist rules.');
+ }
+
+ contexts.unshift(STANDARD_CONTEXT);
+ const enabler = () => window.snowplow('enableFormTracking', config, contexts);
+
+ if (document.readyState !== 'loading') enabler();
+ else document.addEventListener('DOMContentLoaded', enabler);
+ }
+
static mixin(opts = {}) {
return {
computed: {
diff --git a/app/graphql/types/release_assets_type.rb b/app/graphql/types/release_assets_type.rb
index 79c132358e0..d847d9842d5 100644
--- a/app/graphql/types/release_assets_type.rb
+++ b/app/graphql/types/release_assets_type.rb
@@ -13,7 +13,7 @@ module Types
field :count, GraphQL::INT_TYPE, null: true, method: :assets_count,
description: 'Number of assets of the release.'
- field :links, Types::ReleaseAssetLinkType.connection_type, null: true,
+ field :links, Types::ReleaseAssetLinkType.connection_type, null: true, method: :sorted_links,
description: 'Asset links of the release.'
field :sources, Types::ReleaseSourceType.connection_type, null: true,
description: 'Sources of the release.'
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 6997c8cffda..2729951d685 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -162,7 +162,6 @@ module PageLayoutHelper
default_properties = {
current_emoji: '',
current_message: '',
- can_set_user_availability: Feature.enabled?(:set_user_availability_status, user, default_enabled: :yaml),
default_emoji: UserStatus::DEFAULT_EMOJI
}
diff --git a/app/models/project.rb b/app/models/project.rb
index 5033eb43979..2cddcc43c4b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -129,6 +129,7 @@ class Project < ApplicationRecord
after_create :check_repository_absence!
acts_as_ordered_taggable
+ alias_method :topics, :tag_list
attr_accessor :old_path_with_namespace
attr_accessor :template_name
diff --git a/app/models/release.rb b/app/models/release.rb
index 5ca8f537baa..5037a1558ca 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -13,6 +13,7 @@ class Release < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :links, class_name: 'Releases::Link'
+ has_many :sorted_links, -> { sorted }, class_name: 'Releases::Link'
has_many :milestone_releases
has_many :milestones, through: :milestone_releases
@@ -27,7 +28,10 @@ class Release < ApplicationRecord
validates :links, nested_attributes_duplicates: { scope: :release, child_attributes: %i[name url filepath] }
scope :sorted, -> { order(released_at: :desc) }
- scope :preloaded, -> { includes(:evidences, :milestones, project: [:project_feature, :route, { namespace: :route }]) }
+ scope :preloaded, -> {
+ includes(:author, :evidences, :milestones, :links, :sorted_links,
+ project: [:project_feature, :route, { namespace: :route }])
+ }
scope :with_project_and_namespace, -> { includes(project: :namespace) }
scope :recent, -> { sorted.limit(MAX_NUMBER_TO_DISPLAY) }
scope :without_evidence, -> { left_joins(:evidences).where(::Releases::Evidence.arel_table[:id].eq(nil)) }
@@ -58,8 +62,8 @@ class Release < ApplicationRecord
end
def assets_count(except: [])
- links_count = links.count
- sources_count = except.include?(:sources) ? 0 : sources.count
+ links_count = links.size
+ sources_count = except.include?(:sources) ? 0 : sources.size
links_count + sources_count
end
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index aad1c816cf1..681c5cfca74 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -390,16 +390,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def topics_to_show
- project.tag_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
+ project.topics.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
def topics_not_shown
- project.tag_list - topics_to_show
+ project.topics - topics_to_show
end
def count_of_extra_topics_not_shown
- if project.tag_list.count > MAX_TOPICS_TO_SHOW
- project.tag_list.count - MAX_TOPICS_TO_SHOW
+ if project.topics.count > MAX_TOPICS_TO_SHOW
+ project.topics.count - MAX_TOPICS_TO_SHOW
else
0
end
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 15544fb9c45..c3ec2f7bab3 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -70,10 +70,9 @@
prepend: emoji_button,
append: reset_message_button,
placeholder: s_("Profiles|What's your status?")
- - if Feature.enabled?(:set_user_availability_status, @user, default_enabled: :yaml)
- .checkbox-icon-inline-wrapper
- = status_form.check_box :availability, { data: { testid: "user-availability-checkbox" }, label: s_("Profiles|Busy"), wrapper_class: 'gl-mr-0 gl-font-weight-bold' }, availability["busy"], availability["not_set"]
- .gl-text-gray-600.gl-ml-5= s_('Profiles|"Busy" will be shown next to your name')
+ .checkbox-icon-inline-wrapper
+ = status_form.check_box :availability, { data: { testid: "user-availability-checkbox" }, label: s_("Profiles|Busy"), wrapper_class: 'gl-mr-0 gl-font-weight-bold' }, availability["busy"], availability["not_set"]
+ .gl-text-gray-600.gl-ml-5= s_('Profiles|"Busy" will be shown next to your name')
- if Feature.enabled?(:user_time_settings)
.col-lg-12
%hr
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index b7c39e829fd..f69d07b1dd9 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1115,6 +1115,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: package_repositories:packages_debian_process_changes
+ :feature_category: :package_registry
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: package_repositories:packages_go_sync_packages
:feature_category: :package_registry
:has_external_dependencies:
diff --git a/app/workers/packages/debian/process_changes_worker.rb b/app/workers/packages/debian/process_changes_worker.rb
new file mode 100644
index 00000000000..c1731112099
--- /dev/null
+++ b/app/workers/packages/debian/process_changes_worker.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Packages
+ module Debian
+ class ProcessChangesWorker
+ include ApplicationWorker
+ include Gitlab::Utils::StrongMemoize
+
+ deduplicate :until_executed
+ idempotent!
+
+ queue_namespace :package_repositories
+ feature_category :package_registry
+
+ def perform(package_file_id, user_id)
+ @package_file_id = package_file_id
+ @user_id = user_id
+
+ return unless package_file && user
+
+ ::Packages::Debian::ProcessChangesService.new(package_file, user).execute
+ rescue ArgumentError,
+ Packages::Debian::ExtractChangesMetadataService::ExtractionError,
+ Packages::Debian::ExtractDebMetadataService::CommandFailedError,
+ Packages::Debian::ExtractMetadataService::ExtractionError,
+ Packages::Debian::ParseDebian822Service::InvalidDebian822Error,
+ ActiveRecord::RecordNotFound => e
+ Gitlab::ErrorTracking.log_exception(e, package_file_id: @package_file_id, user_id: @user_id)
+ package_file.destroy!
+ end
+
+ private
+
+ attr_reader :package_file_id, :user_id
+
+ def package_file
+ strong_memoize(:package_file) do
+ ::Packages::PackageFile.find_by_id(package_file_id)
+ end
+ end
+
+ def user
+ strong_memoize(:user) do
+ ::User.find_by_id(user_id)
+ end
+ end
+ end
+ end
+end