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-12-03 15:10:23 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-03 15:10:23 +0300
commit5f0d27d131aced1a53e8cbc7db023d9f947f8a1a (patch)
tree7007c07fc37c95638f3e71c1902dcd055db1d8ca /app
parentcc8ea69201e2e4d020018c43efeb993c44cd8a71 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/crm/components/contacts_root.vue67
-rw-r--r--app/assets/javascripts/crm/components/new_contact_form.vue140
-rw-r--r--app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql10
-rw-r--r--app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql14
-rw-r--r--app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql15
-rw-r--r--app/assets/javascripts/crm/contacts_bundle.js20
-rw-r--r--app/assets/javascripts/editor/extensions/example_source_editor_extension.js4
-rw-r--r--app/assets/javascripts/editor/source_editor_instance.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue8
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js42
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/controllers/concerns/cycle_analytics_params.rb1
-rw-r--r--app/controllers/groups/crm/contacts_controller.rb17
-rw-r--r--app/controllers/groups/crm/organizations_controller.rb13
-rw-r--r--app/controllers/groups/crm_controller.rb30
-rw-r--r--app/controllers/groups/variables_controller.rb2
-rw-r--r--app/controllers/projects/ci/lints_controller.rb1
-rw-r--r--app/controllers/projects/ci/pipeline_editor_controller.rb2
-rw-r--r--app/controllers/projects/variables_controller.rb2
-rw-r--r--app/finders/group_descendants_finder.rb18
-rw-r--r--app/models/bulk_imports/entity.rb4
-rw-r--r--app/models/bulk_imports/file_transfer/base_config.rb4
-rw-r--r--app/models/bulk_imports/file_transfer/project_config.rb6
-rw-r--r--app/models/ci/pipeline.rb7
-rw-r--r--app/models/commit_status.rb13
-rw-r--r--app/services/audit_event_service.rb11
-rw-r--r--app/services/bulk_imports/uploads_export_service.rb3
-rw-r--r--app/services/ci/expire_pipeline_cache_service.rb23
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service.rb4
-rw-r--r--app/services/concerns/audit_event_save_type.rb26
-rw-r--r--app/services/merge_requests/after_create_service.rb6
-rw-r--r--app/views/groups/crm/contacts.html.haml4
-rw-r--r--app/views/groups/crm/contacts/index.html.haml4
-rw-r--r--app/views/groups/crm/organizations/index.html.haml (renamed from app/views/groups/crm/organizations.html.haml)0
-rw-r--r--app/views/projects/blob/show.html.haml4
-rw-r--r--app/workers/expire_job_cache_worker.rb15
36 files changed, 426 insertions, 118 deletions
diff --git a/app/assets/javascripts/crm/components/contacts_root.vue b/app/assets/javascripts/crm/components/contacts_root.vue
index 97220a3409d..0242bdab541 100644
--- a/app/assets/javascripts/crm/components/contacts_root.vue
+++ b/app/assets/javascripts/crm/components/contacts_root.vue
@@ -1,22 +1,28 @@
<script>
-import { GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
+import NewContactForm from './new_contact_form.vue';
export default {
components: {
+ GlAlert,
GlButton,
GlLoadingIcon,
GlTable,
+ NewContactForm,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- inject: ['groupFullPath', 'groupIssuesPath'],
+ inject: ['groupFullPath', 'groupIssuesPath', 'canAdminCrmContact'],
data() {
- return { contacts: [] };
+ return {
+ contacts: [],
+ error: false,
+ errorMessages: [],
+ };
},
apollo: {
contacts: {
@@ -31,12 +37,8 @@ export default {
update(data) {
return this.extractContacts(data);
},
- error(error) {
- createFlash({
- message: __('Something went wrong. Please try again.'),
- error,
- captureError: true,
- });
+ error() {
+ this.error = true;
},
},
},
@@ -44,12 +46,31 @@ export default {
isLoading() {
return this.$apollo.queries.contacts.loading;
},
+ showNewForm() {
+ return this.$route.path.startsWith('/new');
+ },
},
methods: {
extractContacts(data) {
const contacts = data?.group?.contacts?.nodes || [];
return contacts.slice().sort((a, b) => a.firstName.localeCompare(b.firstName));
},
+ displayNewForm() {
+ if (this.showNewForm) return;
+
+ this.$router.push({ path: '/new' });
+ },
+ hideNewForm() {
+ this.$router.replace({ path: '/' });
+ },
+ handleError(errors) {
+ this.error = true;
+ if (errors) this.errorMessages = errors;
+ },
+ dismissError() {
+ this.error = false;
+ this.errorMessages = [];
+ },
},
fields: [
{ key: 'firstName', sortable: true },
@@ -75,15 +96,41 @@ export default {
i18n: {
emptyText: s__('Crm|No contacts found'),
issuesButtonLabel: __('View issues'),
+ title: s__('Crm|Customer Relations Contacts'),
+ newContact: s__('Crm|New contact'),
+ errorText: __('Something went wrong. Please try again.'),
},
};
</script>
<template>
<div>
+ <gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="dismissError">
+ <div v-if="errorMessages.length == 0">{{ $options.i18n.errorText }}</div>
+ <div v-for="(message, index) in errorMessages" :key="index">{{ message }}</div>
+ </gl-alert>
+ <div
+ class="gl-display-flex gl-align-items-baseline gl-flex-direction-row gl-justify-content-space-between gl-mt-6"
+ >
+ <h2 class="gl-font-size-h2 gl-my-0">
+ {{ $options.i18n.title }}
+ </h2>
+ <div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end">
+ <gl-button
+ v-if="canAdminCrmContact"
+ variant="confirm"
+ data-testid="new-contact-button"
+ @click="displayNewForm"
+ >
+ {{ $options.i18n.newContact }}
+ </gl-button>
+ </div>
+ </div>
+ <new-contact-form v-if="showNewForm" @close="hideNewForm" @error="handleError" />
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" />
<gl-table
v-else
+ class="gl-mt-5"
:items="contacts"
:fields="$options.fields"
:empty-text="$options.i18n.emptyText"
diff --git a/app/assets/javascripts/crm/components/new_contact_form.vue b/app/assets/javascripts/crm/components/new_contact_form.vue
new file mode 100644
index 00000000000..77ff82c5af4
--- /dev/null
+++ b/app/assets/javascripts/crm/components/new_contact_form.vue
@@ -0,0 +1,140 @@
+<script>
+import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { produce } from 'immer';
+import { __, s__ } from '~/locale';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPE_GROUP } from '~/graphql_shared/constants';
+import createContact from './queries/create_contact.mutation.graphql';
+import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
+
+export default {
+ components: {
+ GlButton,
+ GlFormGroup,
+ GlFormInput,
+ },
+ inject: ['groupFullPath', 'groupId'],
+ data() {
+ return {
+ firstName: '',
+ lastName: '',
+ phone: '',
+ email: '',
+ description: '',
+ submitting: false,
+ };
+ },
+ computed: {
+ invalid() {
+ return this.firstName === '' || this.lastName === '' || this.email === '';
+ },
+ },
+ methods: {
+ save() {
+ this.submitting = true;
+ return this.$apollo
+ .mutate({
+ mutation: createContact,
+ variables: {
+ input: {
+ groupId: convertToGraphQLId(TYPE_GROUP, this.groupId),
+ firstName: this.firstName,
+ lastName: this.lastName,
+ phone: this.phone,
+ email: this.email,
+ description: this.description,
+ },
+ },
+ update: this.updateCache,
+ })
+ .then(({ data }) => {
+ if (data.customerRelationsContactCreate.errors.length === 0) this.close();
+
+ this.submitting = false;
+ })
+ .catch(() => {
+ this.error();
+ this.submitting = false;
+ });
+ },
+ close() {
+ this.$emit('close');
+ },
+ error(errors = null) {
+ this.$emit('error', errors);
+ },
+ updateCache(store, { data: { customerRelationsContactCreate } }) {
+ if (customerRelationsContactCreate.errors.length > 0) {
+ this.error(customerRelationsContactCreate.errors);
+ return;
+ }
+
+ const variables = {
+ groupFullPath: this.groupFullPath,
+ };
+ const sourceData = store.readQuery({
+ query: getGroupContactsQuery,
+ variables,
+ });
+
+ const data = produce(sourceData, (draftState) => {
+ draftState.group.contacts.nodes = [
+ ...sourceData.group.contacts.nodes,
+ customerRelationsContactCreate.contact,
+ ];
+ });
+
+ store.writeQuery({
+ query: getGroupContactsQuery,
+ variables,
+ data,
+ });
+ },
+ },
+ i18n: {
+ buttonLabel: s__('Crm|Create new contact'),
+ cancel: __('Cancel'),
+ firstName: s__('Crm|First name'),
+ lastName: s__('Crm|Last name'),
+ email: s__('Crm|Email'),
+ phone: s__('Crm|Phone number (optional)'),
+ description: s__('Crm|Description (optional)'),
+ },
+};
+</script>
+
+<template>
+ <div class="col-md-4">
+ <form @submit.prevent="save">
+ <gl-form-group :label="$options.i18n.firstName" label-for="contact-first-name">
+ <gl-form-input id="contact-first-name" v-model="firstName" />
+ </gl-form-group>
+ <gl-form-group :label="$options.i18n.lastName" label-for="contact-last-name">
+ <gl-form-input id="contact-last-name" v-model="lastName" />
+ </gl-form-group>
+ <gl-form-group :label="$options.i18n.email" label-for="contact-email">
+ <gl-form-input id="contact-email" v-model="email" />
+ </gl-form-group>
+ <gl-form-group :label="$options.i18n.phone" label-for="contact-phone">
+ <gl-form-input id="contact-phone" v-model="phone" />
+ </gl-form-group>
+ <gl-form-group :label="$options.i18n.description" label-for="contact-description">
+ <gl-form-input id="contact-description" v-model="description" />
+ </gl-form-group>
+ <div class="form-actions">
+ <gl-button
+ variant="confirm"
+ :disabled="invalid"
+ :loading="submitting"
+ data-testid="create-new-contact-button"
+ type="submit"
+ >{{ $options.i18n.buttonLabel }}</gl-button
+ >
+ <gl-button data-testid="cancel-button" @click="close">
+ {{ $options.i18n.cancel }}
+ </gl-button>
+ </div>
+ </form>
+ <div class="gl-pb-5"></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql b/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql
new file mode 100644
index 00000000000..e0192459609
--- /dev/null
+++ b/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql
@@ -0,0 +1,10 @@
+#import "./crm_contact_fields.fragment.graphql"
+
+mutation createContact($input: CustomerRelationsContactCreateInput!) {
+ customerRelationsContactCreate(input: $input) {
+ contact {
+ ...ContactFragment
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql b/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql
new file mode 100644
index 00000000000..cef4083b446
--- /dev/null
+++ b/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql
@@ -0,0 +1,14 @@
+fragment ContactFragment on CustomerRelationsContact {
+ __typename
+ id
+ firstName
+ lastName
+ email
+ phone
+ description
+ organization {
+ __typename
+ id
+ name
+ }
+}
diff --git a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql b/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql
index f6acd258585..2a8150e42e3 100644
--- a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql
+++ b/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql
@@ -1,21 +1,12 @@
+#import "./crm_contact_fields.fragment.graphql"
+
query contacts($groupFullPath: ID!) {
group(fullPath: $groupFullPath) {
__typename
id
contacts {
nodes {
- __typename
- id
- firstName
- lastName
- email
- phone
- description
- organization {
- __typename
- id
- name
- }
+ ...ContactFragment
}
}
}
diff --git a/app/assets/javascripts/crm/contacts_bundle.js b/app/assets/javascripts/crm/contacts_bundle.js
index b0edd0107b6..6ddc53840cc 100644
--- a/app/assets/javascripts/crm/contacts_bundle.js
+++ b/app/assets/javascripts/crm/contacts_bundle.js
@@ -1,9 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import VueRouter from 'vue-router';
import createDefaultClient from '~/lib/graphql';
import CrmContactsRoot from './components/contacts_root.vue';
Vue.use(VueApollo);
+Vue.use(VueRouter);
export default () => {
const el = document.getElementById('js-crm-contacts-app');
@@ -16,12 +18,26 @@ export default () => {
return false;
}
- const { groupFullPath, groupIssuesPath } = el.dataset;
+ const { basePath, groupFullPath, groupIssuesPath, canAdminCrmContact, groupId } = el.dataset;
+
+ const router = new VueRouter({
+ base: basePath,
+ mode: 'history',
+ routes: [
+ {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ name: 'Contacts List',
+ path: '/',
+ component: CrmContactsRoot,
+ },
+ ],
+ });
return new Vue({
el,
+ router,
apolloProvider,
- provide: { groupFullPath, groupIssuesPath },
+ provide: { groupFullPath, groupIssuesPath, canAdminCrmContact, groupId },
render(createElement) {
return createElement(CrmContactsRoot);
},
diff --git a/app/assets/javascripts/editor/extensions/example_source_editor_extension.js b/app/assets/javascripts/editor/extensions/example_source_editor_extension.js
index 119a2aea9eb..33be6cf9e5d 100644
--- a/app/assets/javascripts/editor/extensions/example_source_editor_extension.js
+++ b/app/assets/javascripts/editor/extensions/example_source_editor_extension.js
@@ -16,11 +16,11 @@ export class MyFancyExtension {
* actions, keystrokes, update options, etc.
* Is called only once before the extension gets registered
*
- * @param { Object } [setupOptions] The setupOptions object
* @param { Object } [instance] The Source Editor instance
+ * @param { Object } [setupOptions] The setupOptions object
*/
// eslint-disable-next-line class-methods-use-this,no-unused-vars
- onSetup(setupOptions, instance) {}
+ onSetup(instance, setupOptions) {}
/**
* The first thing called after the extension is
diff --git a/app/assets/javascripts/editor/source_editor_instance.js b/app/assets/javascripts/editor/source_editor_instance.js
index 052a73d7091..fcffdc587be 100644
--- a/app/assets/javascripts/editor/source_editor_instance.js
+++ b/app/assets/javascripts/editor/source_editor_instance.js
@@ -153,7 +153,7 @@ export default class EditorInstance {
const extensionInstance = new EditorExtension(extension);
const { setupOptions, obj: extensionObj } = extensionInstance;
if (extensionObj.onSetup) {
- extensionObj.onSetup(setupOptions, this);
+ extensionObj.onSetup(this, setupOptions);
}
if (extensionsStore) {
this.registerExtension(extensionInstance, extensionsStore);
diff --git a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue
index 7f6dce05b6e..13e254f138a 100644
--- a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue
+++ b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue
@@ -1,5 +1,5 @@
<script>
-import { GlAlert, GlLink, GlSprintf, GlTable } from '@gitlab/ui';
+import { GlAlert, GlLink, GlSprintf, GlTableLite } from '@gitlab/ui';
import { __ } from '~/locale';
import CiLintResultsParam from './ci_lint_results_param.vue';
import CiLintResultsValue from './ci_lint_results_value.vue';
@@ -36,7 +36,7 @@ export default {
GlAlert,
GlLink,
GlSprintf,
- GlTable,
+ GlTableLite,
CiLintWarnings,
CiLintResultsValue,
CiLintResultsParam,
@@ -129,7 +129,7 @@ export default {
@dismiss="isWarningDismissed = true"
/>
- <gl-table
+ <gl-table-lite
v-if="shouldShowTable"
:items="jobs"
:fields="$options.fields"
@@ -142,6 +142,6 @@ export default {
<template #cell(value)="{ item }">
<ci-lint-results-value :item="item" :dry-run="dryRun" />
</template>
- </gl-table>
+ </gl-table-lite>
</div>
</template>
diff --git a/app/assets/javascripts/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index 321315d531b..4f3f1365f4a 100644
--- a/app/assets/javascripts/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -122,7 +122,6 @@ export default function simulateDrag(options) {
const firstRect = getRect(firstEl);
const lastRect = getRect(lastEl);
- const startTime = new Date().getTime();
const duration = options.duration || 1000;
simulateEvent(fromEl, 'pointerdown', {
@@ -140,8 +139,28 @@ export default function simulateDrag(options) {
toRect.cy = lastRect.y + lastRect.h + 50;
}
- const dragInterval = setInterval(() => {
- const progress = (new Date().getTime() - startTime) / duration;
+ let startTime;
+
+ // Called within dragFn when the drag should finish
+ const finishFn = () => {
+ if (options.ondragend) options.ondragend();
+
+ if (options.performDrop) {
+ simulateEvent(toEl, 'mouseup');
+ }
+
+ window.SIMULATE_DRAG_ACTIVE = 0;
+ };
+
+ const dragFn = (timestamp) => {
+ if (!startTime) {
+ startTime = timestamp;
+ }
+
+ const elapsed = timestamp - startTime;
+
+ // Make sure that progress maxes at 1
+ const progress = Math.min(elapsed / duration, 1);
const x = fromRect.cx + (toRect.cx - fromRect.cx) * progress;
const y = fromRect.cy + (toRect.cy - fromRect.cy + options.extraHeight) * progress;
const overEl = fromEl.ownerDocument.elementFromPoint(x, y);
@@ -152,16 +171,15 @@ export default function simulateDrag(options) {
});
if (progress >= 1) {
- if (options.ondragend) options.ondragend();
-
- if (options.performDrop) {
- simulateEvent(toEl, 'mouseup');
- }
-
- clearInterval(dragInterval);
- window.SIMULATE_DRAG_ACTIVE = 0;
+ // finish on next frame, so we can pause in the correct position for a frame
+ requestAnimationFrame(finishFn);
+ } else {
+ requestAnimationFrame(dragFn);
}
- }, 100);
+ };
+
+ // Start the drag animation
+ requestAnimationFrame(dragFn);
return {
target: fromEl,
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 8877cfa39fb..1963d1aa7fe 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -141,6 +141,7 @@ export default {
variant="link"
:icon="descriptionVersionToggleIcon"
data-testid="compare-btn"
+ class="gl-vertical-align-text-bottom"
@click="toggleDescriptionVersion"
>{{ __('Compare with previous version') }}</gl-button
>
@@ -149,6 +150,7 @@ export default {
:icon="showLines ? 'chevron-up' : 'chevron-down'"
variant="link"
data-testid="outdated-lines-change-btn"
+ class="gl-vertical-align-text-bottom"
@click="toggleDiff"
>
{{ __('Compare changes') }}
diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb
index 626093b4588..70bcefe339c 100644
--- a/app/controllers/concerns/cycle_analytics_params.rb
+++ b/app/controllers/concerns/cycle_analytics_params.rb
@@ -23,6 +23,7 @@ module CycleAnalyticsParams
opts[:from] = params[:from] || start_date(params)
opts[:to] = params[:to] if params[:to]
opts[:end_event_filter] = params[:end_event_filter] if params[:end_event_filter]
+ opts[:use_aggregated_data_collector] = params[:use_aggregated_data_collector] if params[:use_aggregated_data_collector]
opts.merge!(params.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES))
opts.merge!(date_range(params))
end
diff --git a/app/controllers/groups/crm/contacts_controller.rb b/app/controllers/groups/crm/contacts_controller.rb
new file mode 100644
index 00000000000..97904fdd2fd
--- /dev/null
+++ b/app/controllers/groups/crm/contacts_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class Groups::Crm::ContactsController < Groups::ApplicationController
+ feature_category :team_planning
+
+ before_action :authorize_read_crm_contact!
+
+ def new
+ render action: "index"
+ end
+
+ private
+
+ def authorize_read_crm_contact!
+ render_404 unless can?(current_user, :read_crm_contact, group)
+ end
+end
diff --git a/app/controllers/groups/crm/organizations_controller.rb b/app/controllers/groups/crm/organizations_controller.rb
new file mode 100644
index 00000000000..6f285687e6b
--- /dev/null
+++ b/app/controllers/groups/crm/organizations_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Groups::Crm::OrganizationsController < Groups::ApplicationController
+ feature_category :team_planning
+
+ before_action :authorize_read_crm_organization!
+
+ private
+
+ def authorize_read_crm_organization!
+ render_404 unless can?(current_user, :read_crm_organization, group)
+ end
+end
diff --git a/app/controllers/groups/crm_controller.rb b/app/controllers/groups/crm_controller.rb
deleted file mode 100644
index 40661b09be6..00000000000
--- a/app/controllers/groups/crm_controller.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-class Groups::CrmController < Groups::ApplicationController
- feature_category :team_planning
-
- before_action :authorize_read_crm_contact!, only: [:contacts]
- before_action :authorize_read_crm_organization!, only: [:organizations]
-
- def contacts
- respond_to do |format|
- format.html
- end
- end
-
- def organizations
- respond_to do |format|
- format.html
- end
- end
-
- private
-
- def authorize_read_crm_contact!
- render_404 unless can?(current_user, :read_crm_contact, group)
- end
-
- def authorize_read_crm_organization!
- render_404 unless can?(current_user, :read_crm_organization, group)
- end
-end
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
index 9dbbd385ea8..1e23db9f32b 100644
--- a/app/controllers/groups/variables_controller.rb
+++ b/app/controllers/groups/variables_controller.rb
@@ -8,6 +8,8 @@ module Groups
feature_category :pipeline_authoring
+ urgency :low, [:show]
+
def show
respond_to do |format|
format.json do
diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb
index 9dc3194df85..7ef5016ac00 100644
--- a/app/controllers/projects/ci/lints_controller.rb
+++ b/app/controllers/projects/ci/lints_controller.rb
@@ -6,6 +6,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController
feature_category :pipeline_authoring
respond_to :json, only: [:create]
+ urgency :low, [:create]
def show
end
diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb
index 600516f95a2..392a6afc636 100644
--- a/app/controllers/projects/ci/pipeline_editor_controller.rb
+++ b/app/controllers/projects/ci/pipeline_editor_controller.rb
@@ -9,6 +9,8 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
feature_category :pipeline_authoring
+ urgency :low, [:show]
+
def show
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index f93c75a203e..e7bccf5a243 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -5,6 +5,8 @@ class Projects::VariablesController < Projects::ApplicationController
feature_category :pipeline_authoring
+ urgency :low, [:show, :update]
+
def show
respond_to do |format|
format.json do
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 18ccea330af..7974710e67b 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -87,9 +87,13 @@ class GroupDescendantsFinder
visible_to_user = visible_to_user.or(authorized_to_user)
end
- hierarchy_for_parent
- .descendants
- .where(visible_to_user)
+ group_to_query = if Feature.enabled?(:linear_group_descendants_finder, current_user, default_enabled: :yaml)
+ parent_group
+ else
+ hierarchy_for_parent
+ end
+
+ group_to_query.descendants.where(visible_to_user)
# rubocop: enable CodeReuse/Finder
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -155,7 +159,13 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
def projects_matching_filter
# rubocop: disable CodeReuse/Finder
- projects_nested_in_group = Project.where(namespace_id: hierarchy_for_parent.base_and_descendants.select(:id))
+ objects_in_hierarchy = if Feature.enabled?(:linear_group_descendants_finder, current_user, default_enabled: :yaml)
+ parent_group.self_and_descendants.as_ids
+ else
+ hierarchy_for_parent.base_and_descendants.select(:id)
+ end
+
+ projects_nested_in_group = Project.where(namespace_id: objects_in_hierarchy)
params_with_search = params.merge(search: params[:filter])
ProjectsFinder.new(params: params_with_search,
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index 33ce4686e27..38b7da76306 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -134,6 +134,10 @@ class BulkImports::Entity < ApplicationRecord
source_type == 'group_entity'
end
+ def update_service
+ "::#{pluralized_name.capitalize}::UpdateService".constantize
+ end
+
private
def validate_parent_is_a_group
diff --git a/app/models/bulk_imports/file_transfer/base_config.rb b/app/models/bulk_imports/file_transfer/base_config.rb
index 4d370315ad5..e735503a47f 100644
--- a/app/models/bulk_imports/file_transfer/base_config.rb
+++ b/app/models/bulk_imports/file_transfer/base_config.rb
@@ -5,6 +5,8 @@ module BulkImports
class BaseConfig
include Gitlab::Utils::StrongMemoize
+ UPLOADS_RELATION = 'uploads'
+
def initialize(portable)
@portable = portable
end
@@ -78,7 +80,7 @@ module BulkImports
end
def file_relations
- []
+ [UPLOADS_RELATION]
end
def skipped_relations
diff --git a/app/models/bulk_imports/file_transfer/project_config.rb b/app/models/bulk_imports/file_transfer/project_config.rb
index 9a0434da08a..fdfb0dd0186 100644
--- a/app/models/bulk_imports/file_transfer/project_config.rb
+++ b/app/models/bulk_imports/file_transfer/project_config.rb
@@ -3,8 +3,6 @@
module BulkImports
module FileTransfer
class ProjectConfig < BaseConfig
- UPLOADS_RELATION = 'uploads'
-
SKIPPED_RELATIONS = %w(
project_members
group_members
@@ -14,10 +12,6 @@ module BulkImports
::Gitlab::ImportExport.config_file
end
- def file_relations
- [UPLOADS_RELATION]
- end
-
def skipped_relations
SKIPPED_RELATIONS
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2bf33f821ab..a18b760eeb4 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -236,7 +236,12 @@ module Ci
pipeline.run_after_commit do
PipelineHooksWorker.perform_async(pipeline.id)
- ExpirePipelineCacheWorker.perform_async(pipeline.id)
+
+ if Feature.enabled?(:expire_job_and_pipeline_cache_synchronously, pipeline.project, default_enabled: :yaml)
+ Ci::ExpirePipelineCacheService.new.execute(pipeline) # rubocop: disable CodeReuse/ServiceClass
+ else
+ ExpirePipelineCacheWorker.perform_async(pipeline.id)
+ end
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 1bbcf8837f6..d80b2fe37dc 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -188,7 +188,12 @@ class CommitStatus < Ci::ApplicationRecord
commit_status.run_after_commit do
PipelineProcessWorker.perform_async(pipeline_id) unless transition_options[:skip_pipeline_processing]
- ExpireJobCacheWorker.perform_async(id)
+
+ if Feature.enabled?(:expire_job_and_pipeline_cache_synchronously, project, default_enabled: :yaml)
+ expire_etag_cache!
+ else
+ ExpireJobCacheWorker.perform_async(id)
+ end
end
end
@@ -301,6 +306,12 @@ class CommitStatus < Ci::ApplicationRecord
.update_all(retried: true, processed: true)
end
+ def expire_etag_cache!
+ job_path = Gitlab::Routing.url_helpers.project_build_path(project, id, format: :json)
+
+ Gitlab::EtagCaching::Store.new.touch(job_path)
+ end
+
private
def unrecoverable_failure?
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index 563d4a924fc..1426bf25a00 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class AuditEventService
+ include AuditEventSaveType
+
# Instantiates a new service
#
# @param [User] author the user who authors the change
@@ -10,13 +12,16 @@ class AuditEventService
# - Group: events are visible at Group and Instance level
# - User: events are visible at Instance level
# @param [Hash] details extra data of audit event
+ # @param [Symbol] save_type the type to save the event
+ # Can be selected from the following, :database, :stream, :database_and_stream .
#
# @return [AuditEventService]
- def initialize(author, entity, details = {})
+ def initialize(author, entity, details = {}, save_type = :database_and_stream)
@author = build_author(author)
@entity = entity
@details = details
@ip_address = resolve_ip_address(@author)
+ @save_type = save_type
end
# Builds the @details attribute for authentication
@@ -133,8 +138,8 @@ class AuditEventService
end
def save_or_track(event)
- event.save!
- stream_event_to_external_destinations(event)
+ event.save! if should_save_database?(@save_type)
+ stream_event_to_external_destinations(event) if should_save_stream?(@save_type)
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, audit_event_type: event.class.to_s)
end
diff --git a/app/services/bulk_imports/uploads_export_service.rb b/app/services/bulk_imports/uploads_export_service.rb
index 32cc48c152c..7f5ee7b8624 100644
--- a/app/services/bulk_imports/uploads_export_service.rb
+++ b/app/services/bulk_imports/uploads_export_service.rb
@@ -5,6 +5,7 @@ module BulkImports
include Gitlab::ImportExport::CommandLineUtil
BATCH_SIZE = 100
+ AVATAR_PATH = 'avatar'
def initialize(portable, export_path)
@portable = portable
@@ -34,7 +35,7 @@ module BulkImports
def export_subdir_path(upload)
subdir = if upload.path == avatar_path
- 'avatar'
+ AVATAR_PATH
else
upload.try(:secret).to_s
end
diff --git a/app/services/ci/expire_pipeline_cache_service.rb b/app/services/ci/expire_pipeline_cache_service.rb
index 177c85cebcc..8622b1a5863 100644
--- a/app/services/ci/expire_pipeline_cache_service.rb
+++ b/app/services/ci/expire_pipeline_cache_service.rb
@@ -74,20 +74,25 @@ module Ci
def update_etag_cache(pipeline, store)
project = pipeline.project
- store.touch(project_pipelines_path(project))
- store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
- store.touch(new_merge_request_pipelines_path(project))
+ etag_paths = [
+ project_pipelines_path(project),
+ new_merge_request_pipelines_path(project),
+ graphql_project_on_demand_scan_counts_path(project)
+ ]
+
+ etag_paths << commit_pipelines_path(project, pipeline.commit) unless pipeline.commit.nil?
+
each_pipelines_merge_request_path(pipeline) do |path|
- store.touch(path)
+ etag_paths << path
end
- pipeline.self_with_upstreams_and_downstreams.each do |relative_pipeline|
- store.touch(project_pipeline_path(relative_pipeline.project, relative_pipeline))
- store.touch(graphql_pipeline_path(relative_pipeline))
- store.touch(graphql_pipeline_sha_path(relative_pipeline.sha))
+ pipeline.self_with_upstreams_and_downstreams.includes(project: [:route, { namespace: :route }]).each do |relative_pipeline| # rubocop: disable CodeReuse/ActiveRecord
+ etag_paths << project_pipeline_path(relative_pipeline.project, relative_pipeline)
+ etag_paths << graphql_pipeline_path(relative_pipeline)
+ etag_paths << graphql_pipeline_sha_path(relative_pipeline.sha)
end
- store.touch(graphql_project_on_demand_scan_counts_path(project))
+ store.touch(*etag_paths)
end
def url_helpers
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb
index 236d660d829..d8ce063ffb4 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb
@@ -36,6 +36,10 @@ module Ci
update_pipeline!
update_statuses_processed!
+ if Feature.enabled?(:expire_job_and_pipeline_cache_synchronously, pipeline.project, default_enabled: :yaml)
+ Ci::ExpirePipelineCacheService.new.execute(pipeline)
+ end
+
true
end
diff --git a/app/services/concerns/audit_event_save_type.rb b/app/services/concerns/audit_event_save_type.rb
new file mode 100644
index 00000000000..6696e4adae7
--- /dev/null
+++ b/app/services/concerns/audit_event_save_type.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module AuditEventSaveType
+ SAVE_TYPES = {
+ database: 0b01,
+ stream: 0b10,
+ database_and_stream: 0b11
+ }.freeze
+
+ # def should_save_stream?(type)
+ # def should_save_database?(type)
+ [:database, :stream].each do |type|
+ define_method("should_save_#{type}?") do |param_type|
+ return false unless save_type_valid?(param_type)
+
+ # If the current type does not support query, the result of the `&` operation is 0 .
+ SAVE_TYPES[param_type] & SAVE_TYPES[type] != 0
+ end
+ end
+
+ private
+
+ def save_type_valid?(type)
+ SAVE_TYPES.key?(type)
+ end
+end
diff --git a/app/services/merge_requests/after_create_service.rb b/app/services/merge_requests/after_create_service.rb
index f120cb26d22..d2c83f82ff8 100644
--- a/app/services/merge_requests/after_create_service.rb
+++ b/app/services/merge_requests/after_create_service.rb
@@ -2,6 +2,8 @@
module MergeRequests
class AfterCreateService < MergeRequests::BaseService
+ include Gitlab::Utils::StrongMemoize
+
def execute(merge_request)
prepare_for_mergeability(merge_request) if early_prepare_for_mergeability?(merge_request)
prepare_merge_request(merge_request)
@@ -48,7 +50,9 @@ module MergeRequests
end
def early_prepare_for_mergeability?(merge_request)
- Feature.enabled?(:early_prepare_for_mergeability, merge_request.target_project)
+ strong_memoize("early_prepare_for_mergeability_#{merge_request.target_project_id}".to_sym) do
+ Feature.enabled?(:early_prepare_for_mergeability, merge_request.target_project)
+ end
end
def mark_as_unchecked(merge_request)
diff --git a/app/views/groups/crm/contacts.html.haml b/app/views/groups/crm/contacts.html.haml
deleted file mode 100644
index 7d0ee5b64b1..00000000000
--- a/app/views/groups/crm/contacts.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- breadcrumb_title _('Customer Relations Contacts')
-- page_title _('Customer Relations Contacts')
-
-#js-crm-contacts-app{ data: { group_full_path: @group.full_path, group_issues_path: issues_group_path(@group) } }
diff --git a/app/views/groups/crm/contacts/index.html.haml b/app/views/groups/crm/contacts/index.html.haml
new file mode 100644
index 00000000000..81293937f77
--- /dev/null
+++ b/app/views/groups/crm/contacts/index.html.haml
@@ -0,0 +1,4 @@
+- breadcrumb_title _('Customer Relations Contacts')
+- page_title _('Customer Relations Contacts')
+
+#js-crm-contacts-app{ data: { group_full_path: @group.full_path, group_issues_path: issues_group_path(@group), group_id: @group.id, can_admin_crm_contact: can?(current_user, :admin_crm_contact, @group).to_s, base_path: group_crm_contacts_path(@group) } }
diff --git a/app/views/groups/crm/organizations.html.haml b/app/views/groups/crm/organizations/index.html.haml
index e83dab9fda6..e83dab9fda6 100644
--- a/app/views/groups/crm/organizations.html.haml
+++ b/app/views/groups/crm/organizations/index.html.haml
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 168b240c657..d4e7ee90a84 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -14,8 +14,8 @@
- if can_modify_blob?(@blob)
= render 'projects/blob/remove'
- - title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
+ - title = _("Replace %{blob_name}") % { blob_name: @blob.name }
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: _('Replace file'), form_path: project_update_blob_path(@project, @id), method: :put
= render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration?
= render 'shared/web_ide_path'
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index 3c5a7717d70..49f0222e9c9 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -15,19 +15,10 @@ class ExpireJobCacheWorker # rubocop:disable Scalability/IdempotentWorker
idempotent!
def perform(job_id)
- job = CommitStatus.preload(:pipeline, :project).find_by_id(job_id) # rubocop: disable CodeReuse/ActiveRecord
+ job = CommitStatus.find_by_id(job_id)
return unless job
- pipeline = job.pipeline
- project = job.project
-
- Gitlab::EtagCaching::Store.new.touch(project_job_path(project, job))
- ExpirePipelineCacheWorker.perform_async(pipeline.id)
- end
-
- private
-
- def project_job_path(project, job)
- Gitlab::Routing.url_helpers.project_build_path(project, job.id, format: :json)
+ job.expire_etag_cache!
+ ExpirePipelineCacheWorker.perform_async(job.pipeline_id)
end
end