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:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/dropzone_input.js3
-rw-r--r--app/assets/javascripts/issuable_list/components/issuable_item.vue79
-rw-r--r--app/assets/javascripts/issuable_list/components/issuable_list_root.vue45
-rw-r--r--app/assets/javascripts/issuable_list/components/issuable_tabs.vue5
-rw-r--r--app/assets/javascripts/issuable_list/constants.js49
-rw-r--r--app/assets/javascripts/notes/components/note_attachment.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue9
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/help_state.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue10
-rw-r--r--app/assets/javascripts/tracking.js1
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/fontawesome_custom.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/ci_status.scss (renamed from app/assets/stylesheets/pages/status.scss)2
-rw-r--r--app/assets/stylesheets/page_bundles/todos.scss1
-rw-r--r--app/graphql/resolvers/releases_resolver.rb16
-rw-r--r--app/graphql/types/release_sort_enum.rb18
-rw-r--r--app/models/service.rb3
-rw-r--r--app/services/admin/propagate_integration_service.rb2
-rw-r--r--app/services/notes/create_service.rb6
-rw-r--r--app/services/notes/destroy_service.rb7
-rw-r--r--app/services/notes/update_service.rb6
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/events/event/_note.html.haml2
-rw-r--r--app/views/projects/jobs/index.html.haml1
-rw-r--r--app/views/projects/jobs/show.html.haml1
-rw-r--r--app/views/projects/merge_requests/creations/new.html.haml1
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--app/views/projects/pipelines/index.html.haml1
-rw-r--r--app/views/projects/pipelines/show.html.haml1
-rw-r--r--app/views/shared/notes/_note.html.haml2
36 files changed, 266 insertions, 46 deletions
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index f65e22a31c5..f56c402091f 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -7,6 +7,7 @@ import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';
import { n__, __ } from '~/locale';
import { getFilename } from '~/lib/utils/file_upload';
+import { spriteIcon } from '~/lib/utils/common_utils';
Dropzone.autoDiscover = false;
@@ -25,7 +26,7 @@ function getErrorMessage(res) {
export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
const divHover = '<div class="div-dropzone-hover"></div>';
- const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
+ const iconPaperclip = spriteIcon('paperclip', 'div-dropzone-icon');
const $attachButton = form.find('.button-attach-file');
const $attachingFileMessage = form.find('.attaching-file-message');
const $cancelButton = form.find('.button-cancel-uploading-files');
diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue
index d8cb1ab07cd..283e49c287a 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_item.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue
@@ -1,15 +1,20 @@
<script>
-import { GlLink, GlLabel, GlTooltipDirective } from '@gitlab/ui';
+import { GlLink, GlIcon, GlLabel, GlTooltipDirective } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { isScopedLabel } from '~/lib/utils/common_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
+import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
+
export default {
components: {
GlLink,
+ GlIcon,
GlLabel,
+ IssuableAssignees,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -24,25 +29,33 @@ export default {
type: Object,
required: true,
},
+ enableLabelPermalinks: {
+ type: Boolean,
+ required: true,
+ },
},
computed: {
author() {
return this.issuable.author;
},
authorId() {
- const id = parseInt(this.author.id, 10);
-
- if (Number.isNaN(id)) {
- return this.author.id.includes('gid')
- ? this.author.id.split('gid://gitlab/User/').pop()
- : '';
+ return getIdFromGraphQLId(`${this.author.id}`);
+ },
+ isIssuableUrlExternal() {
+ // Check if URL is relative, which means it is internal.
+ if (!/^https?:\/\//g.test(this.issuable.webUrl)) {
+ return false;
}
-
- return id;
+ // In case URL is absolute, it may or may not be internal,
+ // hence use `gon.gitlab_url` which is current instance domain.
+ return !this.issuable.webUrl.includes(gon.gitlab_url);
},
labels() {
return this.issuable.labels?.nodes || this.issuable.labels || [];
},
+ assignees() {
+ return this.issuable.assignees || [];
+ },
createdAt() {
return sprintf(__('created %{timeAgo}'), {
timeAgo: getTimeago().format(this.issuable.createdAt),
@@ -53,11 +66,33 @@ export default {
timeAgo: getTimeago().format(this.issuable.updatedAt),
});
},
+ issuableTitleProps() {
+ if (this.isIssuableUrlExternal) {
+ return {
+ target: '_blank',
+ };
+ }
+ return {};
+ },
},
methods: {
+ hasSlotContents(slotName) {
+ return Boolean(this.$slots[slotName]);
+ },
scopedLabel(label) {
return isScopedLabel(label);
},
+ labelTitle(label) {
+ return label.title || label.name;
+ },
+ labelTarget(label) {
+ if (this.enableLabelPermalinks) {
+ const key = encodeURIComponent('label_name[]');
+ const value = encodeURIComponent(this.labelTitle(label));
+ return `?${key}=${value}`;
+ }
+ return '#';
+ },
/**
* This is needed as an independent method since
* when user changes current page, `$refs.authorLink`
@@ -74,17 +109,20 @@ export default {
</script>
<template>
- <li class="issue">
+ <li class="issue px-3">
<div class="issue-box">
<div class="issuable-info-container">
<div class="issuable-main-info">
<div data-testid="issuable-title" class="issue-title title">
<span class="issue-title-text" dir="auto">
- <gl-link :href="issuable.webUrl">{{ issuable.title }}</gl-link>
+ <gl-link :href="issuable.webUrl" v-bind="issuableTitleProps"
+ >{{ issuable.title }}<gl-icon v-if="isIssuableUrlExternal" name="external-link"
+ /></gl-link>
</span>
</div>
<div class="issuable-info">
- <span data-testid="issuable-reference" class="issuable-reference"
+ <slot v-if="hasSlotContents('reference')" name="reference"></slot>
+ <span v-else data-testid="issuable-reference" class="issuable-reference"
>{{ issuableSymbol }}{{ issuable.iid }}</span
>
<span class="issuable-authored d-none d-sm-inline-block">
@@ -113,15 +151,30 @@ export default {
v-for="(label, index) in labels"
:key="index"
:background-color="label.color"
- :title="label.title"
+ :title="labelTitle(label)"
:description="label.description"
:scoped="scopedLabel(label)"
+ :target="labelTarget(label)"
:class="{ 'gl-ml-2': index }"
size="sm"
/>
</div>
</div>
<div class="issuable-meta">
+ <ul v-if="hasSlotContents('status') || issuable.assignees" class="controls">
+ <li v-if="hasSlotContents('status')" class="issuable-status">
+ <slot name="status"></slot>
+ </li>
+ <li v-if="assignees.length" class="gl-display-flex">
+ <issuable-assignees
+ :assignees="issuable.assignees"
+ :icon-size="16"
+ :max-visible="4"
+ img-css-classes="gl-mr-2!"
+ class="gl-align-items-center gl-display-flex gl-ml-3"
+ />
+ </li>
+ </ul>
<div
data-testid="issuable-updated-at"
class="float-right issuable-updated-at d-none d-sm-inline-block"
diff --git a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
index 7535203dea1..52ebb9acdc3 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue
@@ -1,6 +1,7 @@
<script>
import { GlLoadingIcon, GlPagination } from '@gitlab/ui';
+import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import IssuableTabs from './issuable_tabs.vue';
@@ -35,6 +36,11 @@ export default {
type: Array,
required: true,
},
+ urlParams: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
initialFilterValue: {
type: Array,
required: false,
@@ -55,7 +61,8 @@ export default {
},
tabCounts: {
type: Object,
- required: true,
+ required: false,
+ default: null,
},
currentTab: {
type: String,
@@ -81,6 +88,11 @@ export default {
required: false,
default: 20,
},
+ totalPages: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
currentPage: {
type: Number,
required: false,
@@ -96,6 +108,26 @@ export default {
required: false,
default: 2,
},
+ enableLabelPermalinks: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ watch: {
+ urlParams: {
+ deep: true,
+ immediate: true,
+ handler(params) {
+ if (Object.keys(params).length) {
+ updateHistory({
+ url: setUrlParams(params, window.location.href, true),
+ title: document.title,
+ replace: true,
+ });
+ }
+ },
+ },
},
};
</script>
@@ -135,12 +167,21 @@ export default {
:key="issuable.id"
:issuable-symbol="issuableSymbol"
:issuable="issuable"
- />
+ :enable-label-permalinks="enableLabelPermalinks"
+ >
+ <template #reference>
+ <slot name="reference" :issuable="issuable"></slot>
+ </template>
+ <template #status>
+ <slot name="status" :issuable="issuable"></slot>
+ </template>
+ </issuable-item>
</ul>
<slot v-if="!issuablesLoading && !issuables.length" name="empty-state"></slot>
<gl-pagination
v-if="showPaginationControls"
:per-page="defaultPageSize"
+ :total-items="totalPages"
:value="currentPage"
:prev-page="previousPage"
:next-page="nextPage"
diff --git a/app/assets/javascripts/issuable_list/components/issuable_tabs.vue b/app/assets/javascripts/issuable_list/components/issuable_tabs.vue
index df544ce69e7..d9aab004077 100644
--- a/app/assets/javascripts/issuable_list/components/issuable_tabs.vue
+++ b/app/assets/javascripts/issuable_list/components/issuable_tabs.vue
@@ -14,7 +14,8 @@ export default {
},
tabCounts: {
type: Object,
- required: true,
+ required: false,
+ default: null,
},
currentTab: {
type: String,
@@ -40,7 +41,7 @@ export default {
>
<template #title>
<span :title="tab.titleTooltip">{{ tab.title }}</span>
- <gl-badge variant="neutral" size="sm" class="gl-px-2 gl-py-1!">{{
+ <gl-badge v-if="tabCounts" variant="neutral" size="sm" class="gl-px-2 gl-py-1!">{{
tabCounts[tab.name]
}}</gl-badge>
</template>
diff --git a/app/assets/javascripts/issuable_list/constants.js b/app/assets/javascripts/issuable_list/constants.js
new file mode 100644
index 00000000000..4d3ce61366a
--- /dev/null
+++ b/app/assets/javascripts/issuable_list/constants.js
@@ -0,0 +1,49 @@
+import { __ } from '~/locale';
+
+export const IssuableStates = {
+ Opened: 'opened',
+ Closed: 'closed',
+ All: 'all',
+};
+
+export const IssuableListTabs = [
+ {
+ id: 'state-opened',
+ name: IssuableStates.Opened,
+ title: __('Open'),
+ titleTooltip: __('Filter by issues that are currently opened.'),
+ },
+ {
+ id: 'state-closed',
+ name: IssuableStates.Closed,
+ title: __('Closed'),
+ titleTooltip: __('Filter by issues that are currently closed.'),
+ },
+ {
+ id: 'state-all',
+ name: IssuableStates.All,
+ title: __('All'),
+ titleTooltip: __('Show all issues.'),
+ },
+];
+
+export const AvailableSortOptions = [
+ {
+ id: 1,
+ title: __('Created date'),
+ sortDirection: {
+ descending: 'created_desc',
+ ascending: 'created_asc',
+ },
+ },
+ {
+ id: 2,
+ title: __('Last updated'),
+ sortDirection: {
+ descending: 'updated_desc',
+ ascending: 'updated_asc',
+ },
+ },
+];
+
+export const DEFAULT_PAGE_SIZE = 20;
diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue
index 72f9a4c7e74..b20facc4032 100644
--- a/app/assets/javascripts/notes/components/note_attachment.vue
+++ b/app/assets/javascripts/notes/components/note_attachment.vue
@@ -1,6 +1,11 @@
<script>
+import { GlIcon } from '@gitlab/ui';
+
export default {
name: 'NoteAttachment',
+ components: {
+ GlIcon,
+ },
props: {
attachment: {
type: Object,
@@ -29,7 +34,7 @@ export default {
target="_blank"
rel="noopener noreferrer"
>
- <i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }}
+ <gl-icon name="paperclip" /> {{ attachment.filename }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
index 97595e5d2ce..e52afe08336 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
@@ -87,7 +87,7 @@ export default {
:aria-label="__('Run manual or delayed jobs')"
>
<gl-icon name="play" class="icon-play" />
- <i class="fa fa-caret-down" aria-hidden="true"></i>
+ <gl-icon name="chevron-down" />
<gl-loading-icon v-if="isLoading" />
</button>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
index 4a3d134685e..55c71e299be 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
@@ -29,7 +29,7 @@ export default {
:aria-label="__('Artifacts')"
>
<gl-icon name="download" />
- <i class="fa fa-caret-down" aria-hidden="true"></i>
+ <gl-icon name="chevron-down" />
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li v-for="(artifact, i) in artifacts" :key="i">
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 9d72bf4394e..7b67c34ded6 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
@@ -96,7 +96,12 @@ export default {
</script>
<template>
- <div v-gl-tooltip:body.viewport.left :title="tooltipText" class="sidebar-collapsed-icon">
+ <div
+ v-gl-tooltip:body.viewport.left
+ :title="tooltipText"
+ data-testid="collapsedState"
+ class="sidebar-collapsed-icon"
+ >
<gl-icon name="timer" />
<div class="time-tracking-collapsed-summary">
<div :class="divClass">
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
index d4cc98e3743..99302993b9a 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -70,14 +70,19 @@ export default {
</script>
<template>
- <div class="time-tracking-comparison-pane">
+ <div data-testid="timeTrackingComparisonPane">
<div
v-gl-tooltip
+ data-testid="compareMeter"
:title="timeRemainingTooltip"
:class="timeRemainingStatusClass"
class="compare-meter"
>
- <gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" />
+ <gl-progress-bar
+ data-testid="timeRemainingProgress"
+ :value="timeRemainingPercent"
+ :variant="progressBarVariant"
+ />
<div class="compare-display-container">
<div class="compare-display float-left">
<span class="compare-label">{{ s__('TimeTracking|Spent') }}</span>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
index 305726d9725..8a80b1bf13f 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
@@ -11,7 +11,8 @@ export default {
</script>
<template>
- <div class="time-tracking-estimate-only-pane">
- <span class="bold"> {{ s__('TimeTracking|Estimated:') }} </span> {{ timeEstimateHumanReadable }}
+ <div data-testid="estimateOnlyPane">
+ <span class="gl-font-weight-bold">{{ s__('TimeTracking|Estimated:') }} </span
+ >{{ timeEstimateHumanReadable }}
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
index b45746e789d..8bc828091c0 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
@@ -34,7 +34,7 @@ export default {
</script>
<template>
- <div class="time-tracking-help-state">
+ <div data-testid="helpPane" class="time-tracking-help-state">
<div class="time-tracking-info">
<h4>{{ __('Track time with quick actions') }}</h4>
<p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue
index 45552589e50..2d3d0ce8dc5 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue
@@ -5,7 +5,7 @@ export default {
</script>
<template>
- <div class="time-tracking-no-tracking-pane">
- <span class="no-value"> {{ __('No estimate or time spent') }} </span>
+ <div data-testid="noTrackingPane">
+ <span class="no-value">{{ __('No estimate or time spent') }}</span>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
index b2b3b289c5c..33c6ac6e2ba 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue
@@ -15,7 +15,7 @@ export default {
return sprintf(
s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'),
{
- startTag: '<span class="bold">',
+ startTag: '<span class="gl-font-weight-bold">',
endTag: '</span>',
timeSpentHumanReadable: this.timeSpentHumanReadable,
},
@@ -27,5 +27,5 @@ export default {
</script>
<template>
- <div class="time-tracking-spend-only-pane" v-html="timeSpent"></div>
+ <div data-testid="spentOnlyPane" v-html="timeSpent"></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 a2fb0ebcbc6..95864462a68 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -105,11 +105,17 @@ export default {
/>
<div class="title hide-collapsed">
{{ __('Time tracking') }}
- <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)">
+ <div
+ v-if="!showHelpState"
+ data-testid="helpButton"
+ class="help-button float-right"
+ @click="toggleHelpState(true)"
+ >
<gl-icon name="question-o" />
</div>
<div
- v-if="showHelpState"
+ v-else
+ data-testid="closeHelpButton"
class="close-help-button float-right"
@click="toggleHelpState(false)"
>
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
index c1521882682..0a1211d0a76 100644
--- a/app/assets/javascripts/tracking.js
+++ b/app/assets/javascripts/tracking.js
@@ -12,6 +12,7 @@ const DEFAULT_SNOWPLOW_OPTIONS = {
contexts: { webPage: true, performanceTiming: true },
formTracking: false,
linkClickTracking: false,
+ pageUnloadTimer: 10,
};
const createEventPayload = (el, { suffix = '' } = {}) => {
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index a31cb0b0485..ad4d42abae6 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -39,7 +39,6 @@
@import './pages/settings';
@import './pages/settings_ci_cd';
@import './pages/sherlock';
-@import './pages/status';
@import './pages/storage_quota';
@import './pages/tree';
@import './pages/trials';
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index 7b1953be69d..937ed5119cb 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -117,10 +117,6 @@
content: '\f077';
}
-.fa-paperclip::before {
- content: '\f0c6';
-}
-
.fa-bug::before {
content: '\f188';
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/page_bundles/ci_status.scss
index b37c5172ad2..8522a0a8fe4 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/page_bundles/ci_status.scss
@@ -1,3 +1,5 @@
+@import 'mixins_and_variables_and_functions';
+
.ci-status {
padding: 2px 7px 4px;
border: 1px solid $gray-darker;
diff --git a/app/assets/stylesheets/page_bundles/todos.scss b/app/assets/stylesheets/page_bundles/todos.scss
index 3eec5b53a30..3e20ca9c62f 100644
--- a/app/assets/stylesheets/page_bundles/todos.scss
+++ b/app/assets/stylesheets/page_bundles/todos.scss
@@ -219,7 +219,6 @@
.todos-empty-content {
align-self: center;
max-width: 480px;
- margin-right: 20px;
}
.todos-empty-hero {
diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb
index 85892c2abeb..8e8127cf279 100644
--- a/app/graphql/resolvers/releases_resolver.rb
+++ b/app/graphql/resolvers/releases_resolver.rb
@@ -4,6 +4,10 @@ module Resolvers
class ReleasesResolver < BaseResolver
type Types::ReleaseType.connection_type, null: true
+ argument :sort, Types::ReleaseSortEnum,
+ required: false, default_value: :released_at_desc,
+ description: 'Sort releases by this criteria'
+
alias_method :project, :object
# This resolver has a custom singular resolver
@@ -11,12 +15,20 @@ module Resolvers
Resolvers::ReleaseResolver
end
- def resolve(**args)
+ SORT_TO_PARAMS_MAP = {
+ released_at_desc: { order_by: 'released_at', sort: 'desc' },
+ released_at_asc: { order_by: 'released_at', sort: 'asc' },
+ created_desc: { order_by: 'created_at', sort: 'desc' },
+ created_asc: { order_by: 'created_at', sort: 'asc' }
+ }.freeze
+
+ def resolve(sort:)
return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true)
ReleasesFinder.new(
project,
- current_user
+ current_user,
+ SORT_TO_PARAMS_MAP[sort]
).execute
end
end
diff --git a/app/graphql/types/release_sort_enum.rb b/app/graphql/types/release_sort_enum.rb
new file mode 100644
index 00000000000..2f9af1bced9
--- /dev/null
+++ b/app/graphql/types/release_sort_enum.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ # Not inheriting from Types::SortEnum since we only want
+ # to implement a subset of the sort values it defines.
+ class ReleaseSortEnum < BaseEnum
+ graphql_name 'ReleaseSort'
+ description 'Values for sorting releases'
+
+ # Borrowed from Types::SortEnum
+ # These values/descriptions should stay in-sync as much as possible.
+ value 'CREATED_DESC', 'Created at descending order', value: :created_desc
+ value 'CREATED_ASC', 'Created at ascending order', value: :created_asc
+
+ value 'RELEASED_AT_DESC', 'Released at by descending order', value: :released_at_desc
+ value 'RELEASED_AT_ASC', 'Released at by ascending order', value: :released_at_asc
+ end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index 764f417362f..0998a9a102a 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -8,6 +8,7 @@ class Service < ApplicationRecord
include ProjectServicesLoggable
include DataFields
include FromUnion
+ include EachBatch
SERVICE_NAMES = %w[
alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker discord
@@ -294,7 +295,7 @@ class Service < ApplicationRecord
end
def initialize_properties
- self.properties = {} if properties.nil?
+ self.properties = {} if has_attribute?(:properties) && properties.nil?
end
def title
diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb
index 96a6d861e47..b5f034186df 100644
--- a/app/services/admin/propagate_integration_service.rb
+++ b/app/services/admin/propagate_integration_service.rb
@@ -20,7 +20,7 @@ module Admin
# rubocop: disable Cop/InBatches
def update_inherited_integrations
- Service.by_type(integration.type).inherit_from_id(integration.id).in_batches(of: BATCH_SIZE) do |services|
+ Service.by_type(integration.type).inherit_from_id(integration.id).each_batch(of: BATCH_SIZE) do |services|
min_id, max_id = services.pick("MIN(services.id), MAX(services.id)")
PropagateIntegrationInheritWorker.perform_async(integration.id, min_id, max_id)
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 48f44affb23..b2826b5c905 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -73,6 +73,8 @@ module Notes
if note.for_merge_request? && note.diff_note? && note.start_of_discussion?
Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion)
end
+
+ track_note_creation_usage_for_issues(note) if note.for_issue?
end
def do_commands(note, update_params, message, only_commands)
@@ -113,5 +115,9 @@ module Notes
track_usage_event(:incident_management_incident_comment, user.id)
end
+
+ def track_note_creation_usage_for_issues(note)
+ Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_added_action(author: note.author)
+ end
end
end
diff --git a/app/services/notes/destroy_service.rb b/app/services/notes/destroy_service.rb
index ee8a680fcb4..2b6ec47eaef 100644
--- a/app/services/notes/destroy_service.rb
+++ b/app/services/notes/destroy_service.rb
@@ -8,6 +8,13 @@ module Notes
end
clear_noteable_diffs_cache(note)
+ track_note_removal_usage_for_issues(note) if note.for_issue?
+ end
+
+ private
+
+ def track_note_removal_usage_for_issues(note)
+ Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_removed_action(author: note.author)
end
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 193d3080078..37872f7fbdb 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -14,6 +14,8 @@ module Notes
note.save
end
+ track_note_edit_usage_for_issues(note) if note.for_issue?
+
only_commands = false
quick_actions_service = QuickActionsService.new(project, current_user)
@@ -89,6 +91,10 @@ module Notes
Note.id_in(note.discussion.notes.map(&:id)).update_all(confidential: params[:confidential])
end
+
+ def track_note_edit_usage_for_issues(note)
+ Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_edited_action(author: note.author)
+ end
end
end
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index ebff5a182c4..9a9fbfc1ee8 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -114,7 +114,7 @@
.todos-empty
.todos-empty-hero.svg-content
= image_tag 'illustrations/todos_empty.svg'
- .todos-empty-content
+ .todos-empty-content.gl-mx-5
%h4
Your To-Do List shows what to work on next
%p
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 4e936025c74..2fa595503e5 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -25,5 +25,5 @@
= image_tag note.attachment.url, class: 'note-image-attach'
- else
= link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do
- %i.fa.fa-paperclip
+ = sprite_icon("paperclip")
= note.attachment_identifier
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index 0b4b4aafeee..a1960fc99cf 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -1,4 +1,5 @@
- page_title _("Jobs")
+- add_page_specific_style 'page_bundles/ci_status'
.top-area
- build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index d7a778088ee..3ac81d4fad6 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -2,6 +2,7 @@
- breadcrumb_title "##{@build.id}"
- page_title "#{@build.name} (##{@build.id})", _("Jobs")
- add_page_specific_style 'page_bundles/xterm'
+- add_page_specific_style 'page_bundles/ci_status'
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml
index 4c968c8e8eb..0741b24a5a1 100644
--- a/app/views/projects/merge_requests/creations/new.html.haml
+++ b/app/views/projects/merge_requests/creations/new.html.haml
@@ -2,6 +2,7 @@
- breadcrumb_title _("New")
- page_title _("New Merge Request")
- add_page_specific_style 'page_bundles/pipelines'
+- add_page_specific_style 'page_bundles/ci_status'
- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 1dbcd613ceb..6b506c38795 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -11,6 +11,7 @@
- add_page_specific_style 'page_bundles/merge_requests'
- add_page_specific_style 'page_bundles/pipelines'
- add_page_specific_style 'page_bundles/reports'
+- add_page_specific_style 'page_bundles/ci_status'
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title"
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index ca07f33136b..6aa1a564499 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,5 +1,6 @@
- page_title _('Pipelines')
- add_page_specific_style 'page_bundles/pipelines'
+- add_page_specific_style 'page_bundles/ci_status'
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 34f7744f825..a25c30d01d9 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -4,6 +4,7 @@
- pipeline_has_errors = @pipeline.builds.empty? && @pipeline.yaml_errors.present?
- add_page_specific_style 'page_bundles/pipeline'
- add_page_specific_style 'page_bundles/reports'
+- add_page_specific_style 'page_bundles/ci_status'
.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container{ data: {full_path: @project.full_path, retry_path: retry_project_pipeline_path(@pipeline.project, @pipeline), cancel_path: cancel_project_pipeline_path(@pipeline.project, @pipeline), delete_path: project_pipeline_path(@pipeline.project, @pipeline), pipeline_iid: @pipeline.iid, pipeline_id: @pipeline.id} }
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 97ed2852871..f1352be28e3 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -72,7 +72,7 @@
= image_tag note.attachment.url, class: 'note-image-attach'
.attachment
= link_to note.attachment.url, target: '_blank' do
- = icon('paperclip')
+ = sprite_icon('paperclip')
= note.attachment_identifier
= link_to delete_attachment_project_note_path(note.project, note),
title: _('Delete this attachment'), method: :delete, remote: true, data: { confirm: _('Are you sure you want to remove the attachment?') }, class: 'danger js-note-attachment-delete' do