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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-09 18:09:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-09 18:09:13 +0300
commit9c39a0a9b81f06f6345a6b6e071b8e8cd249c064 (patch)
tree45caaf45fac61866d8cc9098487a78b17c1016a0 /app
parent53af44b90f87cdd8d7126d64669848b0e2be5960 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue32
-rw-r--r--app/assets/javascripts/issues_list/constants.js6
-rw-r--r--app/assets/javascripts/issues_list/index.js24
-rw-r--r--app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql7
-rw-r--r--app/assets/javascripts/issues_list/utils.js5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue95
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue32
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.js1
-rw-r--r--app/models/integrations/datadog.rb40
-rw-r--r--app/models/member.rb5
-rw-r--r--app/models/members/group_member.rb14
-rw-r--r--app/models/members/project_member.rb17
-rw-r--r--app/services/ci/drop_pipeline_service.rb13
-rw-r--r--app/services/projects/create_service.rb2
16 files changed, 213 insertions, 87 deletions
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 2c00daf39dd..8910734eb12 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -12,7 +12,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import createFlash from '~/flash';
import { TYPE_USER } from '~/graphql_shared/constants';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
@@ -20,7 +20,6 @@ import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import {
CREATED_DESC,
i18n,
- initialPageParams,
issuesCountSmartQueryBase,
MAX_LIST_SIZE,
PAGE_SIZE,
@@ -46,12 +45,13 @@ import {
convertToUrlParams,
getDueDateValue,
getFilterTokens,
+ getInitialPageParams,
getSortKey,
getSortOptions,
} from '~/issues_list/utils';
import axios from '~/lib/utils/axios_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
-import { getParameterByName } from '~/lib/utils/url_utility';
+import { getParameterByName, joinPaths } from '~/lib/utils/url_utility';
import {
DEFAULT_NONE_ANY,
OPERATOR_IS_ONLY,
@@ -73,6 +73,7 @@ import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
import eventHub from '../eventhub';
+import reorderIssuesMutation from '../queries/reorder_issues.mutation.graphql';
import searchIterationsQuery from '../queries/search_iterations.query.graphql';
import searchLabelsQuery from '../queries/search_labels.query.graphql';
import searchMilestonesQuery from '../queries/search_milestones.query.graphql';
@@ -161,6 +162,7 @@ export default {
},
data() {
const state = getParameterByName(PARAM_STATE);
+ const sortKey = getSortKey(getParameterByName(PARAM_SORT));
const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC;
return {
@@ -169,9 +171,9 @@ export default {
filterTokens: getFilterTokens(window.location.search),
issues: [],
pageInfo: {},
- pageParams: initialPageParams,
+ pageParams: getInitialPageParams(sortKey),
showBulkEditSidebar: false,
- sortKey: getSortKey(getParameterByName(PARAM_SORT)) || defaultSortKey,
+ sortKey: sortKey || defaultSortKey,
state: state || IssuableStates.Opened,
};
},
@@ -516,12 +518,12 @@ export default {
},
handleClickTab(state) {
if (this.state !== state) {
- this.pageParams = initialPageParams;
+ this.pageParams = getInitialPageParams(this.sortKey);
}
this.state = state;
},
handleFilter(filter) {
- this.pageParams = initialPageParams;
+ this.pageParams = getInitialPageParams(this.sortKey);
this.filterTokens = filter;
},
handleNextPage() {
@@ -558,14 +560,16 @@ export default {
}
return axios
- .put(`${this.issuesPath}/${issueToMove.iid}/reorder`, {
- move_before_id: isMovingToBeginning ? null : moveBeforeId,
- move_after_id: isMovingToEnd ? null : moveAfterId,
+ .put(joinPaths(this.issuesPath, issueToMove.iid, 'reorder'), {
+ move_before_id: isMovingToBeginning ? null : getIdFromGraphQLId(moveBeforeId),
+ move_after_id: isMovingToEnd ? null : getIdFromGraphQLId(moveAfterId),
})
.then(() => {
- // Move issue to new position in list
- this.issues.splice(oldIndex, 1);
- this.issues.splice(newIndex, 0, issueToMove);
+ const serializedVariables = JSON.stringify(this.queryVariables);
+ this.$apollo.mutate({
+ mutation: reorderIssuesMutation,
+ variables: { oldIndex, newIndex, serializedVariables },
+ });
})
.catch(() => {
createFlash({ message: this.$options.i18n.reorderError });
@@ -573,7 +577,7 @@ export default {
},
handleSort(sortKey) {
if (this.sortKey !== sortKey) {
- this.pageParams = initialPageParams;
+ this.pageParams = getInitialPageParams(sortKey);
}
this.sortKey = sortKey;
},
diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js
index e49eec96f81..e07518efba4 100644
--- a/app/assets/javascripts/issues_list/constants.js
+++ b/app/assets/javascripts/issues_list/constants.js
@@ -109,10 +109,14 @@ export const PARAM_DUE_DATE = 'due_date';
export const PARAM_SORT = 'sort';
export const PARAM_STATE = 'state';
-export const initialPageParams = {
+export const defaultPageSizeParams = {
firstPageSize: PAGE_SIZE,
};
+export const largePageSizeParams = {
+ firstPageSize: PAGE_SIZE_MANUAL,
+};
+
export const DUE_DATE_NONE = '0';
export const DUE_DATE_ANY = '';
export const DUE_DATE_OVERDUE = 'overdue';
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js
index 8ff7222814b..b4def1b6e00 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues_list/index.js
@@ -1,5 +1,7 @@
+import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
@@ -82,7 +84,27 @@ export function mountIssuesListApp() {
Vue.use(VueApollo);
- const defaultClient = createDefaultClient({}, { assumeImmutableResults: true });
+ const resolvers = {
+ Mutation: {
+ reorderIssues: (_, { oldIndex, newIndex, serializedVariables }, { cache }) => {
+ const variables = JSON.parse(serializedVariables);
+ const sourceData = cache.readQuery({ query: getIssuesQuery, variables });
+
+ const data = produce(sourceData, (draftData) => {
+ const issues = draftData.project.issues.nodes.slice();
+ const issueToMove = issues[oldIndex];
+ issues.splice(oldIndex, 1);
+ issues.splice(newIndex, 0, issueToMove);
+
+ draftData.project.issues.nodes = issues;
+ });
+
+ cache.writeQuery({ query: getIssuesQuery, variables, data });
+ },
+ },
+ };
+
+ const defaultClient = createDefaultClient(resolvers, { assumeImmutableResults: true });
const apolloProvider = new VueApollo({
defaultClient,
});
diff --git a/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql b/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql
new file mode 100644
index 00000000000..5927e3e83c7
--- /dev/null
+++ b/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql
@@ -0,0 +1,7 @@
+mutation reorderIssues($oldIndex: Int, $newIndex: Int, $serializedVariables: String) {
+ reorderIssues(
+ oldIndex: $oldIndex
+ newIndex: $newIndex
+ serializedVariables: $serializedVariables
+ ) @client
+}
diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js
index 287c85b60d7..7f44ba6bab4 100644
--- a/app/assets/javascripts/issues_list/utils.js
+++ b/app/assets/javascripts/issues_list/utils.js
@@ -3,12 +3,14 @@ import {
BLOCKING_ISSUES_DESC,
CREATED_ASC,
CREATED_DESC,
+ defaultPageSizeParams,
DUE_DATE_ASC,
DUE_DATE_DESC,
DUE_DATE_VALUES,
filters,
LABEL_PRIORITY_ASC,
LABEL_PRIORITY_DESC,
+ largePageSizeParams,
MILESTONE_DUE_ASC,
MILESTONE_DUE_DESC,
NORMAL_FILTER,
@@ -36,6 +38,9 @@ import {
OPERATOR_IS_NOT,
} from '~/vue_shared/components/filtered_search_bar/constants';
+export const getInitialPageParams = (sortKey) =>
+ sortKey === RELATIVE_POSITION_ASC ? largePageSizeParams : defaultPageSizeParams;
+
export const getSortKey = (sort) =>
Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort);
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
index 65e396b34e9..8d360cd2c28 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
@@ -20,13 +20,14 @@ import { numberToHumanSize } from '~/lib/utils/number_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
// import DependencyRow from '~/packages/details/components/dependency_row.vue';
-// import PackageFiles from '~/packages/details/components/package_files.vue';
// import PackageListRow from '~/packages/shared/components/package_list_row.vue';
import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import { packageTypeToTrackCategory } from '~/packages/shared/utils';
import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
+import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue';
+import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue';
import {
PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_COMPOSER,
@@ -40,8 +41,12 @@ import {
SHOW_DELETE_SUCCESS_ALERT,
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
DELETE_PACKAGE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
} from '~/packages_and_registries/package_registry/constants';
+
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
+import destroyPackageFileMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql';
import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import Tracking from '~/tracking';
@@ -55,17 +60,14 @@ export default {
GlTab,
GlTabs,
GlSprintf,
- PackageTitle: () =>
- import('~/packages_and_registries/package_registry/components/details/package_title.vue'),
- TerraformTitle: () =>
- import('~/packages_and_registries/infrastructure_registry/components/details_title.vue'),
+ PackageTitle,
PackagesListLoader,
// PackageListRow,
// DependencyRow,
PackageHistory,
AdditionalMetadata,
InstallationCommands,
- // PackageFiles,
+ PackageFiles,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -74,7 +76,6 @@ export default {
mixins: [Tracking.mixin()],
inject: [
'packageId',
- 'titleComponent',
'projectName',
'canDelete',
'svgPath',
@@ -123,7 +124,7 @@ export default {
};
},
packageFiles() {
- return this.packageEntity.packageFiles;
+ return this.packageEntity?.packageFiles?.nodes;
},
isLoading() {
return this.$apollo.queries.packageEntity.loading;
@@ -133,7 +134,7 @@ export default {
},
tracking() {
return {
- category: packageTypeToTrackCategory(this.packageEntity.package_type),
+ category: packageTypeToTrackCategory(this.packageEntity.packageType),
};
},
hasVersions() {
@@ -143,10 +144,10 @@ export default {
return this.packageEntity.dependency_links || [];
},
showDependencies() {
- return this.packageEntity.package_type === PACKAGE_TYPE_NUGET;
+ return this.packageEntity.packageType === PACKAGE_TYPE_NUGET;
},
showFiles() {
- return this.packageEntity?.package_type !== PACKAGE_TYPE_COMPOSER;
+ return this.packageEntity?.packageType !== PACKAGE_TYPE_COMPOSER;
},
},
methods: {
@@ -158,19 +159,17 @@ export default {
// this.fetchPackageVersions();
}
},
- deletePackage() {
- return this.$apollo
- .mutate({
- mutation: destroyPackageMutation,
- variables: {
- id: this.packageEntity.id,
- },
- })
- .then(({ data }) => {
- if (data?.destroyPackage?.errors[0]) {
- throw data.destroyPackage.errors[0];
- }
- });
+ async deletePackage() {
+ const { data } = await this.$apollo.mutate({
+ mutation: destroyPackageMutation,
+ variables: {
+ id: this.packageEntity.id,
+ },
+ });
+
+ if (data?.destroyPackage?.errors[0]) {
+ throw data.destroyPackage.errors[0];
+ }
},
async confirmPackageDeletion() {
this.track(DELETE_PACKAGE_TRACKING_ACTION);
@@ -195,6 +194,37 @@ export default {
});
}
},
+ async deletePackageFile(id) {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: destroyPackageFileMutation,
+ variables: {
+ id,
+ },
+ awaitRefetchQueries: true,
+ refetchQueries: [
+ {
+ query: getPackageDetails,
+ variables: this.queryVariables,
+ },
+ ],
+ });
+ if (data?.destroyPackageFile?.errors[0]) {
+ throw data.destroyPackageFile.errors[0];
+ }
+ createFlash({
+ message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
+ type: 'success',
+ });
+ } catch (error) {
+ createFlash({
+ message: DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ type: 'warning',
+ captureError: true,
+ error,
+ });
+ }
+ },
handleFileDelete(file) {
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
this.fileToDelete = { ...file };
@@ -202,7 +232,7 @@ export default {
},
confirmFileDelete() {
this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION);
- // this.deletePackageFile(this.fileToDelete.id);
+ this.deletePackageFile(this.fileToDelete.id);
this.fileToDelete = null;
},
},
@@ -245,7 +275,7 @@ export default {
/>
<div v-else class="packages-app">
- <component :is="titleComponent" :package-entity="packageEntity">
+ <package-title :package-entity="packageEntity">
<template #delete-button>
<gl-button
v-if="canDelete"
@@ -258,7 +288,7 @@ export default {
{{ __('Delete') }}
</gl-button>
</template>
- </component>
+ </package-title>
<gl-tabs>
<gl-tab :title="__('Detail')">
@@ -270,13 +300,12 @@ export default {
<additional-metadata :package-entity="packageEntity" />
</div>
- <!-- <package-files
+ <package-files
v-if="showFiles"
:package-files="packageFiles"
- :can-delete="canDelete"
@download-file="track($options.trackingActions.PULL_PACKAGE)"
@delete-file="handleFileDelete"
- /> -->
+ />
</gl-tab>
<gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
@@ -288,11 +317,11 @@ export default {
</template>
<template v-if="packageDependencies.length > 0">
- <dependency-row
+ <!-- <dependency-row
v-for="(dep, index) in packageDependencies"
:key="index"
:dependency="dep"
- />
+ /> -->
</template>
<p v-else class="gl-mt-3" data-testid="no-dependencies-message">
@@ -329,6 +358,7 @@ export default {
<gl-modal
ref="deleteModal"
modal-id="delete-modal"
+ data-testid="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@primary="confirmPackageDeletion"
@@ -351,6 +381,7 @@ export default {
modal-id="delete-file-modal"
:action-primary="$options.modal.fileDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
+ data-testid="delete-file-modal"
@primary="confirmFileDelete"
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)"
>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
index 3c2876e902b..bf7fe6fb91b 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
@@ -22,17 +22,13 @@ export default {
FileSha,
},
mixins: [Tracking.mixin()],
+ inject: ['canDelete'],
props: {
packageFiles: {
type: Array,
required: false,
default: () => [],
},
- canDelete: {
- type: Boolean,
- default: false,
- required: false,
- },
},
computed: {
filesTableRows() {
@@ -43,6 +39,8 @@ export default {
}));
},
showCommitColumn() {
+ // note that this is always false for now since we do not return
+ // pipelines associated to files for performance concerns
return this.filesTableRows.some((row) => Boolean(row.pipeline?.id));
},
filesTableHeaderFields() {
@@ -80,7 +78,7 @@ export default {
return numberToHumanSize(size);
},
hasDetails(item) {
- return item.file_sha256 || item.file_md5 || item.file_sha1;
+ return item.fileSha256 || item.fileMd5 || item.fileSha1;
},
},
i18n: {
@@ -107,32 +105,32 @@ export default {
@click="toggleDetails"
/>
<gl-link
- :href="item.download_path"
+ :href="item.downloadPath"
class="gl-text-gray-500"
data-testid="download-link"
@click="$emit('download-file')"
>
<file-icon
- :file-name="item.file_name"
+ :file-name="item.fileName"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
- <span>{{ item.file_name }}</span>
+ <span>{{ item.fileName }}</span>
</gl-link>
</template>
<template #cell(commit)="{ item }">
<gl-link
- v-if="item.pipeline && item.pipeline.project"
- :href="item.pipeline.project.commit_url"
+ v-if="item.pipeline && item.pipeline"
+ :href="item.pipeline.commitPath"
class="gl-text-gray-500"
data-testid="commit-link"
- >{{ item.pipeline.git_commit_message }}</gl-link
+ >{{ item.pipeline.sha }}</gl-link
>
</template>
<template #cell(created)="{ item }">
- <time-ago-tooltip :time="item.created_at" />
+ <time-ago-tooltip :time="item.createdAt" />
</template>
<template #cell(actions)="{ item }">
@@ -151,13 +149,13 @@ export default {
class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100"
>
<file-sha
- v-if="item.file_sha256"
+ v-if="item.fileSha256"
data-testid="sha-256"
title="SHA-256"
- :sha="item.file_sha256"
+ :sha="item.fileSha256"
/>
- <file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" />
- <file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" />
+ <file-sha v-if="item.fileMd5" data-testid="md5" title="MD5" :sha="item.fileMd5" />
+ <file-sha v-if="item.fileSha1" data-testid="sha-1" title="SHA-1" :sha="item.fileSha1" />
</div>
</template>
</gl-table>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql
new file mode 100644
index 00000000000..f016640f57d
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql
@@ -0,0 +1,5 @@
+mutation destroyPackageFile($id: PackagesPackageFileID!) {
+ destroyPackageFile(input: { id: $id }) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index 2dff256525a..6deb406f0db 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -38,6 +38,8 @@ query getPackageDetails($id: ID!) {
fileSha1
fileSha256
size
+ createdAt
+ downloadPath
}
}
metadata {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
index f25b01f88d5..d94bbd21035 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
@@ -18,7 +18,6 @@ export default () => {
apolloProvider,
provide: {
canDelete: parseBoolean(canDelete),
- titleComponent: 'PackageTitle',
...datasetOptions,
},
render(createElement) {
diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb
index 08cd451db94..5516e6bc2c0 100644
--- a/app/models/integrations/datadog.rb
+++ b/app/models/integrations/datadog.rb
@@ -2,6 +2,7 @@
module Integrations
class Datadog < Integration
+ include ActionView::Helpers::UrlHelper
include HasWebHook
extend Gitlab::Utils::Override
@@ -47,11 +48,12 @@ module Integrations
end
def description
- 'Trace your GitLab pipelines with Datadog'
+ s_('DatadogIntegration|Trace your GitLab pipelines with Datadog.')
end
def help
- nil
+ docs_link = link_to s_('DatadogIntegration|How do I set up this integration?'), Rails.application.routes.url_helpers.help_page_url('integration/datadog'), target: '_blank', rel: 'noopener noreferrer'
+ s_('DatadogIntegration|Send CI/CD pipeline information to Datadog to monitor for job failures and troubleshoot performance issues. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
@@ -64,14 +66,19 @@ module Integrations
type: 'text',
name: 'datadog_site',
placeholder: DEFAULT_DOMAIN,
- help: 'Choose the Datadog site to send data to. Set to "datadoghq.eu" to send data to the EU site',
+ help: ERB::Util.html_escape(
+ s_('DatadogIntegration|The Datadog site to send data to. To send data to the EU site, use %{codeOpen}datadoghq.eu%{codeClose}.')
+ ) % {
+ codeOpen: '<code>'.html_safe,
+ codeClose: '</code>'.html_safe
+ },
required: false
},
{
type: 'text',
name: 'api_url',
- title: 'API URL',
- help: '(Advanced) Define the full URL for your Datadog site directly',
+ title: s_('DatadogIntegration|API URL'),
+ help: s_('DatadogIntegration|(Advanced) The full URL for your Datadog site.'),
required: false
},
{
@@ -80,21 +87,34 @@ module Integrations
title: _('API key'),
non_empty_password_title: s_('ProjectService|Enter new API key'),
non_empty_password_help: s_('ProjectService|Leave blank to use your current API key'),
- help: "<a href=\"#{api_keys_url}\" target=\"_blank\">API key</a> used for authentication with Datadog",
+ help: ERB::Util.html_escape(
+ s_('DatadogIntegration|%{linkOpen}API key%{linkClose} used for authentication with Datadog.')
+ ) % {
+ linkOpen: '<a href="%s" target="_blank" rel="noopener noreferrer">'.html_safe % api_keys_url,
+ linkClose: '</a>'.html_safe
+ },
required: true
},
{
type: 'text',
name: 'datadog_service',
- title: 'Service',
+ title: s_('DatadogIntegration|Service'),
placeholder: 'gitlab-ci',
- help: 'Name of this GitLab instance that all data will be tagged with'
+ help: s_('DatadogIntegration|Tag all data from this GitLab instance in Datadog. Useful when managing several self-managed deployments.')
},
{
type: 'text',
name: 'datadog_env',
- title: 'Env',
- help: 'The environment tag that traces will be tagged with'
+ title: s_('DatadogIntegration|Environment'),
+ placeholder: 'ci',
+ help: ERB::Util.html_escape(
+ s_('DatadogIntegration|For self-managed deployments, set the %{codeOpen}env%{codeClose} tag for all the data sent to Datadog. %{linkOpen}How do I use tags?%{linkClose}')
+ ) % {
+ codeOpen: '<code>'.html_safe,
+ codeClose: '</code>'.html_safe,
+ linkOpen: '<a href="https://docs.datadoghq.com/getting_started/tagging/#using-tags" target="_blank" rel="noopener noreferrer">'.html_safe,
+ linkClose: '</a>'.html_safe
+ }
}
]
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 0a0f4b1e273..e003a5d15db 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -393,11 +393,6 @@ class Member < ApplicationRecord
# error or not doing any meaningful work.
# rubocop: disable CodeReuse/ServiceClass
def refresh_member_authorized_projects
- # If user/source is being destroyed, project access are going to be
- # destroyed eventually because of DB foreign keys, so we shouldn't bother
- # with refreshing after each member is destroyed through association
- return if destroyed_by_association.present?
-
UserProjectAccessChangedService.new(user_id).execute
end
# rubocop: enable CodeReuse/ServiceClass
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index ab044b80133..a13133c90e9 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class GroupMember < Member
+ extend ::Gitlab::Utils::Override
include FromUnion
include CreatedAtFilterable
@@ -49,6 +50,19 @@ class GroupMember < Member
{ group: group }
end
+ override :refresh_member_authorized_projects
+ def refresh_member_authorized_projects
+ # Here, `destroyed_by_association` will be present if the
+ # GroupMember is being destroyed due to the `dependent: :destroy`
+ # callback on Group. In this case, there is no need to refresh the
+ # authorizations, because whenever a Group is being destroyed,
+ # its projects are also destroyed, so the removal of project_authorizations
+ # will happen behind the scenes via DB foreign keys anyway.
+ return if destroyed_by_association.present?
+
+ super
+ end
+
private
def access_level_inclusion
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 8ea12a043d4..49144d9dbf3 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class ProjectMember < Member
+ extend ::Gitlab::Utils::Override
SOURCE_TYPE = 'Project'
belongs_to :project, foreign_key: 'source_id'
@@ -89,6 +90,22 @@ class ProjectMember < Member
{ project: project }
end
+ override :refresh_member_authorized_projects
+ def refresh_member_authorized_projects
+ return super unless Feature.enabled?(:specialized_service_for_project_member_auth_refresh)
+ return unless user
+
+ # rubocop:disable CodeReuse/ServiceClass
+ AuthorizedProjectUpdate::ProjectRecalculatePerUserService.new(project, user).execute
+
+ # Until we compare the inconsistency rates of the new, specialized service and
+ # the old approach, we still run AuthorizedProjectsWorker
+ # but with some delay and lower urgency as a safety net.
+ UserProjectAccessChangedService.new(user_id)
+ .execute(blocking: false, priority: UserProjectAccessChangedService::LOW_PRIORITY)
+ # rubocop:enable CodeReuse/ServiceClass
+ end
+
private
def send_invite
diff --git a/app/services/ci/drop_pipeline_service.rb b/app/services/ci/drop_pipeline_service.rb
index f510943251b..16d3abcbfa0 100644
--- a/app/services/ci/drop_pipeline_service.rb
+++ b/app/services/ci/drop_pipeline_service.rb
@@ -2,6 +2,9 @@
module Ci
class DropPipelineService
+ PRELOADED_COMMIT_STATUS_RELATIONS = [:project, :pipeline].freeze
+ PRELOADED_CI_BUILD_RELATIONS = [:metadata, :deployment, :taggings].freeze
+
# execute service asynchronously for each cancelable pipeline
def execute_async_for_all(pipelines, failure_reason, context_user)
pipelines.cancelable.select(:id).find_in_batches do |pipelines_batch|
@@ -27,11 +30,11 @@ module Ci
private
- def preload_associations_for_drop(builds_batch)
- ActiveRecord::Associations::Preloader.new.preload( # rubocop: disable CodeReuse/ActiveRecord
- builds_batch,
- [:project, :pipeline, :metadata, :deployment, :taggings]
- )
+ # rubocop: disable CodeReuse/ActiveRecord
+ def preload_associations_for_drop(commit_status_batch)
+ ActiveRecord::Associations::Preloader.new.preload(commit_status_batch, PRELOADED_COMMIT_STATUS_RELATIONS)
+ ActiveRecord::Associations::Preloader.new.preload(commit_status_batch.select { |job| job.is_a?(Ci::Build) }, PRELOADED_CI_BUILD_RELATIONS)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index a810993f622..302c047a65f 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -65,7 +65,7 @@ module Projects
save_project_and_import_data
- Gitlab::ApplicationContext.with_context(related_class: "Projects::CreateService", project: @project) do
+ Gitlab::ApplicationContext.with_context(project: @project) do
after_create_actions if @project.persisted?
import_schedule