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>2023-06-13 12:10:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-13 12:10:22 +0300
commit10e15ac3c2798956ff6a43d7b36bdf86c68aa817 (patch)
tree491a448439d1c2f43258d0418c53e8cc00b51039 /app
parent1cd5d53f92b07b0be71b6c2d9fdfa7cf07221890 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue90
-rw-r--r--app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_approved.vue65
-rw-r--r--app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_base.vue54
-rw-r--r--app/assets/javascripts/contribution_events/components/contribution_events.vue119
-rw-r--r--app/assets/javascripts/contribution_events/components/resource_parent_link.vue22
-rw-r--r--app/assets/javascripts/contribution_events/components/target_link.vue31
-rw-r--r--app/assets/javascripts/contribution_events/constants.js14
-rw-r--r--app/assets/javascripts/issuable/components/csv_import_export_buttons.vue51
-rw-r--r--app/assets/javascripts/issues/list/components/empty_state_without_any_issues.vue10
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue46
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue2
-rw-r--r--app/assets/javascripts/profile/components/overview_tab.vue45
-rw-r--r--app/assets/javascripts/profile/index.js2
-rw-r--r--app/assets/javascripts/super_sidebar/components/frequent_items_list.vue2
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown.vue96
-rw-r--r--app/controllers/projects/environments_controller.rb2
-rw-r--r--app/helpers/users_helper.rb1
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service.rb46
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb5
21 files changed, 545 insertions, 169 deletions
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 247910301e7..960c8e472b8 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,15 +1,10 @@
<script>
-import {
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlSearchBoxByType,
- GlIntersectionObserver,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import { mapActions, mapState, mapGetters } from 'vuex';
+import { GlCollapsibleListbox } from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { debounce } from 'lodash';
import { s__ } from '~/locale';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { ListType } from '../constants';
export default {
@@ -27,12 +22,7 @@ export default {
order_by: 'similarity',
},
components: {
- GlIntersectionObserver,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlSearchBoxByType,
+ GlCollapsibleListbox,
},
inject: ['groupId'],
props: {
@@ -44,6 +34,7 @@ export default {
data() {
return {
initialLoading: true,
+ selectedProjectId: '',
selectedProject: {},
searchTerm: '',
};
@@ -51,6 +42,12 @@ export default {
computed: {
...mapState(['groupProjectsFlags']),
...mapGetters(['activeGroupProjects']),
+ projects() {
+ return this.activeGroupProjects.map((project) => ({
+ value: project.id,
+ text: project.nameWithNamespace,
+ }));
+ },
selectedProjectName() {
return this.selectedProject.name || this.$options.i18n.dropdownText;
},
@@ -73,26 +70,27 @@ export default {
},
},
watch: {
- searchTerm() {
+ searchTerm: debounce(function debouncedSearch() {
this.fetchGroupProjects({ search: this.searchTerm });
- },
+ }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
},
mounted() {
this.fetchGroupProjects({});
-
this.initialLoading = false;
},
methods: {
...mapActions(['fetchGroupProjects', 'setSelectedProject']),
selectProject(projectId) {
+ this.selectedProjectId = projectId;
this.selectedProject = this.activeGroupProjects.find((project) => project.id === projectId);
this.setSelectedProject(this.selectedProject);
},
loadMoreProjects() {
+ if (!this.hasNextPage) return;
this.fetchGroupProjects({ search: this.searchTerm, fetchNext: true });
},
- setFocus() {
- this.$refs.search.focusInput();
+ onSearch(query) {
+ this.searchTerm = query;
},
},
};
@@ -103,45 +101,23 @@ export default {
<label class="gl-font-weight-bold gl-mt-3" data-testid="header-label">{{
$options.i18n.headerTitle
}}</label>
- <gl-dropdown
+ <gl-collapsible-listbox
+ v-model="selectedProjectId"
+ block
+ searchable
+ infinite-scroll
data-testid="project-select-dropdown"
- :text="selectedProjectName"
+ :items="projects"
+ :toggle-text="selectedProjectName"
:header-text="$options.i18n.headerTitle"
- block
- menu-class="gl-w-full!"
:loading="initialLoading"
- @shown="setFocus"
- >
- <gl-search-box-by-type
- ref="search"
- v-model.trim="searchTerm"
- debounce="250"
- :placeholder="$options.i18n.searchPlaceholder"
- />
- <gl-dropdown-item
- v-for="project in activeGroupProjects"
- v-show="!groupProjectsFlags.isLoading"
- :key="project.id"
- :name="project.name"
- @click="selectProject(project.id)"
- >
- {{ project.nameWithNamespace }}
- </gl-dropdown-item>
- <gl-dropdown-text
- v-show="groupProjectsFlags.isLoading"
- data-testid="dropdown-text-loading-icon"
- >
- <gl-loading-icon class="gl-mx-auto" size="sm" />
- </gl-dropdown-text>
- <gl-dropdown-text
- v-if="isFetchResultEmpty && !groupProjectsFlags.isLoading"
- data-testid="empty-result-message"
- >
- <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span>
- </gl-dropdown-text>
- <gl-intersection-observer v-if="hasNextPage" @appear="loadMoreProjects">
- <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="lg" />
- </gl-intersection-observer>
- </gl-dropdown>
+ :searching="groupProjectsFlags.isLoading"
+ :search-placeholder="$options.i18n.searchPlaceholder"
+ :no-results-text="$options.i18n.emptySearchResult"
+ :infinite-scroll-loading="groupProjectsFlags.isLoadingMore"
+ @select="selectProject"
+ @search="onSearch"
+ @bottom-reached="loadMoreProjects"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_approved.vue b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_approved.vue
new file mode 100644
index 00000000000..a7787ae84bc
--- /dev/null
+++ b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_approved.vue
@@ -0,0 +1,65 @@
+<script>
+import { GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import TargetLink from '../target_link.vue';
+import ResourceParentLink from '../resource_parent_link.vue';
+import ContributionEventBase from './contribution_event_base.vue';
+
+export default {
+ name: 'ContributionEventApproved',
+ i18n: {
+ message: s__(
+ 'ContributionEvent|Approved merge request %{targetLink} in %{resourceParentLink}.',
+ ),
+ },
+ components: { ContributionEventBase, GlSprintf, TargetLink, ResourceParentLink },
+ props: {
+ /**
+ * Expected format
+ * {
+ * created_at: string;
+ * action: "approved"
+ * author: {
+ * id: number;
+ * username: string;
+ * name: string;
+ * state: string;
+ * avatar_url: string;
+ * web_url: string;
+ * };
+ * target: {
+ * id: number;
+ * type: "MergeRequest"
+ * title: string;
+ * reference_link_text: string;
+ * web_url: string;
+ * };
+ * resource_parent: {
+ * type: "project";
+ * full_name: string;
+ * full_path: string;
+ * web_url: string;
+ * avatar_url: string;
+ * };
+ * };
+ */
+ event: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <contribution-event-base :event="event" icon-name="approval-solid" icon-class="gl-text-green-500">
+ <gl-sprintf :message="$options.i18n.message">
+ <template #targetLink>
+ <target-link :event="event" />
+ </template>
+ <template #resourceParentLink>
+ <resource-parent-link :event="event" />
+ </template>
+ </gl-sprintf>
+ </contribution-event-base>
+</template>
diff --git a/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_base.vue b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_base.vue
new file mode 100644
index 00000000000..93ac94a6f4f
--- /dev/null
+++ b/app/assets/javascripts/contribution_events/components/contribution_event/contribution_event_base.vue
@@ -0,0 +1,54 @@
+<script>
+import { GlAvatarLabeled, GlAvatarLink, GlIcon } from '@gitlab/ui';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: { GlAvatarLabeled, GlAvatarLink, GlIcon, TimeAgoTooltip },
+ props: {
+ event: {
+ type: Object,
+ required: true,
+ },
+ iconName: {
+ type: String,
+ required: true,
+ },
+ iconClass: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ author() {
+ return this.event.author;
+ },
+ authorUsername() {
+ return `@${this.author.username}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="gl-mt-5 gl-pb-5 gl-border-b gl-relative">
+ <time-ago-tooltip :time="event.created_at" class="gl-float-right gl-text-secondary" />
+ <gl-avatar-link :href="author.web_url">
+ <gl-avatar-labeled
+ :label="author.name"
+ :sub-label="authorUsername"
+ :src="author.avatar_url"
+ :size="32"
+ />
+ </gl-avatar-link>
+ <div class="gl-pl-8 gl-mt-2" data-testid="event-body">
+ <div class="gl-text-secondary">
+ <gl-icon :class="iconClass" :name="iconName" />
+ <slot></slot>
+ </div>
+ <div v-if="$scopedSlots['additional-info']" class="gl-mt-2">
+ <slot name="additional-info"></slot>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/contribution_events/components/contribution_events.vue b/app/assets/javascripts/contribution_events/components/contribution_events.vue
new file mode 100644
index 00000000000..41ec4f5692e
--- /dev/null
+++ b/app/assets/javascripts/contribution_events/components/contribution_events.vue
@@ -0,0 +1,119 @@
+<script>
+import EmptyComponent from '~/vue_shared/components/empty_component';
+import { EVENT_TYPE_APPROVED } from '../constants';
+import ContributionEventApproved from './contribution_event/contribution_event_approved.vue';
+
+export default {
+ props: {
+ /**
+ * Expected format
+ * {
+ * created_at: string;
+ * action:
+ * | "created"
+ * | "updated"
+ * | "closed"
+ * | "reopened"
+ * | "pushed"
+ * | "commented"
+ * | "merged"
+ * | "joined"
+ * | "left"
+ * | "destroyed"
+ * | "expired"
+ * | "approved"
+ * | "private";
+ * ref?: {
+ * type: "branch" | "tag";
+ * count: number;
+ * name: string;
+ * path: string;
+ * is_new: boolean;
+ * is_removed: boolean;
+ * };
+ * commit?: {
+ * truncated_sha: string;
+ * path: string;
+ * title: string;
+ * count: number;
+ * create_mr_path: string;
+ * from_truncated_sha?: string;
+ * to_truncated_sha?: string;
+ * compare_path?: string;
+ * };
+ * author: {
+ * id: number;
+ * username: string;
+ * name: string;
+ * state: string;
+ * avatar_url: string;
+ * web_url: string;
+ * };
+ * noteable?: {
+ * type: string;
+ * reference_link_text: string;
+ * web_url: string;
+ * first_line_in_markdown: string;
+ * };
+ * target?: {
+ * id: number;
+ * type:
+ * | "Issue"
+ * | "Milestone"
+ * | "MergeRequest"
+ * | "Note"
+ * | "Project"
+ * | "Snippet"
+ * | "User"
+ * | "WikiPage::Meta"
+ * | "DesignManagement::Design";
+ * title: string;
+ * issue_type?:
+ * | "issue"
+ * | "incident"
+ * | "test_case"
+ * | "requirement"
+ * | "task"
+ * | "objective"
+ * | "key_result";
+ * reference_link_text?: string;
+ * web_url: string;
+ * };
+ * resource_parent?: {
+ * type: "project" | "group";
+ * full_name: string;
+ * full_path: string;
+ * web_url: string;
+ * avatar_url: string;
+ * };
+ * }[];
+ */
+ events: {
+ type: Array,
+ required: true,
+ },
+ },
+ methods: {
+ eventComponent(action) {
+ switch (action) {
+ case EVENT_TYPE_APPROVED:
+ return ContributionEventApproved;
+
+ default:
+ return EmptyComponent;
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <ul class="gl-list-style-none gl-p-0">
+ <component
+ :is="eventComponent(event.action)"
+ v-for="(event, index) in events"
+ :key="index"
+ :event="event"
+ />
+ </ul>
+</template>
diff --git a/app/assets/javascripts/contribution_events/components/resource_parent_link.vue b/app/assets/javascripts/contribution_events/components/resource_parent_link.vue
new file mode 100644
index 00000000000..5add9d788bb
--- /dev/null
+++ b/app/assets/javascripts/contribution_events/components/resource_parent_link.vue
@@ -0,0 +1,22 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+
+export default {
+ components: { GlLink },
+ props: {
+ event: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ resourceParent() {
+ return this.event.resource_parent;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-link :href="resourceParent.web_url">{{ resourceParent.full_name }}</gl-link>
+</template>
diff --git a/app/assets/javascripts/contribution_events/components/target_link.vue b/app/assets/javascripts/contribution_events/components/target_link.vue
new file mode 100644
index 00000000000..a661121b2fb
--- /dev/null
+++ b/app/assets/javascripts/contribution_events/components/target_link.vue
@@ -0,0 +1,31 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+
+export default {
+ components: { GlLink },
+ props: {
+ event: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ target() {
+ return this.event.target;
+ },
+ targetLinkText() {
+ return this.target.reference_link_text;
+ },
+ targetLinkAttributes() {
+ return {
+ href: this.target.web_url,
+ title: this.target.title,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-link v-bind="targetLinkAttributes">{{ targetLinkText }}</gl-link>
+</template>
diff --git a/app/assets/javascripts/contribution_events/constants.js b/app/assets/javascripts/contribution_events/constants.js
new file mode 100644
index 00000000000..05f968e7bc4
--- /dev/null
+++ b/app/assets/javascripts/contribution_events/constants.js
@@ -0,0 +1,14 @@
+// From app/models/event.rb#L16
+export const EVENT_TYPE_CREATED = 'created';
+export const EVENT_TYPE_UPDATED = 'updated';
+export const EVENT_TYPE_CLOSED = 'closed';
+export const EVENT_TYPE_REOPENED = 'reopened';
+export const EVENT_TYPE_PUSHED = 'pushed';
+export const EVENT_TYPE_COMMENTED = 'commented';
+export const EVENT_TYPE_MERGED = 'merged';
+export const EVENT_TYPE_JOINED = 'joined';
+export const EVENT_TYPE_LEFT = 'left';
+export const EVENT_TYPE_DESTROYED = 'destroyed';
+export const EVENT_TYPE_EXPIRED = 'expired';
+export const EVENT_TYPE_APPROVED = 'approved';
+export const EVENT_TYPE_PRIVATE = 'private';
diff --git a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
index b492194d1cf..872e1d4269d 100644
--- a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
+++ b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
@@ -1,18 +1,13 @@
<script>
-import { GlDropdownItem, GlModalDirective } from '@gitlab/ui';
+import { GlDisclosureDropdownItem, GlModalDirective } from '@gitlab/ui';
import { TYPE_ISSUE } from '~/issues/constants';
import { __ } from '~/locale';
import CsvExportModal from './csv_export_modal.vue';
import CsvImportModal from './csv_import_modal.vue';
export default {
- i18n: {
- exportAsCsvButtonText: __('Export as CSV'),
- importCsvText: __('Import CSV'),
- importFromJiraText: __('Import from Jira'),
- },
components: {
- GlDropdownItem,
+ GlDisclosureDropdownItem,
CsvExportModal,
CsvImportModal,
},
@@ -48,6 +43,22 @@ export default {
default: undefined,
},
},
+ data() {
+ return {
+ dropdownItems: {
+ exportAsCSV: {
+ text: __('Export as CSV'),
+ },
+ importCSV: {
+ text: __('Import CSV'),
+ },
+ importFromJIRA: {
+ text: __('Import from Jira'),
+ href: this.projectImportJiraPath,
+ },
+ },
+ };
+ },
computed: {
exportModalId() {
return `${this.issuableType}-export-modal`;
@@ -61,23 +72,25 @@ export default {
<template>
<ul class="gl-display-contents">
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-if="showExportButton"
v-gl-modal="exportModalId"
+ data-testid="export-as-csv-button"
data-qa-selector="export_as_csv_button"
- >
- {{ $options.i18n.exportAsCsvButtonText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="showImportButton" v-gl-modal="importModalId">
- {{ $options.i18n.importCsvText }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ :item="dropdownItems.exportAsCSV"
+ />
+ <gl-disclosure-dropdown-item
+ v-if="showImportButton"
+ v-gl-modal="importModalId"
+ data-testid="import-from-csv-button"
+ :item="dropdownItems.importCSV"
+ />
+ <gl-disclosure-dropdown-item
v-if="showImportButton && canEdit"
- :href="projectImportJiraPath"
+ data-testid="import-from-jira-link"
data-qa-selector="import_from_jira_link"
- >
- {{ $options.i18n.importFromJiraText }}
- </gl-dropdown-item>
+ :item="dropdownItems.importFromJIRA"
+ />
<csv-export-modal
v-if="showExportButton"
diff --git a/app/assets/javascripts/issues/list/components/empty_state_without_any_issues.vue b/app/assets/javascripts/issues/list/components/empty_state_without_any_issues.vue
index c8b52609569..3f29fc66abb 100644
--- a/app/assets/javascripts/issues/list/components/empty_state_without_any_issues.vue
+++ b/app/assets/javascripts/issues/list/components/empty_state_without_any_issues.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlDropdown, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlButton, GlDisclosureDropdown, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue';
@@ -12,7 +12,7 @@ export default {
components: {
CsvImportExportButtons,
GlButton,
- GlDropdown,
+ GlDisclosureDropdown,
GlEmptyState,
GlLink,
GlSprintf,
@@ -77,17 +77,17 @@ export default {
{{ $options.i18n.newIssueLabel }}
</gl-button>
- <gl-dropdown
+ <gl-disclosure-dropdown
v-if="showCsvButtons"
class="gl-w-full gl-sm-w-auto gl-sm-mr-3"
- :text="$options.i18n.importIssues"
+ :toggle-text="$options.i18n.importIssues"
data-qa-selector="import_issues_dropdown"
>
<csv-import-export-buttons
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
- </gl-dropdown>
+ </gl-disclosure-dropdown>
<new-resource-dropdown
v-if="showNewIssueDropdown"
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 2cb966cf1fd..83b0bcebe67 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -2,11 +2,10 @@
import {
GlButton,
GlButtonGroup,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownGroup,
GlFilteredSearchToken,
GlTooltipDirective,
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
@@ -70,6 +69,9 @@ import {
defaultWorkItemTypes,
i18n,
ISSUE_REFERENCE,
+ ISSUES_GRID_VIEW_KEY,
+ ISSUES_LIST_VIEW_KEY,
+ ISSUES_VIEW_TYPE_KEY,
MAX_LIST_SIZE,
PARAM_FIRST_PAGE_SIZE,
PARAM_LAST_PAGE_SIZE,
@@ -80,9 +82,6 @@ import {
RELATIVE_POSITION_ASC,
UPDATED_DESC,
urlSortParams,
- ISSUES_VIEW_TYPE_KEY,
- ISSUES_LIST_VIEW_KEY,
- ISSUES_GRID_VIEW_KEY,
} from '../constants';
import eventHub from '../eventhub';
import reorderIssuesMutation from '../queries/reorder_issues.mutation.graphql';
@@ -126,13 +125,12 @@ export default {
ISSUES_LIST_VIEW_KEY,
components: {
CsvImportExportButtons,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownGroup,
EmptyStateWithAnyIssues,
EmptyStateWithoutAnyIssues,
GlButton,
GlButtonGroup,
- GlDropdown,
- GlDropdownDivider,
- GlDropdownItem,
IssuableByEmail,
IssuableList,
IssueCardStatistics,
@@ -205,6 +203,20 @@ export default {
state: STATUS_OPEN,
pageSize: DEFAULT_PAGE_SIZE,
viewType: ISSUES_LIST_VIEW_KEY,
+ subscribeDropdownOptions: {
+ items: [
+ {
+ text: i18n.rssLabel,
+ href: this.rssPath,
+ extraAttrs: { 'data-testid': 'subscribe-rss' },
+ },
+ {
+ text: i18n.calendarLabel,
+ href: this.calendarPath,
+ extraAttrs: { 'data-testid': 'subscribe-calendar' },
+ },
+ ],
+ },
};
},
apollo: {
@@ -882,12 +894,12 @@ export default {
:query-variables="newIssueDropdownQueryVariables"
:extract-projects="extractProjects"
/>
- <gl-dropdown
+ <gl-disclosure-dropdown
v-gl-tooltip.hover="$options.i18n.actionsLabel"
category="tertiary"
icon="ellipsis_v"
no-caret
- :text="$options.i18n.actionsLabel"
+ :toggle-text="$options.i18n.actionsLabel"
text-sr-only
data-qa-selector="issues_list_more_actions_dropdown"
>
@@ -896,16 +908,8 @@ export default {
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
-
- <gl-dropdown-divider v-if="showCsvButtons" />
-
- <gl-dropdown-item :href="rssPath">
- {{ $options.i18n.rssLabel }}
- </gl-dropdown-item>
- <gl-dropdown-item :href="calendarPath">
- {{ $options.i18n.calendarLabel }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <gl-disclosure-dropdown-group :bordered="true" :group="subscribeDropdownOptions" />
+ </gl-disclosure-dropdown>
</template>
<template #timeframe="{ issuable = {} }">
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index 8c8cc7984b1..18dd3f4366c 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -22,7 +22,7 @@ export default {
data-track-action="click_button"
data-track-label="reply_comment_button"
category="tertiary"
- icon="comment"
+ icon="reply"
:title="$options.i18n.buttonText"
:aria-label="$options.i18n.buttonText"
@click="$emit('startReplying')"
diff --git a/app/assets/javascripts/profile/components/overview_tab.vue b/app/assets/javascripts/profile/components/overview_tab.vue
index 21f8a2d3500..8cfa3fb3eea 100644
--- a/app/assets/javascripts/profile/components/overview_tab.vue
+++ b/app/assets/javascripts/profile/components/overview_tab.vue
@@ -1,16 +1,24 @@
<script>
import { GlTab, GlLoadingIcon, GlLink } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
+import { createAlert } from '~/alert';
import { s__ } from '~/locale';
import ProjectsList from '~/vue_shared/components/projects_list/projects_list.vue';
+import ContributionEvents from '~/contribution_events/components/contribution_events.vue';
import ActivityCalendar from './activity_calendar.vue';
export default {
i18n: {
title: s__('UserProfile|Overview'),
personalProjects: s__('UserProfile|Personal projects'),
+ activity: s__('UserProfile|Activity'),
viewAll: s__('UserProfile|View all'),
+ eventsErrorMessage: s__(
+ 'UserProfile|An error occurred loading the activity. Please refresh the page to try again.',
+ ),
},
- components: { GlTab, GlLoadingIcon, GlLink, ActivityCalendar, ProjectsList },
+ components: { GlTab, GlLoadingIcon, GlLink, ActivityCalendar, ProjectsList, ContributionEvents },
+ inject: ['userActivityPath'],
props: {
personalProjects: {
type: Array,
@@ -21,15 +29,44 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ events: [],
+ eventsLoading: false,
+ };
+ },
+ async mounted() {
+ this.eventsLoading = true;
+
+ try {
+ const { data: events } = await axios.get(this.userActivityPath, {
+ params: { limit: 10 },
+ });
+ this.events = events;
+ } catch (error) {
+ createAlert({ message: this.$options.i18n.eventsErrorMessage, error, captureError: true });
+ } finally {
+ this.eventsLoading = false;
+ }
+ },
};
</script>
<template>
<gl-tab :title="$options.i18n.title">
<activity-calendar />
- <div class="gl-mx-n3 gl-display-flex gl-flex-wrap">
- <div class="gl-px-3 gl-w-full gl-lg-w-half"></div>
- <div class="gl-px-3 gl-w-full gl-lg-w-half" data-testid="personal-projects-section">
+ <div class="gl-mx-n5 gl-display-flex gl-flex-wrap">
+ <div class="gl-px-5 gl-w-full gl-lg-w-half" data-testid="activity-section">
+ <div
+ class="gl-display-flex gl-align-items-center gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
+ >
+ <h4 class="gl-flex-grow-1">{{ $options.i18n.activity }}</h4>
+ <gl-link href="">{{ $options.i18n.viewAll }}</gl-link>
+ </div>
+ <gl-loading-icon v-if="eventsLoading" class="gl-mt-5" size="md" />
+ <contribution-events v-else :events="events" />
+ </div>
+ <div class="gl-px-5 gl-w-full gl-lg-w-half" data-testid="personal-projects-section">
<div
class="gl-display-flex gl-align-items-center gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
>
diff --git a/app/assets/javascripts/profile/index.js b/app/assets/javascripts/profile/index.js
index 70c60c2d884..198ffdb434b 100644
--- a/app/assets/javascripts/profile/index.js
+++ b/app/assets/javascripts/profile/index.js
@@ -17,6 +17,7 @@ export const initProfileTabs = () => {
followeesCount,
followersCount,
userCalendarPath,
+ userActivityPath,
utcOffset,
userId,
snippetsEmptyState,
@@ -34,6 +35,7 @@ export const initProfileTabs = () => {
followeesCount: parseInt(followeesCount, 10),
followersCount: parseInt(followersCount, 10),
userCalendarPath,
+ userActivityPath,
utcOffset,
userId,
snippetsEmptyState,
diff --git a/app/assets/javascripts/super_sidebar/components/frequent_items_list.vue b/app/assets/javascripts/super_sidebar/components/frequent_items_list.vue
index 79f3faacac9..02adebc50af 100644
--- a/app/assets/javascripts/super_sidebar/components/frequent_items_list.vue
+++ b/app/assets/javascripts/super_sidebar/components/frequent_items_list.vue
@@ -105,7 +105,7 @@ export default {
icon="dash"
:aria-label="$options.i18n.removeItem"
:title="$options.i18n.removeItem"
- class="gl-align-self-center gl-p-1! gl-absolute gl-right-4"
+ class="gl-align-self-center gl-mr-2"
data-testid="item-remove"
@click.stop.prevent="handleItemRemove(item)"
/>
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue
index 90a040b97f7..0ee9db10ee2 100644
--- a/app/assets/javascripts/super_sidebar/components/nav_item.vue
+++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue
@@ -147,14 +147,14 @@ export default {
/>
</slot>
</div>
- <div class="gl-pr-8 gl-text-gray-900 gl-truncate-end">
+ <div class="gl-flex-grow-1 gl-text-gray-900 gl-truncate-end">
{{ item.title }}
<div v-if="item.subtitle" class="gl-font-sm gl-text-gray-500 gl-truncate-end">
{{ item.subtitle }}
</div>
</div>
<slot name="actions"></slot>
- <span v-if="hasPill || isPinnable" class="gl-flex-grow-1 gl-text-right gl-mr-3 gl-relative">
+ <span v-if="hasPill || isPinnable" class="gl-text-right gl-mr-3 gl-relative">
<gl-badge
v-if="hasPill"
size="sm"
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
index dd6923d9fcd..69fce23ede4 100644
--- a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
@@ -1,9 +1,10 @@
<script>
import {
- GlDropdown,
- GlDropdownSectionHeader,
- GlFormInputGroup,
GlButton,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ GlFormGroup,
+ GlFormInputGroup,
GlTooltipDirective,
} from '@gitlab/ui';
import { getHTTPProtocol } from '~/lib/utils/url_utility';
@@ -11,8 +12,9 @@ import { __, sprintf } from '~/locale';
export default {
components: {
- GlDropdown,
- GlDropdownSectionHeader,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ GlFormGroup,
GlFormInputGroup,
GlButton,
},
@@ -45,47 +47,45 @@ export default {
};
</script>
<template>
- <gl-dropdown right :text="$options.labels.defaultLabel" category="primary" variant="confirm">
- <div class="pb-2 mx-1">
- <template v-if="sshLink">
- <gl-dropdown-section-header>{{ $options.labels.ssh }}</gl-dropdown-section-header>
-
- <div class="mx-3">
- <gl-form-input-group :value="sshLink" readonly select-on-click>
- <template #append>
- <gl-button
- v-gl-tooltip.hover
- :title="$options.copyURLTooltip"
- :aria-label="$options.copyURLTooltip"
- :data-clipboard-text="sshLink"
- data-qa-selector="copy_ssh_url_button"
- icon="copy-to-clipboard"
- class="d-inline-flex"
- />
- </template>
- </gl-form-input-group>
- </div>
- </template>
-
- <template v-if="httpLink">
- <gl-dropdown-section-header>{{ httpLabel }}</gl-dropdown-section-header>
-
- <div class="mx-3">
- <gl-form-input-group :value="httpLink" readonly select-on-click>
- <template #append>
- <gl-button
- v-gl-tooltip.hover
- :title="$options.copyURLTooltip"
- :aria-label="$options.copyURLTooltip"
- :data-clipboard-text="httpLink"
- data-qa-selector="copy_http_url_button"
- icon="copy-to-clipboard"
- class="d-inline-flex"
- />
- </template>
- </gl-form-input-group>
- </div>
- </template>
- </div>
- </gl-dropdown>
+ <gl-disclosure-dropdown
+ :toggle-text="$options.labels.defaultLabel"
+ category="primary"
+ variant="confirm"
+ placement="right"
+ >
+ <gl-disclosure-dropdown-item v-if="sshLink">
+ <gl-form-group :label="$options.labels.ssh" class="gl-px-3 gl-my-3">
+ <gl-form-input-group :value="sshLink" readonly select-on-click>
+ <template #append>
+ <gl-button
+ v-gl-tooltip.hover
+ :title="$options.copyURLTooltip"
+ :aria-label="$options.copyURLTooltip"
+ :data-clipboard-text="sshLink"
+ data-qa-selector="copy_ssh_url_button"
+ icon="copy-to-clipboard"
+ class="gl-display-inline-flex"
+ />
+ </template>
+ </gl-form-input-group>
+ </gl-form-group>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item v-if="httpLink">
+ <gl-form-group :label="httpLabel" class="gl-px-3 gl-mb-3">
+ <gl-form-input-group :value="httpLink" readonly select-on-click>
+ <template #append>
+ <gl-button
+ v-gl-tooltip.hover
+ :title="$options.copyURLTooltip"
+ :aria-label="$options.copyURLTooltip"
+ :data-clipboard-text="httpLink"
+ data-qa-selector="copy_http_url_button"
+ icon="copy-to-clipboard"
+ class="gl-display-inline-flex"
+ />
+ </template>
+ </gl-form-input-group>
+ </gl-form-group>
+ </gl-disclosure-dropdown-item>
+ </gl-disclosure-dropdown>
</template>
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index ab7f9bb4927..10d0d03e56d 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -28,7 +28,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :cancel_auto_stop]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
- before_action :set_kas_cookie, only: [:index], if: -> { current_user }
+ before_action :set_kas_cookie, only: [:index], if: -> { current_user && request.format.html? }
after_action :expire_etag_cache, only: [:cancel_auto_stop]
track_event :index, :folder, :show, :new, :edit, :create, :update, :stop, :cancel_auto_stop, :terminal,
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 108ee65bffd..c8002c437a9 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -187,6 +187,7 @@ module UsersHelper
followees_count: user.followees.count,
followers_count: user.followers.count,
user_calendar_path: user_calendar_path(user, :json),
+ user_activity_path: user_activity_path(user, :json),
utc_offset: local_timezone_instance(user.timezone).now.utc_offset,
user_id: user.id,
snippets_empty_state: image_path('illustrations/empty-state/empty-snippets-md.svg')
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index a823b8a93fb..6cb95d665cc 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -695,13 +695,6 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
presence: true,
if: :update_runner_versions_enabled?
- validates :unconfirmed_users_delete_after_days,
- numericality: { only_integer: true, greater_than: 0 },
- if: :delete_unconfirmed_users
-
- validates :delete_unconfirmed_users,
- inclusion: { in: [true, false], message: N_('must be a boolean value') }
-
validates :inactive_projects_min_size_mb,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb
index 1094a131e68..c0ffbb401f6 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb
@@ -22,9 +22,19 @@ module Ci
# Run the process only if we can obtain an exclusive lease; returns nil if lease is unavailable
success = try_obtain_lease { process! }
- # Re-schedule if we need further processing
- if success && pipeline.needs_processing?
- PipelineProcessWorker.perform_async(pipeline.id)
+ if success
+ if ::Feature.enabled?(:ci_reset_skipped_jobs_in_atomic_processing, project)
+ # If any jobs changed from stopped to alive status during pipeline processing, we must
+ # re-reset their dependent jobs; see https://gitlab.com/gitlab-org/gitlab/-/issues/388539.
+ new_alive_jobs.group_by(&:user).each do |user, jobs|
+ log_running_reset_skipped_jobs_service(jobs)
+
+ ResetSkippedJobsService.new(project, user).execute(jobs)
+ end
+ end
+
+ # Re-schedule if we need further processing
+ PipelineProcessWorker.perform_async(pipeline.id) if pipeline.needs_processing?
end
success
@@ -105,6 +115,25 @@ module Ci
end
end
+ # Gets the jobs that changed from stopped to alive status since the initial status collection
+ # was evaluated. We determine this by checking if their current status is no longer stopped.
+ def new_alive_jobs
+ initial_stopped_job_names = @collection.stopped_job_names
+
+ return [] if initial_stopped_job_names.empty?
+
+ new_collection = AtomicProcessingService::StatusCollection.new(pipeline)
+ new_alive_job_names = initial_stopped_job_names - new_collection.stopped_job_names
+
+ return [] if new_alive_job_names.empty?
+
+ pipeline
+ .current_jobs
+ .by_name(new_alive_job_names)
+ .preload(:user) # rubocop: disable CodeReuse/ActiveRecord
+ .to_a
+ end
+
def project
pipeline.project
end
@@ -116,6 +145,17 @@ module Ci
def lease_timeout
DEFAULT_LEASE_TIMEOUT
end
+
+ def log_running_reset_skipped_jobs_service(jobs)
+ Gitlab::AppJsonLogger.info(
+ class: self.class.name.to_s,
+ message: 'Running ResetSkippedJobsService on new alive jobs',
+ project_id: project.id,
+ pipeline_id: pipeline.id,
+ user_id: jobs.first.user.id,
+ jobs_count: jobs.count
+ )
+ end
end
end
end
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb
index 85646b79254..9a53c6d8fc1 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb
@@ -67,6 +67,11 @@ module Ci
all_jobs.lazy.reject { |job| job[:processed] }
end
+ # This method returns the names of jobs that have a stopped status
+ def stopped_job_names
+ all_jobs.select { |job| job[:status].in?(Ci::HasStatus::STOPPED_STATUSES) }.pluck(:name) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
private
# We use these columns to perform an efficient calculation of a status