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-11-08 18:07:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-08 18:07:19 +0300
commita34d7fd9a723d6cc9c7348be2afe522bdc2be67f (patch)
tree5971e13ca0832ae06c599b3d5eec2e2fe71d884f /app
parent5f89187f0433fc84d8387de25220185235d61ed1 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js28
-rw-r--r--app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue45
-rw-r--r--app/assets/javascripts/api/bulk_imports_api.js15
-rw-r--r--app/assets/javascripts/import/constants.js12
-rw-r--r--app/assets/javascripts/import/details/components/bulk_import_details_app.vue31
-rw-r--r--app/assets/javascripts/import/details/components/import_details_app.vue32
-rw-r--r--app/assets/javascripts/import/details/components/import_details_table.vue96
-rw-r--r--app/assets/javascripts/pages/import/bulk_imports/details/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/product_analytics/graphs/index.js3
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue48
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js10
-rw-r--r--app/controllers/jwt_controller.rb2
-rw-r--r--app/finders/organizations/user_organizations_finder.rb26
-rw-r--r--app/graphql/resolvers/users/frecent_groups_resolver.rb23
-rw-r--r--app/graphql/resolvers/users/frecent_projects_resolver.rb21
-rw-r--r--app/graphql/resolvers/users/organizations_resolver.rb18
-rw-r--r--app/graphql/types/query_type.rb8
-rw-r--r--app/graphql/types/user_interface.rb5
-rw-r--r--app/helpers/sorting_helper.rb15
-rw-r--r--app/models/concerns/enums/package_metadata.rb3
-rw-r--r--app/models/concerns/enums/sbom.rb3
-rw-r--r--app/models/concerns/users/visitable.rb39
-rw-r--r--app/models/deploy_token.rb3
-rw-r--r--app/models/users/group_visit.rb7
-rw-r--r--app/models/users/project_visit.rb7
-rw-r--r--app/policies/group_policy.rb26
-rw-r--r--app/policies/user_policy.rb1
-rw-r--r--app/services/auth/dependency_proxy_authentication_service.rb26
-rw-r--r--app/services/service_desk/custom_emails/create_service.rb4
-rw-r--r--app/views/import/bulk_imports/details.html.haml2
33 files changed, 433 insertions, 144 deletions
diff --git a/app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js b/app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js
deleted file mode 100644
index 91cb48e181b..00000000000
--- a/app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import Vue from 'vue';
-import ActivityChart from './components/activity_chart.vue';
-
-export default () => {
- const containers = document.querySelectorAll('.js-project-analytics-chart');
-
- if (!containers) {
- return false;
- }
-
- return containers.forEach((container) => {
- const { chartData } = container.dataset;
- const formattedData = JSON.parse(chartData);
-
- return new Vue({
- el: container,
- components: {
- ActivityChart,
- },
- provide: {
- formattedData,
- },
- render(createElement) {
- return createElement('activity-chart');
- },
- });
- });
-};
diff --git a/app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue b/app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue
deleted file mode 100644
index 2be9ebda87a..00000000000
--- a/app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<script>
-import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { s__ } from '~/locale';
-
-export default {
- i18n: {
- noDataMsg: s__(
- 'ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already.',
- ),
- },
- components: {
- GlColumnChart,
- },
- inject: {
- formattedData: {
- default: {},
- },
- },
- computed: {
- barSeriesData() {
- return [
- {
- name: 'full',
- data: this.formattedData.keys.map((val, idx) => [val, this.formattedData.values[idx]]),
- },
- ];
- },
- },
-};
-</script>
-
-<template>
- <div class="gl-xs-w-full">
- <gl-column-chart
- v-if="formattedData.keys"
- :bars="barSeriesData"
- :x-axis-title="__('Value')"
- :y-axis-title="__('Number of events')"
- :x-axis-type="'category'"
- />
- <p v-else data-testid="noActivityChartData">
- {{ $options.i18n.noDataMsg }}
- </p>
- </div>
-</template>
diff --git a/app/assets/javascripts/api/bulk_imports_api.js b/app/assets/javascripts/api/bulk_imports_api.js
index d636cfdff0b..248f5601705 100644
--- a/app/assets/javascripts/api/bulk_imports_api.js
+++ b/app/assets/javascripts/api/bulk_imports_api.js
@@ -2,6 +2,21 @@ import { buildApiUrl } from '~/api/api_utils';
import axios from '~/lib/utils/axios_utils';
const BULK_IMPORT_ENTITIES_PATH = '/api/:version/bulk_imports/entities';
+const BULK_IMPORT_ENTITIES_FAILURES_PATH =
+ '/api/:version/bulk_imports/:id/entities/:entity_id/failures';
export const getBulkImportsHistory = (params) =>
axios.get(buildApiUrl(BULK_IMPORT_ENTITIES_PATH), { params });
+
+export const getBulkImportFailures = (id, entityId, { page, perPage }) => {
+ const failuresPath = buildApiUrl(BULK_IMPORT_ENTITIES_FAILURES_PATH)
+ .replace(':id', encodeURIComponent(id))
+ .replace(':entity_id', encodeURIComponent(entityId));
+
+ return axios.get(failuresPath, {
+ params: {
+ page,
+ per_page: perPage,
+ },
+ });
+};
diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js
index ddf69a8fcdf..b02eb3c4307 100644
--- a/app/assets/javascripts/import/constants.js
+++ b/app/assets/javascripts/import/constants.js
@@ -1,6 +1,18 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { __, s__ } from '~/locale';
+export const BULK_IMPORT_STATIC_ITEMS = {
+ badges: __('Badge'),
+ boards: s__('IssueBoards|Board'),
+ epics: __('Epic'),
+ issues: __('Issue'),
+ labels: __('Label'),
+ members: __('Member'),
+ merge_requests: __('Merge request'),
+ milestones: __('Milestone'),
+ project: __('Project'),
+};
+
const STATISTIC_ITEMS = {
diff_note: __('Diff notes'),
issue: __('Issues'),
diff --git a/app/assets/javascripts/import/details/components/bulk_import_details_app.vue b/app/assets/javascripts/import/details/components/bulk_import_details_app.vue
index d6c16075482..5da16454032 100644
--- a/app/assets/javascripts/import/details/components/bulk_import_details_app.vue
+++ b/app/assets/javascripts/import/details/components/bulk_import_details_app.vue
@@ -1,15 +1,44 @@
<script>
+import { __ } from '~/locale';
import ImportDetailsTable from '~/import/details/components/import_details_table.vue';
export default {
name: 'BulkImportDetailsApp',
components: { ImportDetailsTable },
+
+ fields: [
+ {
+ key: 'relation',
+ label: __('Type'),
+ tdClass: 'gl-white-space-nowrap',
+ },
+ {
+ key: 'source_title',
+ label: __('Title'),
+ tdClass: 'gl-md-w-30 gl-word-break-word',
+ },
+ {
+ key: 'error',
+ label: __('Error'),
+ },
+ {
+ key: 'correlation_id_value',
+ label: __('Correlation ID'),
+ },
+ ],
+
+ LOCAL_STORAGE_KEY: 'gl-bulk-import-details-page-size',
};
</script>
<template>
<div>
<h1>{{ s__('Import|GitLab Migration details') }}</h1>
- <import-details-table />
+
+ <import-details-table
+ bulk-import
+ :fields="$options.fields"
+ :local-storage-key="$options.LOCAL_STORAGE_KEY"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/import/details/components/import_details_app.vue b/app/assets/javascripts/import/details/components/import_details_app.vue
index 3aa60c00ff8..f654dc61e07 100644
--- a/app/assets/javascripts/import/details/components/import_details_app.vue
+++ b/app/assets/javascripts/import/details/components/import_details_app.vue
@@ -1,14 +1,44 @@
<script>
+import { __ } from '~/locale';
import ImportDetailsTable from './import_details_table.vue';
export default {
+ name: 'ImportDetailsApp',
components: { ImportDetailsTable },
+
+ fields: [
+ {
+ key: 'type',
+ label: __('Type'),
+ tdClass: 'gl-white-space-nowrap',
+ },
+ {
+ key: 'title',
+ label: __('Title'),
+ tdClass: 'gl-md-w-30 gl-word-break-word',
+ },
+ {
+ key: 'provider_url',
+ label: __('URL'),
+ tdClass: 'gl-white-space-nowrap',
+ },
+ {
+ key: 'details',
+ label: __('Details'),
+ },
+ ],
+
+ LOCAL_STORAGE_KEY: 'gl-import-details-page-size',
};
</script>
<template>
<div>
<h1>{{ s__('Import|GitHub import details') }}</h1>
- <import-details-table />
+
+ <import-details-table
+ :fields="$options.fields"
+ :local-storage-key="$options.LOCAL_STORAGE_KEY"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue
index 813dc1f2645..535ccb525ac 100644
--- a/app/assets/javascripts/import/details/components/import_details_table.vue
+++ b/app/assets/javascripts/import/details/components/import_details_table.vue
@@ -1,12 +1,13 @@
<script>
import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
-import { __, s__ } from '~/locale';
+import { s__ } from '~/locale';
import { createAlert } from '~/alert';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
-import { STATISTIC_ITEMS } from '../../constants';
+import { getBulkImportFailures } from '~/rest_api';
+import { BULK_IMPORT_STATIC_ITEMS, STATISTIC_ITEMS } from '../../constants';
import { fetchImportFailures } from '../api';
const DEFAULT_PAGE_SIZE = 20;
@@ -21,28 +22,6 @@ export default {
PaginationBar,
},
STATISTIC_ITEMS,
- LOCAL_STORAGE_KEY: 'gl-import-details-page-size',
- fields: [
- {
- key: 'type',
- label: __('Type'),
- tdClass: 'gl-white-space-nowrap',
- },
- {
- key: 'title',
- label: __('Title'),
- tdClass: 'gl-md-w-30 gl-word-break-word',
- },
- {
- key: 'provider_url',
- label: __('URL'),
- tdClass: 'gl-white-space-nowrap',
- },
- {
- key: 'details',
- label: __('Details'),
- },
- ],
i18n: {
fetchErrorMessage: s__('Import|An error occurred while fetching import details.'),
@@ -55,6 +34,25 @@ export default {
},
},
+ props: {
+ bulkImport: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ fields: {
+ type: Array,
+ required: true,
+ },
+
+ localStorageKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
data() {
return {
items: [],
@@ -97,18 +95,28 @@ export default {
this.loadImportFailures();
},
+ fetchFn(params) {
+ return this.bulkImport
+ ? getBulkImportFailures(
+ getParameterValues('id')[0],
+ getParameterValues('entity_id')[0],
+ params,
+ )
+ : fetchImportFailures(this.failuresPath, {
+ projectId: getParameterValues('project_id')[0],
+ ...params,
+ });
+ },
+
async loadImportFailures() {
- if (!this.failuresPath) {
+ if (!this.bulkImport && !this.failuresPath) {
return;
}
this.loading = true;
+
try {
- const response = await fetchImportFailures(this.failuresPath, {
- projectId: getParameterValues('project_id')[0],
- page: this.page,
- perPage: this.perPage,
- });
+ const response = await this.fetchFn({ page: this.page, perPage: this.perPage });
const { page, perPage, totalPages, total } = parseIntPagination(
normalizeHeaders(response.headers),
@@ -123,13 +131,17 @@ export default {
}
this.loading = false;
},
+
+ itemTypeText(type) {
+ return (this.bulkImport ? BULK_IMPORT_STATIC_ITEMS[type] : STATISTIC_ITEMS[type]) || type;
+ },
},
};
</script>
<template>
<div>
- <gl-table :fields="$options.fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
+ <gl-table :fields="fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
<template #table-busy>
<gl-loading-icon size="lg" class="gl-my-5" />
</template>
@@ -139,7 +151,7 @@ export default {
</template>
<template #cell(type)="{ item: { type } }">
- {{ $options.STATISTIC_ITEMS[type] }}
+ {{ itemTypeText(type) }}
</template>
<template #cell(provider_url)="{ item: { provider_url } }">
<gl-link v-if="provider_url" :href="provider_url" target="_blank">
@@ -147,12 +159,30 @@ export default {
<gl-icon name="external-link" />
</gl-link>
</template>
+
+ <template #cell(relation)="{ item: { relation } }">
+ {{ itemTypeText(relation) }}
+ </template>
+ <template #cell(source_title)="{ item: { source_title, source_url } }">
+ <gl-link v-if="source_url" :href="source_url" target="_blank">
+ {{ source_title }}
+ <gl-icon name="external-link" />
+ </gl-link>
+ <span v-else>
+ {{ source_title }}
+ </span>
+ </template>
+ <template #cell(error)="{ item: { exception_class, exception_message } }">
+ <strong>{{ exception_class }}</strong>
+ <p>{{ exception_message }}</p>
+ </template>
</gl-table>
+
<pagination-bar
v-if="hasItems"
:page-info="pageInfo"
class="gl-mt-5"
- :storage-key="$options.LOCAL_STORAGE_KEY"
+ :storage-key="localStorageKey"
@set-page="setPage"
@set-page-size="setPageSize"
/>
diff --git a/app/assets/javascripts/pages/import/bulk_imports/details/index.js b/app/assets/javascripts/pages/import/bulk_imports/details/index.js
index ca5de576536..5c2571af60f 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/details/index.js
+++ b/app/assets/javascripts/pages/import/bulk_imports/details/index.js
@@ -8,14 +8,9 @@ export const initBulkImportDetails = () => {
return null;
}
- const { failuresPath } = el.dataset;
-
return new Vue({
el,
name: 'BulkImportDetailsRoot',
- provide: {
- failuresPath,
- },
render(createElement) {
return createElement(BulkImportDetailsApp);
},
diff --git a/app/assets/javascripts/pages/projects/product_analytics/graphs/index.js b/app/assets/javascripts/pages/projects/product_analytics/graphs/index.js
deleted file mode 100644
index ba03fccdb03..00000000000
--- a/app/assets/javascripts/pages/projects/product_analytics/graphs/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initActivityCharts from '~/analytics/product_analytics/activity_charts_bundle';
-
-initActivityCharts();
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue b/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue
index 8edf2cfb4aa..6f22af4bd26 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue
@@ -6,6 +6,7 @@ import {
GlFormInputGroup,
GlFormInput,
GlLink,
+ GlFormSelect,
GlSprintf,
} from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
@@ -28,6 +29,11 @@ import {
I18N_FORM_SMTP_USERNAME_LABEL,
I18N_FORM_SMTP_PASSWORD_LABEL,
I18N_FORM_SMTP_PASSWORD_DESCRIPTION,
+ I18N_FORM_SMTP_AUTHENTICATION_LABEL,
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
I18N_FORM_SUBMIT_LABEL,
I18N_FORM_INVALID_FEEDBACK_CUSTOM_EMAIL,
I18N_FORM_INVALID_FEEDBACK_SMTP_ADDRESS,
@@ -47,6 +53,7 @@ export default {
GlFormGroup,
GlFormInputGroup,
GlFormInput,
+ GlFormSelect,
GlLink,
GlSprintf,
},
@@ -61,6 +68,11 @@ export default {
I18N_FORM_SMTP_USERNAME_LABEL,
I18N_FORM_SMTP_PASSWORD_LABEL,
I18N_FORM_SMTP_PASSWORD_DESCRIPTION,
+ I18N_FORM_SMTP_AUTHENTICATION_LABEL,
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
I18N_FORM_SUBMIT_LABEL,
I18N_FORM_INVALID_FEEDBACK_CUSTOM_EMAIL,
I18N_FORM_INVALID_FEEDBACK_SMTP_ADDRESS,
@@ -87,6 +99,7 @@ export default {
smtpPort: '587',
smtpUsername: '',
smtpPassword: '',
+ smtpAuthentication: null,
validationState: {
customEmail: null,
smtpAddress: null,
@@ -118,6 +131,7 @@ export default {
smtp_port: this.smtpPort,
smtp_username: this.smtpUsername,
smtp_password: this.smtpPassword,
+ smtp_authentication: this.smtpAuthentication,
};
},
onCustomEmailChange() {
@@ -150,6 +164,26 @@ export default {
this.validateSmtpUsername();
this.validateSmtpPassword();
},
+ getSmtpAuthenticationOptions() {
+ return [
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ value: null,
+ },
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ value: 'plain',
+ },
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ value: 'login',
+ },
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
+ value: 'cram_md5',
+ },
+ ];
+ },
},
};
</script>
@@ -303,6 +337,20 @@ export default {
/>
</gl-form-group>
+ <gl-form-group
+ :label="$options.I18N_FORM_SMTP_AUTHENTICATION_LABEL"
+ label-for="custom-email-form-smtp-password"
+ class="gl-mt-3"
+ >
+ <gl-form-select
+ id="custom-email-form-smtp-authentication"
+ v-model.trim="smtpAuthentication"
+ :options="getSmtpAuthenticationOptions()"
+ :aria-label="$options.I18N_FORM_SMTP_AUTHENTICATION_LABEL"
+ :disabled="isSubmitting"
+ />
+ </gl-form-group>
+
<gl-button
type="submit"
variant="confirm"
diff --git a/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js b/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js
index aafd77bd25e..8ac186e292c 100644
--- a/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js
+++ b/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js
@@ -37,6 +37,13 @@ export const I18N_FORM_SMTP_PORT_DESCRIPTION = s__(
export const I18N_FORM_SMTP_USERNAME_LABEL = s__('ServiceDesk|SMTP username');
export const I18N_FORM_SMTP_PASSWORD_LABEL = s__('ServiceDesk|SMTP password');
export const I18N_FORM_SMTP_PASSWORD_DESCRIPTION = s__('ServiceDesk|Minimum 8 characters long.');
+export const I18N_FORM_SMTP_AUTHENTICATION_LABEL = s__('ServiceDesk|SMTP authentication method');
+export const I18N_FORM_SMTP_AUTHENTICATION_NONE = s__(
+ 'ServiceDesk|Let GitLab select a server-supported method (recommended)',
+);
+export const I18N_FORM_SMTP_AUTHENTICATION_PLAIN = s__('ServiceDesk|Plain');
+export const I18N_FORM_SMTP_AUTHENTICATION_LOGIN = s__('ServiceDesk|Login');
+export const I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5 = s__('ServiceDesk|CRAM-MD5');
export const I18N_FORM_SUBMIT_LABEL = s__('ServiceDesk|Save and test connection');
export const I18N_FORM_INVALID_FEEDBACK_CUSTOM_EMAIL = s__(
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index a3a5864240c..a29393d9f93 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -236,7 +236,7 @@ export default {
};
</script>
<template>
- <div class="js-mr-approvals mr-section-container mr-widget-workflow">
+ <div v-if="approvals" class="js-mr-approvals mr-section-container mr-widget-workflow">
<state-container
:is-loading="$apollo.queries.approvals.loading"
:mr="mr"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 24ec740c910..bc3d8ad7824 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -63,6 +63,10 @@ export default {
},
manual: true,
result({ data }) {
+ if (!data.project) {
+ return;
+ }
+
if (Object.keys(this.state).length === 0) {
this.removeSourceBranch =
data.project.mergeRequest.shouldRemoveSourceBranch ||
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
index 564e9321d54..8bb2f2898eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
@@ -18,8 +18,16 @@ export default {
iid: `${this.mr.iid}`,
};
},
- update: (data) => data.project.mergeRequest,
+ update: (data) => data.project?.mergeRequest,
result({ data }) {
+ // This case can occur when backend returns an empty project due to expired session.
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/413627 for more information.
+ if (!data.project) {
+ // Needed to suppress several errors.
+ this.mr.setApprovals({});
+ return;
+ }
+
const { mergeRequest } = data.project;
this.disableCommittersApproval = data.project.mergeRequestsDisableCommittersApproval;
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 83409c7e096..4163ff8727c 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -34,6 +34,7 @@ class JwtController < ApplicationController
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, request: request)
+ @raw_token = password
if @authentication_result.failed?
log_authentication_failed(login, @authentication_result)
@@ -80,6 +81,7 @@ class JwtController < ApplicationController
def additional_params
{
scopes: scopes_param,
+ raw_token: @raw_token,
deploy_token: @authentication_result.deploy_token,
auth_type: @authentication_result.type
}.compact
diff --git a/app/finders/organizations/user_organizations_finder.rb b/app/finders/organizations/user_organizations_finder.rb
new file mode 100644
index 00000000000..739940c44ca
--- /dev/null
+++ b/app/finders/organizations/user_organizations_finder.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Organizations
+ class UserOrganizationsFinder
+ def initialize(current_user, target_user, params = {})
+ @current_user = current_user
+ @target_user = target_user
+ @params = params
+ end
+
+ def execute
+ return Organizations::Organization.none unless can_read_user_organizations?
+ return Organizations::Organization.none if target_user.blank?
+
+ target_user.organizations
+ end
+
+ private
+
+ attr_reader :current_user, :target_user, :params
+
+ def can_read_user_organizations?
+ current_user&.can?(:read_user_organizations, target_user)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/frecent_groups_resolver.rb b/app/graphql/resolvers/users/frecent_groups_resolver.rb
new file mode 100644
index 00000000000..2fc757e31ab
--- /dev/null
+++ b/app/graphql/resolvers/users/frecent_groups_resolver.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class FrecentGroupsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type [Types::GroupType], null: true
+
+ def resolve
+ return unless current_user.present?
+
+ if Feature.disabled?(:frecent_namespaces_suggestions, current_user)
+ raise_resource_not_available_error!("'frecent_namespaces_suggestions' feature flag is disabled")
+ end
+
+ return unless Feature.enabled?(:frecent_namespaces_suggestions, current_user)
+
+ ::Users::GroupVisit.frecent_groups(user_id: current_user.id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/frecent_projects_resolver.rb b/app/graphql/resolvers/users/frecent_projects_resolver.rb
new file mode 100644
index 00000000000..397d4ca0cfd
--- /dev/null
+++ b/app/graphql/resolvers/users/frecent_projects_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class FrecentProjectsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type [Types::ProjectType], null: true
+
+ def resolve
+ return unless current_user.present?
+
+ if Feature.disabled?(:frecent_namespaces_suggestions, current_user)
+ raise_resource_not_available_error!("'frecent_namespaces_suggestions' feature flag is disabled")
+ end
+
+ ::Users::ProjectVisit.frecent_projects(user_id: current_user.id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/organizations_resolver.rb b/app/graphql/resolvers/users/organizations_resolver.rb
new file mode 100644
index 00000000000..ffc1a141eb6
--- /dev/null
+++ b/app/graphql/resolvers/users/organizations_resolver.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class OrganizationsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::Organizations::OrganizationType.connection_type, null: true
+
+ authorize :read_user_organizations
+ authorizes_object!
+
+ def resolve(**args)
+ ::Organizations::UserOrganizationsFinder.new(current_user, object, args).execute
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index ce458af4e60..173e877d86c 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -55,6 +55,14 @@ module Types
null: false,
description: 'Fields related to design management.'
field :echo, resolver: Resolvers::EchoResolver
+ field :frecent_groups, [Types::GroupType],
+ resolver: Resolvers::Users::FrecentGroupsResolver,
+ description: "A user's frecently visited groups. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.",
+ alpha: { milestone: '16.6' }
+ field :frecent_projects, [Types::ProjectType],
+ resolver: Resolvers::Users::FrecentProjectsResolver,
+ description: "A user's frecently visited projects. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.",
+ alpha: { milestone: '16.6' }
field :gitpod_enabled, GraphQL::Types::Boolean,
null: true,
description: "Whether Gitpod is enabled in application settings."
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index ca1b6eaa900..7a43d5891d7 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -71,6 +71,11 @@ module Types
type: GraphQL::Types::String,
null: false,
description: 'Web path of the user.'
+ field :organizations,
+ resolver: Resolvers::Users::OrganizationsResolver,
+ null: true,
+ alpha: { milestone: '16.6' },
+ description: 'Organizations where the user has access.'
field :group_memberships,
type: Types::GroupMemberType.connection_type,
null: true,
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index a1afb0493d5..8b5c0707d08 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -263,21 +263,6 @@ module SortingHelper
sort_direction_button(url, reverse_sort, sort_value)
end
- def packages_reverse_sort_order_hash
- {
- sort_value_recently_created => sort_value_oldest_created,
- sort_value_oldest_created => sort_value_recently_created,
- sort_value_name => sort_value_name_desc,
- sort_value_name_desc => sort_value_name,
- sort_value_version_desc => sort_value_version_asc,
- sort_value_version_asc => sort_value_version_desc,
- sort_value_type_desc => sort_value_type_asc,
- sort_value_type_asc => sort_value_type_desc,
- sort_value_project_name_desc => sort_value_project_name_asc,
- sort_value_project_name_asc => sort_value_project_name_desc
- }
- end
-
def forks_sort_direction_button(sort_value, without = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id])
reverse_sort = forks_reverse_sort_options_hash[sort_value]
url = page_filter_path(sort: reverse_sort, without: without)
diff --git a/app/models/concerns/enums/package_metadata.rb b/app/models/concerns/enums/package_metadata.rb
index 3f107987ef6..cba6cd2db2e 100644
--- a/app/models/concerns/enums/package_metadata.rb
+++ b/app/models/concerns/enums/package_metadata.rb
@@ -14,7 +14,8 @@ module Enums
apk: 9,
rpm: 10,
deb: 11,
- cbl_mariner: 12
+ cbl_mariner: 12,
+ wolfi: 13
}.with_indifferent_access.freeze
ADVISORY_SOURCES = {
diff --git a/app/models/concerns/enums/sbom.rb b/app/models/concerns/enums/sbom.rb
index 59aafc32d94..64e0a7653d6 100644
--- a/app/models/concerns/enums/sbom.rb
+++ b/app/models/concerns/enums/sbom.rb
@@ -18,7 +18,8 @@ module Enums
apk: 9,
rpm: 10,
deb: 11,
- cbl_mariner: 12
+ cbl_mariner: 12,
+ wolfi: 13
}.with_indifferent_access.freeze
def self.component_types
diff --git a/app/models/concerns/users/visitable.rb b/app/models/concerns/users/visitable.rb
index cb8e5fdc682..029d60d61ee 100644
--- a/app/models/concerns/users/visitable.rb
+++ b/app/models/concerns/users/visitable.rb
@@ -13,6 +13,45 @@ module Users
time = time.to_datetime
where(entity_id: entity_id, user_id: user_id, visited_at: (time - 15.minutes)..(time + 15.minutes))
end
+
+ scope :for_user, ->(user_id) { where(user_id: user_id) }
+
+ scope :recently_visited, -> do
+ where('visited_at > ?', 3.months.ago)
+ .where('visited_at <= ?', Time.current)
+ end
+
+ def self.grouped_by_week_start_and_entity_for_user(user_id:)
+ recently_visited
+ .for_user(user_id)
+ .group(:week_start, :entity_id)
+ .select(
+ :entity_id,
+ "COUNT(entity_id) AS week_count",
+ "DATE_TRUNC('week', visited_at)::date AS week_start",
+ "DENSE_RANK() OVER (ORDER BY DATE_TRUNC('week', visited_at)::date)"
+ )
+ end
+
+ def self.frecent_visits_scores(user_id:, limit:)
+ ranked_entity_visits_query = grouped_by_week_start_and_entity_for_user(user_id: user_id).to_sql
+ sql = <<~SQL
+ SELECT
+ entity_id,
+ SUM(week_count * dense_rank) AS score
+ FROM
+ (#{ranked_entity_visits_query}) as ranked_entity_visits
+ GROUP BY
+ entity_id
+ ORDER BY
+ score DESC
+ LIMIT #{limit}
+ SQL
+
+ ::Gitlab::Database::LoadBalancing::Session.current.fallback_to_replicas_for_ambiguous_queries do
+ connection.execute(sql).to_a
+ end
+ end
end
end
end
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index 920321a1699..2405ff3d252 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -11,7 +11,6 @@ class DeployToken < ApplicationRecord
AVAILABLE_SCOPES = %i[read_repository read_registry write_registry
read_package_registry write_package_registry].freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
- REQUIRED_DEPENDENCY_PROXY_SCOPES = %i[read_registry write_registry].freeze
attribute :expires_at, default: -> { Forever.date }
@@ -57,7 +56,7 @@ class DeployToken < ApplicationRecord
def valid_for_dependency_proxy?
group_type? &&
active? &&
- REQUIRED_DEPENDENCY_PROXY_SCOPES.all? { |scope| scope.in?(scopes) }
+ (Gitlab::Auth::REGISTRY_SCOPES & scopes).size == Gitlab::Auth::REGISTRY_SCOPES.size
end
def revoke!
diff --git a/app/models/users/group_visit.rb b/app/models/users/group_visit.rb
index 0bcfda049fc..d7c76e2ee2c 100644
--- a/app/models/users/group_visit.rb
+++ b/app/models/users/group_visit.rb
@@ -13,5 +13,12 @@ module Users
validates :entity_id, presence: true
validates :user_id, presence: true
validates :visited_at, presence: true
+
+ MAX_FRECENT_ITEMS = 3
+
+ def self.frecent_groups(user_id:)
+ ids = frecent_visits_scores(user_id: user_id, limit: MAX_FRECENT_ITEMS).pluck("entity_id")
+ Group.find(ids)
+ end
end
end
diff --git a/app/models/users/project_visit.rb b/app/models/users/project_visit.rb
index 1d076e0be56..9ff3d8d2c91 100644
--- a/app/models/users/project_visit.rb
+++ b/app/models/users/project_visit.rb
@@ -13,5 +13,12 @@ module Users
validates :entity_id, presence: true
validates :user_id, presence: true
validates :visited_at, presence: true
+
+ MAX_FRECENT_ITEMS = 5
+
+ def self.frecent_projects(user_id:)
+ ids = frecent_visits_scores(user_id: user_id, limit: MAX_FRECENT_ITEMS).pluck("entity_id")
+ Project.find(ids)
+ end
end
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index ca170133105..f927d976f0d 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -69,7 +69,9 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
end
condition(:dependency_proxy_access_allowed) do
- access_level(for_any_session: true) >= GroupMember::GUEST || valid_dependency_proxy_deploy_token
+ valid_dependency_proxy_human_token ||
+ valid_dependency_proxy_group_access_token ||
+ valid_dependency_proxy_deploy_token
end
desc "Deploy token with read_package_registry scope"
@@ -386,6 +388,18 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
user.is_a?(User)
end
+ def user_is_human?
+ user_is_user? && user.human?
+ end
+
+ def user_is_project_bot?
+ user_is_user? && user.project_bot?
+ end
+
+ def user_is_deploy_token?
+ user.is_a?(DeployToken)
+ end
+
def group
@subject
end
@@ -406,8 +420,16 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
resource_access_token_create_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
end
+ def valid_dependency_proxy_human_token
+ user_is_human? && access_level(for_any_session: true) >= GroupMember::GUEST
+ end
+
+ def valid_dependency_proxy_group_access_token
+ user_is_project_bot? && access_level(for_any_session: true) >= GroupMember::GUEST
+ end
+
def valid_dependency_proxy_deploy_token
- @user.is_a?(DeployToken) && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
+ user_is_deploy_token? && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 2fd198b8cf4..04fbc8467c9 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -29,6 +29,7 @@ class UserPolicy < BasePolicy
enable :read_user_personal_access_tokens
enable :read_group_count
enable :read_user_groups
+ enable :read_user_organizations
enable :read_saved_replies
enable :read_user_email_address
enable :admin_user_email_address
diff --git a/app/services/auth/dependency_proxy_authentication_service.rb b/app/services/auth/dependency_proxy_authentication_service.rb
index 164594d6f6c..9033baf8c15 100644
--- a/app/services/auth/dependency_proxy_authentication_service.rb
+++ b/app/services/auth/dependency_proxy_authentication_service.rb
@@ -5,10 +5,11 @@ module Auth
AUDIENCE = 'dependency_proxy'
HMAC_KEY = 'gitlab-dependency-proxy'
DEFAULT_EXPIRE_TIME = 1.minute
+ REQUIRED_ABILITIES = %i[read_container_image create_container_image].freeze
def execute(authentication_abilities:)
return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled
- return error('access forbidden', 403) unless valid_user_actor?
+ return error('access forbidden', 403) unless valid_user_actor?(authentication_abilities)
{ token: authorized_token.encoded }
end
@@ -33,8 +34,17 @@ module Auth
private
- def valid_user_actor?
- current_user || valid_deploy_token?
+ def valid_user_actor?(authentication_abilities)
+ valid_human_user? || valid_group_access_token?(authentication_abilities) || valid_deploy_token?
+ end
+
+ def valid_human_user?
+ current_user.is_a?(User) && current_user.human?
+ end
+
+ def valid_group_access_token?(authentication_abilities)
+ current_user&.project_bot? && group_access_token&.active? &&
+ (REQUIRED_ABILITIES & authentication_abilities).size == REQUIRED_ABILITIES.size
end
def valid_deploy_token?
@@ -49,8 +59,18 @@ module Auth
end
end
+ def group_access_token
+ return unless current_user&.project_bot?
+
+ PersonalAccessTokensFinder.new(state: 'active').find_by_token(raw_token)
+ end
+
def deploy_token
params[:deploy_token]
end
+
+ def raw_token
+ params[:raw_token]
+ end
end
end
diff --git a/app/services/service_desk/custom_emails/create_service.rb b/app/services/service_desk/custom_emails/create_service.rb
index 305f5b3fa11..c06c836f0fa 100644
--- a/app/services/service_desk/custom_emails/create_service.rb
+++ b/app/services/service_desk/custom_emails/create_service.rb
@@ -42,6 +42,8 @@ module ServiceDesk
def create_credential
credential = ::ServiceDesk::CustomEmailCredential.new(create_credential_params.merge(project: project))
credential.save
+ rescue ArgumentError
+ false
end
def create_verification
@@ -53,7 +55,7 @@ module ServiceDesk
end
def create_credential_params
- ensure_params.permit(:smtp_address, :smtp_port, :smtp_username, :smtp_password)
+ ensure_params.permit(:smtp_address, :smtp_port, :smtp_username, :smtp_password, :smtp_authentication)
end
def ensure_params
diff --git a/app/views/import/bulk_imports/details.html.haml b/app/views/import/bulk_imports/details.html.haml
index 0efe71362a6..511bf2c38a1 100644
--- a/app/views/import/bulk_imports/details.html.haml
+++ b/app/views/import/bulk_imports/details.html.haml
@@ -2,4 +2,4 @@
- add_to_breadcrumbs _('Import group'), new_group_path(anchor: 'import-group-pane')
- page_title s_('Import|GitLab Migration details')
-.js-bulk-import-details{ data: { failures_path: '' } }
+.js-bulk-import-details