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
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/cache-repo.gitlab-ci.yml1
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue20
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue14
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue32
-rw-r--r--app/assets/javascripts/alerts_settings/graphql.js44
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql19
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/queries/get_current_integration.query.graphql3
-rw-r--r--app/assets/javascripts/alerts_settings/index.js18
-rw-r--r--app/assets/javascripts/groups/new_group_child.js65
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js5
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue2
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js4
-rw-r--r--app/assets/javascripts/registry/explorer/utils.js14
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue6
-rw-r--r--app/controllers/projects/releases_controller.rb2
-rw-r--r--app/graphql/mutations/commits/create.rb10
-rw-r--r--app/graphql/types/release_asset_link_type.rb6
-rw-r--r--app/models/bulk_imports/entity.rb4
-rw-r--r--app/models/bulk_imports/tracker.rb18
-rw-r--r--app/models/design_management/design.rb6
-rw-r--r--app/presenters/release_presenter.rb6
-rw-r--r--app/views/groups/_home_panel.html.haml39
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/buttons/_star.html.haml2
-rw-r--r--app/views/shared/notifications/_new_button.html.haml5
-rw-r--r--changelogs/unreleased/217553-permanent-links-to-release-assets-not-found-404.yml5
-rw-r--r--changelogs/unreleased/245263-mr-refs-cleanup-old-mrs.yml5
-rw-r--r--changelogs/unreleased/263144-commit-to-new-branch.yml5
-rw-r--r--changelogs/unreleased/267113-separate-new-group-project-creation-button.yml5
-rw-r--r--changelogs/unreleased/ab-add-missing-expression-indexes.yml5
-rw-r--r--changelogs/unreleased/add-nulls-last-to-db-index.yml5
-rw-r--r--changelogs/unreleased/kassio-bulkimport-entity-trackers.yml6
-rw-r--r--config/feature_flags/development/ci_lint_creates_pipeline_with_dry_run.yml8
-rw-r--r--config/feature_flags/development/design_management_design_notification_participants.yml8
-rw-r--r--db/migrate/20201104204739_create_bulk_import_trackers.rb42
-rw-r--r--db/migrate/20201110133629_change_index_mr_metrics_target_project_id.rb20
-rw-r--r--db/migrate/20201111152859_add_missing_expression_indexes.rb43
-rw-r--r--db/post_migrate/20201103110018_schedule_merge_request_cleanup_schedules_backfill.rb30
-rw-r--r--db/schema_migrations/202011031100181
-rw-r--r--db/schema_migrations/202011042047391
-rw-r--r--db/schema_migrations/202011101336291
-rw-r--r--db/schema_migrations/202011111528591
-rw-r--r--db/structure.sql48
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql7
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json12
-rw-r--r--doc/user/group/img/create_new_project_from_group_v13_6.pngbin0 -> 40062 bytes
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/group/subgroups/img/create_subgroup_button.pngbin11161 -> 0 bytes
-rw-r--r--doc/user/group/subgroups/img/create_subgroup_button_v13_6.pngbin0 -> 16989 bytes
-rw-r--r--doc/user/group/subgroups/index.md5
-rw-r--r--doc/user/profile/notifications.md28
-rw-r--r--doc/user/project/static_site_editor/index.md5
-rw-r--r--lib/api/entities/releases/link.rb6
-rw-r--r--lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb40
-rw-r--r--lib/gitlab/ci/features.rb4
-rw-r--r--lib/gitlab/ci/lint.rb2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/qa/page/group/show.rb37
-rw-r--r--qa/qa/resource/group.rb2
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb14
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb97
-rw-r--r--spec/factories/bulk_import/trackers.rb10
-rw-r--r--spec/features/groups/container_registry_spec.rb14
-rw-r--r--spec/features/groups/show_spec.rb10
-rw-r--r--spec/features/groups_spec.rb48
-rw-r--r--spec/features/projects/container_registry_spec.rb18
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb2
-rw-r--r--spec/frontend/alerts_settings/alerts_integrations_list_spec.js11
-rw-r--r--spec/frontend/alerts_settings/alerts_settings_form_new_spec.js6
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js2
-rw-r--r--spec/frontend/registry/explorer/stores/actions_spec.js11
-rw-r--r--spec/frontend/registry/explorer/utils_spec.js14
-rw-r--r--spec/frontend/releases/__snapshots__/util_spec.js.snap16
-rw-r--r--spec/graphql/mutations/commits/create_spec.rb29
-rw-r--r--spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb53
-rw-r--r--spec/migrations/schedule_merge_request_cleanup_schedules_backfill_spec.rb41
-rw-r--r--spec/models/bulk_imports/tracker_spec.rb27
-rw-r--r--spec/models/design_management/design_spec.rb8
-rw-r--r--spec/requests/api/graphql/mutations/commits/create_spec.rb38
-rw-r--r--spec/requests/api/graphql/mutations/releases/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb2
-rw-r--r--spec/requests/api/release/links_spec.rb2
-rw-r--r--spec/requests/projects/releases_controller_spec.rb60
-rw-r--r--spec/services/notification_service_spec.rb17
84 files changed, 837 insertions, 457 deletions
diff --git a/.gitlab/ci/cache-repo.gitlab-ci.yml b/.gitlab/ci/cache-repo.gitlab-ci.yml
index ff7655cee71..18e1ca1644d 100644
--- a/.gitlab/ci/cache-repo.gitlab-ci.yml
+++ b/.gitlab/ci/cache-repo.gitlab-ci.yml
@@ -49,6 +49,7 @@ cache-repo:
cd .. && rm -rf $CI_PROJECT_NAME;
echo "Cloning $CI_REPOSITORY_URL into $CI_PROJECT_NAME.";
time git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME;
+ cd $CI_PROJECT_NAME;
echo "Archiving $CI_PROJECT_NAME into /tmp/$FULL_CLONE_TAR_FILENAME.";
time tar cf /tmp/$FULL_CLONE_TAR_FILENAME .;
echo "GZipping /tmp/$FULL_CLONE_TAR_FILENAME.";
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
index 9420480e35a..2e66a1196a4 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
@@ -13,6 +13,7 @@ import {
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import { trackAlertIntegrationsViewsOptions, integrationToDeleteDefault } from '../constants';
+import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
export const i18n = {
title: s__('AlertsIntegrations|Current integrations'),
@@ -58,11 +59,6 @@ export default {
required: false,
default: false,
},
- currentIntegration: {
- type: Object,
- required: false,
- default: null,
- },
},
fields: [
{
@@ -82,9 +78,15 @@ export default {
label: __('Actions'),
},
],
+ apollo: {
+ currentIntegration: {
+ query: getCurrentIntegrationQuery,
+ },
+ },
data() {
return {
integrationToDelete: integrationToDeleteDefault,
+ currentIntegration: null,
};
},
mounted() {
@@ -101,11 +103,11 @@ export default {
const { category, action } = trackAlertIntegrationsViewsOptions;
Tracking.event(category, action);
},
- intergrationToDelete({ name, id }) {
+ setIntegrationToDelete({ name, id }) {
this.integrationToDelete.id = id;
this.integrationToDelete.name = name;
},
- deleteIntergration() {
+ deleteIntegration() {
this.$emit('delete-integration', { id: this.integrationToDelete.id });
this.integrationToDelete = { ...integrationToDeleteDefault };
},
@@ -153,7 +155,7 @@ export default {
<gl-button
v-gl-modal.deleteIntegration
icon="remove"
- @click="intergrationToDelete(item)"
+ @click="setIntegrationToDelete(item)"
/>
</gl-button-group>
</template>
@@ -175,7 +177,7 @@ export default {
:title="__('Are you sure?')"
:ok-title="s__('AlertSettings|Delete integration')"
ok-variant="danger"
- @ok="deleteIntergration"
+ @ok="deleteIntegration"
>
<gl-sprintf
:message="
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue
index cbb247a79e6..2aabe0b8e07 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue
@@ -12,11 +12,12 @@ import {
GlModalDirective,
GlToggle,
} from '@gitlab/ui';
-import MappingBuilder from './alert_mapping_builder.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import MappingBuilder from './alert_mapping_builder.vue';
import AlertSettingsFormHelpBlock from './alert_settings_form_help_block.vue';
+import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
import service from '../services';
import {
integrationTypesNew,
@@ -117,16 +118,16 @@ export default {
type: Boolean,
required: true,
},
- currentIntegration: {
- type: Object,
- required: false,
- default: null,
- },
canAddIntegration: {
type: Boolean,
required: true,
},
},
+ apollo: {
+ currentIntegration: {
+ query: getCurrentIntegrationQuery,
+ },
+ },
data() {
return {
selectedIntegration: integrationTypesNew[0].value,
@@ -140,6 +141,7 @@ export default {
resetSamplePayloadConfirmed: false,
customMapping: null,
parsingPayload: false,
+ currentIntegration: null,
};
},
computed: {
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
index 3904a5b0fe2..4f341ac07e9 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
@@ -4,6 +4,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
+import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
import createHttpIntegrationMutation from '../graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql';
@@ -11,6 +12,7 @@ import updatePrometheusIntegrationMutation from '../graphql/mutations/update_pro
import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql';
+import updateCurrentIntergrationMutation from '../graphql/mutations/update_current_intergration.mutation.graphql';
import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue';
@@ -75,6 +77,9 @@ export default {
createFlash({ message: err });
},
},
+ currentIntegration: {
+ query: getCurrentIntegrationQuery,
+ },
},
data() {
return {
@@ -87,7 +92,7 @@ export default {
loading() {
return this.$apollo.queries.integrations.loading;
},
- intergrationsOptionsOld() {
+ integrationsOptionsOld() {
return [
{
name: s__('AlertSettings|HTTP endpoint'),
@@ -208,7 +213,19 @@ export default {
});
},
editIntegration({ id }) {
- this.currentIntegration = this.integrations.list.find(integration => integration.id === id);
+ const currentIntegration = this.integrations.list.find(integration => integration.id === id);
+ this.$apollo.mutate({
+ mutation: updateCurrentIntergrationMutation,
+ variables: {
+ id: currentIntegration.id,
+ name: currentIntegration.name,
+ active: currentIntegration.active,
+ token: currentIntegration.token,
+ type: currentIntegration.type,
+ url: currentIntegration.url,
+ apiUrl: currentIntegration.apiUrl,
+ },
+ });
},
deleteIntegration({ id }) {
const { projectPath } = this;
@@ -229,7 +246,7 @@ export default {
if (error) {
return createFlash({ message: error });
}
- this.currentIntegration = null;
+ this.clearCurrentIntegration();
return createFlash({
message: this.$options.i18n.integrationRemoved,
type: FLASH_TYPES.SUCCESS,
@@ -243,7 +260,10 @@ export default {
});
},
clearCurrentIntegration() {
- this.currentIntegration = null;
+ this.$apollo.mutate({
+ mutation: updateCurrentIntergrationMutation,
+ variables: {},
+ });
},
testPayloadFailure() {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
@@ -255,16 +275,14 @@ export default {
<template>
<div>
<integrations-list
- :integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld"
+ :integrations="glFeatures.httpIntegrationsList ? integrations.list : integrationsOptionsOld"
:loading="loading"
- :current-integration="currentIntegration"
@edit-integration="editIntegration"
@delete-integration="deleteIntegration"
/>
<settings-form-new
v-if="glFeatures.httpIntegrationsList"
:loading="isUpdating"
- :current-integration="currentIntegration"
:can-add-integration="canAddIntegration"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
diff --git a/app/assets/javascripts/alerts_settings/graphql.js b/app/assets/javascripts/alerts_settings/graphql.js
new file mode 100644
index 00000000000..02c2def87fa
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql.js
@@ -0,0 +1,44 @@
+import Vue from 'vue';
+import produce from 'immer';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql';
+
+Vue.use(VueApollo);
+
+const resolvers = {
+ Mutation: {
+ updateCurrentIntegration: (
+ _,
+ { id = null, name, active, token, type, url, apiUrl },
+ { cache },
+ ) => {
+ const sourceData = cache.readQuery({ query: getCurrentIntegrationQuery });
+ const data = produce(sourceData, draftData => {
+ if (id === null) {
+ // eslint-disable-next-line no-param-reassign
+ draftData.currentIntegration = null;
+ } else {
+ // eslint-disable-next-line no-param-reassign
+ draftData.currentIntegration = {
+ id,
+ name,
+ active,
+ token,
+ type,
+ url,
+ apiUrl,
+ };
+ }
+ });
+ cache.writeQuery({ query: getCurrentIntegrationQuery, data });
+ },
+ },
+};
+
+export default new VueApollo({
+ defaultClient: createDefaultClient(resolvers, {
+ cacheConfig: {},
+ assumeImmutableResults: true,
+ }),
+});
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql
new file mode 100644
index 00000000000..3505241309e
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql
@@ -0,0 +1,19 @@
+mutation updateCurrentIntegration(
+ $id: String
+ $name: String
+ $active: Boolean
+ $token: String
+ $type: String
+ $url: String
+ $apiUrl: String
+) {
+ updateCurrentIntegration(
+ id: $id
+ name: $name
+ active: $active
+ token: $token
+ type: $type
+ url: $url
+ apiUrl: $apiUrl
+ ) @client
+}
diff --git a/app/assets/javascripts/alerts_settings/graphql/queries/get_current_integration.query.graphql b/app/assets/javascripts/alerts_settings/graphql/queries/get_current_integration.query.graphql
new file mode 100644
index 00000000000..4f22849a618
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/queries/get_current_integration.query.graphql
@@ -0,0 +1,3 @@
+query currentIntegration {
+ currentIntegration @client
+}
diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js
index a9d109b3f3e..5867991a738 100644
--- a/app/assets/javascripts/alerts_settings/index.js
+++ b/app/assets/javascripts/alerts_settings/index.js
@@ -1,10 +1,13 @@
import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
+import apolloProvider from './graphql';
-Vue.use(VueApollo);
+apolloProvider.clients.defaultClient.cache.writeData({
+ data: {
+ currentIntegration: null,
+ },
+});
export default el => {
if (!el) {
@@ -32,15 +35,6 @@ export default el => {
multiIntegrations,
} = el.dataset;
- const resolvers = {};
-
- const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(resolvers, {
- cacheConfig: {},
- assumeImmutableResults: true,
- }),
- });
-
return new Vue({
el,
provide: {
diff --git a/app/assets/javascripts/groups/new_group_child.js b/app/assets/javascripts/groups/new_group_child.js
deleted file mode 100644
index bb2aea3ea76..00000000000
--- a/app/assets/javascripts/groups/new_group_child.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { visitUrl } from '../lib/utils/url_utility';
-import DropLab from '../droplab/drop_lab';
-import ISetter from '../droplab/plugins/input_setter';
-
-const InputSetter = { ...ISetter };
-
-const NEW_PROJECT = 'new-project';
-const NEW_SUBGROUP = 'new-subgroup';
-
-export default class NewGroupChild {
- constructor(buttonWrapper) {
- this.buttonWrapper = buttonWrapper;
- this.newGroupChildButton = this.buttonWrapper.querySelector('.js-new-group-child');
- this.dropdownToggle = this.buttonWrapper.querySelector('.js-dropdown-toggle');
- this.dropdownList = this.buttonWrapper.querySelector('.dropdown-menu');
-
- this.newGroupPath = this.buttonWrapper.dataset.projectPath;
- this.subgroupPath = this.buttonWrapper.dataset.subgroupPath;
-
- this.init();
- }
-
- init() {
- this.initDroplab();
- this.bindEvents();
- }
-
- initDroplab() {
- this.droplab = new DropLab();
- this.droplab.init(
- this.dropdownToggle,
- this.dropdownList,
- [InputSetter],
- this.getDroplabConfig(),
- );
- }
-
- getDroplabConfig() {
- return {
- InputSetter: [
- {
- input: this.newGroupChildButton,
- valueAttribute: 'data-value',
- inputAttribute: 'data-action',
- },
- {
- input: this.newGroupChildButton,
- valueAttribute: 'data-text',
- },
- ],
- };
- }
-
- bindEvents() {
- this.newGroupChildButton.addEventListener('click', this.onClickNewGroupChildButton.bind(this));
- }
-
- onClickNewGroupChildButton(e) {
- if (e.target.dataset.action === NEW_PROJECT) {
- visitUrl(this.newGroupPath);
- } else if (e.target.dataset.action === NEW_SUBGROUP) {
- visitUrl(this.subgroupPath);
- }
- }
-}
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
index 8546b1f759f..8d956c694c0 100644
--- a/app/assets/javascripts/pages/groups/shared/group_details.js
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -2,7 +2,6 @@
import { getPagePath, getDashPath } from '~/lib/utils/common_utils';
import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
-import NewGroupChild from '~/groups/new_group_child';
import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
@@ -11,7 +10,6 @@ import GroupTabs from './group_tabs';
import initInviteMembersBanner from '~/groups/init_invite_members_banner';
export default function initGroupDetails(actionName = 'show') {
- const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
const dashPath = getDashPath();
let action = loadableActions.includes(dashPath) ? dashPath : getPagePath(1);
@@ -25,8 +23,5 @@ export default function initGroupDetails(actionName = 'show') {
notificationsDropdown();
new ProjectsList();
- if (newGroupChildWrapper) {
- new NewGroupChild(newGroupChildWrapper);
- }
initInviteMembersBanner();
}
diff --git a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
index 8308ac7e327..b0a7c4824bd 100644
--- a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
@@ -72,7 +72,7 @@ export default {
<template #left-primary>
<router-link
class="gl-text-body gl-font-weight-bold"
- data-testid="detailsLink"
+ data-testid="details-link"
:to="{ name: 'details', params: { id: item.id } }"
>
{{ item.path }}
diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js
index abc0bc8ce8c..c1883095097 100644
--- a/app/assets/javascripts/registry/explorer/stores/actions.js
+++ b/app/assets/javascripts/registry/explorer/stores/actions.js
@@ -56,7 +56,7 @@ export const requestTagsList = ({ commit, dispatch, state: { imageDetails } }, p
dispatch('receiveTagsListSuccess', { data, headers });
})
.catch(() => {
- createFlash(FETCH_TAGS_LIST_ERROR_MESSAGE);
+ createFlash({ message: FETCH_TAGS_LIST_ERROR_MESSAGE });
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
@@ -71,7 +71,7 @@ export const requestImageDetailsAndTagsList = ({ dispatch, commit }, id) => {
dispatch('requestTagsList');
})
.catch(() => {
- createFlash(FETCH_IMAGE_DETAILS_ERROR_MESSAGE);
+ createFlash({ message: FETCH_IMAGE_DETAILS_ERROR_MESSAGE });
commit(types.SET_MAIN_LOADING, false);
});
};
diff --git a/app/assets/javascripts/registry/explorer/utils.js b/app/assets/javascripts/registry/explorer/utils.js
index ff86a6b807d..2c89d508c31 100644
--- a/app/assets/javascripts/registry/explorer/utils.js
+++ b/app/assets/javascripts/registry/explorer/utils.js
@@ -1,8 +1,16 @@
export const pathGenerator = (imageDetails, ending = '?format=json') => {
// this method is a temporary workaround, to be removed with graphql implementation
// https://gitlab.com/gitlab-org/gitlab/-/issues/276432
- const basePath = imageDetails.name
- ? imageDetails.path.replace(`/${imageDetails.name}`, '')
- : imageDetails.path;
+
+ const splitPath = imageDetails.path.split('/').reverse();
+ const splitName = imageDetails.name ? imageDetails.name.split('/').reverse() : [];
+ const basePath = splitPath
+ .reduce((acc, curr, index) => {
+ if (splitPath[index] !== splitName[index]) {
+ acc.unshift(curr);
+ }
+ return acc;
+ }, [])
+ .join('/');
return `/${basePath}/registry/repository/${imageDetails.id}/tags${ending}`;
};
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
deleted file mode 100644
index 4b91d4c00e3..00000000000
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ /dev/null
@@ -1,6 +0,0 @@
-<script>
-// This file was only introduced to not break master and shall be delete soon.
-import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
-
-export default DeprecatedModal2;
-</script>
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 4e8260d9e53..a6e795a2b91 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -54,7 +54,7 @@ class Projects::ReleasesController < Projects::ApplicationController
end
def sanitized_filepath
- CGI.unescape(params[:filepath])
+ "/#{CGI.unescape(params[:filepath])}"
end
def sanitized_tag_name
diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb
index 9ed1bb819c8..2b9107350fd 100644
--- a/app/graphql/mutations/commits/create.rb
+++ b/app/graphql/mutations/commits/create.rb
@@ -13,7 +13,11 @@ module Mutations
argument :branch, GraphQL::STRING_TYPE,
required: true,
- description: 'Name of the branch'
+ description: 'Name of the branch to commit into, it can be a new branch'
+
+ argument :start_branch, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'If on a new branch, name of the original branch'
argument :message,
GraphQL::STRING_TYPE,
@@ -32,13 +36,13 @@ module Mutations
authorize :push_code
- def resolve(project_path:, branch:, message:, actions:)
+ def resolve(project_path:, branch:, message:, actions:, **args)
project = authorized_find!(full_path: project_path)
attributes = {
commit_message: message,
branch_name: branch,
- start_branch: branch,
+ start_branch: args[:start_branch] || branch,
actions: actions.map { |action| action.to_h }
}
diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb
index 0e519ece791..8fb051f5627 100644
--- a/app/graphql/types/release_asset_link_type.rb
+++ b/app/graphql/types/release_asset_link_type.rb
@@ -24,10 +24,8 @@ module Types
def direct_asset_url
return object.url unless object.filepath
- release = object.release
- project = release.project
-
- Gitlab::Routing.url_helpers.project_release_url(project, release) << object.filepath
+ release = object.release.present
+ release.download_url(object.filepath)
end
end
end
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index 80963ff3304..34030e079c7 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -26,6 +26,10 @@ class BulkImports::Entity < ApplicationRecord
belongs_to :project, optional: true
belongs_to :group, foreign_key: :namespace_id, optional: true
+ has_many :trackers,
+ class_name: 'BulkImports::Tracker',
+ foreign_key: :bulk_import_entity_id
+
validates :project, absence: true, if: :group
validates :group, absence: true, if: :project
validates :source_type, :source_full_path, :destination_name,
diff --git a/app/models/bulk_imports/tracker.rb b/app/models/bulk_imports/tracker.rb
new file mode 100644
index 00000000000..02e0904e1af
--- /dev/null
+++ b/app/models/bulk_imports/tracker.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# This model is responsible for keeping track of the requests/pagination
+# happening during a Group Migration (BulkImport).
+class BulkImports::Tracker < ApplicationRecord
+ self.table_name = 'bulk_import_trackers'
+
+ belongs_to :entity,
+ class_name: 'BulkImports::Entity',
+ foreign_key: :bulk_import_entity_id,
+ optional: false
+
+ validates :relation,
+ presence: true,
+ uniqueness: { scope: :bulk_import_entity_id }
+
+ validates :next_page, presence: { if: :has_next_page? }
+end
diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb
index 9f2eada0890..f5e52c04944 100644
--- a/app/models/design_management/design.rb
+++ b/app/models/design_management/design.rb
@@ -146,12 +146,6 @@ module DesignManagement
strong_memoize(:most_recent_action) { actions.ordered.last }
end
- def participants(current_user = nil)
- return [] unless Feature.enabled?(:design_management_design_notification_participants, project)
-
- super
- end
-
# A reference for a design is the issue reference, indexed by the filename
# with an optional infix when full.
#
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
index 9d7e183aaf3..b11585d0d1c 100644
--- a/app/presenters/release_presenter.rb
+++ b/app/presenters/release_presenter.rb
@@ -71,6 +71,12 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
can_download_code? ? release.name : "Release-#{release.id}"
end
+ def download_url(filepath)
+ filepath = filepath.sub(%r{^/}, '') if filepath.start_with?('/')
+
+ downloads_project_release_url(project, release, filepath)
+ end
+
private
def can_download_code?
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index a50327ecbec..ee08829d990 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -20,37 +20,16 @@
%span.access-request-links.gl-ml-3
= render 'shared/members/access_request_links', source: @group
- .home-panel-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
+ .home-panel-buttons.col-md-12.col-lg-6
- if current_user
- .group-buttons
- = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn', emails_disabled: emails_disabled
- - new_project_label = _("New project")
- - new_subgroup_label = _("New subgroup")
- - if can_create_projects and can_create_subgroups
- .btn-group.new-project-subgroup.droplab-dropdown.home-panel-action-button.gl-mt-3.js-new-project-subgroup.qa-new-project-or-subgroup-dropdown{ data: { project_path: new_project_path(namespace_id: @group.id), subgroup_path: new_group_path(parent_id: @group.id) } }
- %input.btn.btn-success.dropdown-primary.js-new-group-child.qa-new-in-group-button{ type: "button", value: new_project_label, data: { action: "new-project" } }
- %button.btn.btn-success.dropdown-toggle.js-dropdown-toggle.qa-new-project-or-subgroup-dropdown-toggle{ type: "button", data: { "dropdown-trigger" => "#new-project-or-subgroup-dropdown", 'display' => 'static' } }
- = sprite_icon("chevron-down", css_class: "icon dropdown-btn-icon")
- %ul#new-project-or-subgroup-dropdown.dropdown-menu.dropdown-menu-right{ data: { dropdown: true } }
- %li.droplab-item-selected.qa-new-project-option{ role: "button", data: { value: "new-project", text: new_project_label } }
- .menu-item
- .icon-container
- = sprite_icon("check", css_class: "list-item-checkmark")
- .description
- %strong= new_project_label
- %span= s_("GroupsTree|Create a project in this group.")
- %li.divider.droplap-item-ignore
- %li.qa-new-subgroup-option{ role: "button", data: { value: "new-subgroup", text: new_subgroup_label } }
- .menu-item
- .icon-container
- = sprite_icon("check", css_class: "list-item-checkmark")
- .description
- %strong= new_subgroup_label
- %span= s_("GroupsTree|Create a subgroup in this group.")
- - elsif can_create_projects
- = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success gl-mt-3"
- - elsif can_create_subgroups
- = link_to new_subgroup_label, new_group_path(parent_id: @group.id), class: "btn btn-success gl-mt-3"
+ .gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2{ data: { testid: 'group-buttons' } }
+ = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn gl-button gl-sm-w-auto gl-w-full', dropdown_container_class: 'gl-mr-0 gl-px-2 gl-sm-w-auto gl-w-full', emails_disabled: emails_disabled
+ - if can_create_subgroups
+ .gl-px-2.gl-sm-w-auto.gl-w-full
+ = link_to _("New subgroup"), new_group_path(parent_id: @group.id), class: "btn btn-success btn-md gl-button btn-success-secondary gl-mt-3 gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_subgroup_button' }
+ - if can_create_projects
+ .gl-px-2.gl-sm-w-auto.gl-w-full
+ = link_to _("New project"), new_project_path(namespace_id: @group.id), class: "btn btn-success btn-md gl-button gl-mt-3 gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_project_button' }
- if @group.description.present?
.group-home-desc.mt-1
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index eb847bea95b..0c33cefc65a 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -44,7 +44,7 @@
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user
.d-inline-flex
- = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', emails_disabled: emails_disabled
+ = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', dropdown_container_class: 'gl-mr-3', emails_disabled: emails_disabled
.count-buttons.d-inline-flex
= render 'projects/buttons/star'
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 80ce4b28931..690f0fe10f7 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -13,7 +13,7 @@
- else
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
- = link_to new_user_session_path, class: 'gl-button btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
+ = link_to new_user_session_path, class: 'btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
= sprite_icon('star-o', css_class: 'icon')
%span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index fbcfec5fd96..14f4b04ef78 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -1,4 +1,5 @@
- btn_class = local_assigns.fetch(:btn_class, '')
+- dropdown_container_class = local_assigns.fetch(:dropdown_container_class, '')
- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting
@@ -8,8 +9,8 @@
- else
- button_title = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
- .js-notification-dropdown.notification-dropdown.home-panel-action-button.gl-mt-3.gl-mr-3.dropdown.inline
- = form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f|
+ .js-notification-dropdown.notification-dropdown.home-panel-action-button.gl-mt-3.dropdown.inline{ class: dropdown_container_class }
+ = form_for notification_setting, remote: true, html: { class: "notification-form no-label" } do |f|
= hidden_setting_source_input(notification_setting)
= hidden_field_tag "hide_label", true
= f.hidden_field :level, class: "notification_setting_level"
diff --git a/changelogs/unreleased/217553-permanent-links-to-release-assets-not-found-404.yml b/changelogs/unreleased/217553-permanent-links-to-release-assets-not-found-404.yml
new file mode 100644
index 00000000000..95fc65e5779
--- /dev/null
+++ b/changelogs/unreleased/217553-permanent-links-to-release-assets-not-found-404.yml
@@ -0,0 +1,5 @@
+---
+title: Fix release assets link redirection
+merge_request: 35381
+author:
+type: fixed
diff --git a/changelogs/unreleased/245263-mr-refs-cleanup-old-mrs.yml b/changelogs/unreleased/245263-mr-refs-cleanup-old-mrs.yml
new file mode 100644
index 00000000000..6e105559cfb
--- /dev/null
+++ b/changelogs/unreleased/245263-mr-refs-cleanup-old-mrs.yml
@@ -0,0 +1,5 @@
+---
+title: Backfill cleanup schedules for old closed/merged MRs
+merge_request: 46782
+author:
+type: other
diff --git a/changelogs/unreleased/263144-commit-to-new-branch.yml b/changelogs/unreleased/263144-commit-to-new-branch.yml
new file mode 100644
index 00000000000..3248b635630
--- /dev/null
+++ b/changelogs/unreleased/263144-commit-to-new-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Extend GraphQL API to commit to a new branch in a single operation
+merge_request: 47203
+author:
+type: changed
diff --git a/changelogs/unreleased/267113-separate-new-group-project-creation-button.yml b/changelogs/unreleased/267113-separate-new-group-project-creation-button.yml
new file mode 100644
index 00000000000..c4d46ad74c4
--- /dev/null
+++ b/changelogs/unreleased/267113-separate-new-group-project-creation-button.yml
@@ -0,0 +1,5 @@
+---
+title: Move "New subgroup" and "New project" out of the dropdown into individual buttons
+merge_request: 46907
+author:
+type: changed
diff --git a/changelogs/unreleased/ab-add-missing-expression-indexes.yml b/changelogs/unreleased/ab-add-missing-expression-indexes.yml
new file mode 100644
index 00000000000..6e6d6b8f5c4
--- /dev/null
+++ b/changelogs/unreleased/ab-add-missing-expression-indexes.yml
@@ -0,0 +1,5 @@
+---
+title: Add missing expression indexes
+merge_request: 47424
+author:
+type: performance
diff --git a/changelogs/unreleased/add-nulls-last-to-db-index.yml b/changelogs/unreleased/add-nulls-last-to-db-index.yml
new file mode 100644
index 00000000000..f1fc7b04785
--- /dev/null
+++ b/changelogs/unreleased/add-nulls-last-to-db-index.yml
@@ -0,0 +1,5 @@
+---
+title: Add NULLS LAST to index on merge request metrics
+merge_request: 47300
+author:
+type: performance
diff --git a/changelogs/unreleased/kassio-bulkimport-entity-trackers.yml b/changelogs/unreleased/kassio-bulkimport-entity-trackers.yml
new file mode 100644
index 00000000000..152f9c5c5b9
--- /dev/null
+++ b/changelogs/unreleased/kassio-bulkimport-entity-trackers.yml
@@ -0,0 +1,6 @@
+---
+title: Add BulkImport::Tracker to store the pagination information of the Group Migration
+ (BulkImport) requests
+merge_request: 47009
+author:
+type: changed
diff --git a/config/feature_flags/development/ci_lint_creates_pipeline_with_dry_run.yml b/config/feature_flags/development/ci_lint_creates_pipeline_with_dry_run.yml
deleted file mode 100644
index 935382989ed..00000000000
--- a/config/feature_flags/development/ci_lint_creates_pipeline_with_dry_run.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_lint_creates_pipeline_with_dry_run
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37828
-rollout_issue_url:
-milestone: '13.3'
-type: development
-group: group::continuous integration
-default_enabled: true
diff --git a/config/feature_flags/development/design_management_design_notification_participants.yml b/config/feature_flags/development/design_management_design_notification_participants.yml
deleted file mode 100644
index a91d61ca44d..00000000000
--- a/config/feature_flags/development/design_management_design_notification_participants.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: design_management_design_notification_participants
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46642
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/277319
-milestone: '13.6'
-type: development
-group: group::knowledge
-default_enabled: false
diff --git a/db/migrate/20201104204739_create_bulk_import_trackers.rb b/db/migrate/20201104204739_create_bulk_import_trackers.rb
new file mode 100644
index 00000000000..906bd06248d
--- /dev/null
+++ b/db/migrate/20201104204739_create_bulk_import_trackers.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class CreateBulkImportTrackers < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ unless table_exists?(:bulk_import_trackers)
+ create_table :bulk_import_trackers do |t|
+ t.references :bulk_import_entity,
+ null: false,
+ index: false,
+ foreign_key: { on_delete: :cascade }
+
+ t.text :relation, null: false
+ t.text :next_page
+ t.boolean :has_next_page, default: false, null: false
+
+ t.index %w(bulk_import_entity_id relation),
+ unique: true,
+ name: :bulk_import_trackers_uniq_relation_by_entity
+ end
+ end
+ end
+
+ add_check_constraint :bulk_import_trackers,
+ '(has_next_page IS FALSE or next_page IS NOT NULL)',
+ 'check_next_page_requirement'
+ add_text_limit :bulk_import_trackers, :relation, 255
+ add_text_limit :bulk_import_trackers, :next_page, 255
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :bulk_import_trackers
+ end
+ end
+end
diff --git a/db/migrate/20201110133629_change_index_mr_metrics_target_project_id.rb b/db/migrate/20201110133629_change_index_mr_metrics_target_project_id.rb
new file mode 100644
index 00000000000..5bd810207d8
--- /dev/null
+++ b/db/migrate/20201110133629_change_index_mr_metrics_target_project_id.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ChangeIndexMrMetricsTargetProjectId < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_merge_request_metrics_on_target_project_id_merged_at'
+ NULLS_LAST_INDEX_NAME = 'index_mr_metrics_on_target_project_id_merged_at_nulls_last'
+
+ def up
+ add_concurrent_index :merge_request_metrics, [:target_project_id, :merged_at, :id], order: { merged_at: 'DESC NULLS LAST', id: 'DESC' }, name: NULLS_LAST_INDEX_NAME
+ remove_concurrent_index_by_name(:merge_request_metrics, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index :merge_request_metrics, [:target_project_id, :merged_at], name: INDEX_NAME
+ remove_concurrent_index_by_name(:merge_request_metrics, NULLS_LAST_INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20201111152859_add_missing_expression_indexes.rb b/db/migrate/20201111152859_add_missing_expression_indexes.rb
new file mode 100644
index 00000000000..e2742f7f3bc
--- /dev/null
+++ b/db/migrate/20201111152859_add_missing_expression_indexes.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class AddMissingExpressionIndexes < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEXES = [
+ [:namespaces, :index_on_namespaces_lower_name, 'LOWER(name)'],
+ [:namespaces, :index_on_namespaces_lower_path, 'LOWER(path)'],
+ [:projects, :index_on_projects_lower_path, 'LOWER(path)'],
+ [:routes, :index_on_routes_lower_path, 'LOWER(path)'],
+ [:users, :index_on_users_lower_username, 'LOWER(username)'],
+ [:users, :index_on_users_lower_email, 'LOWER(email)']
+ ]
+
+ def up
+ # Those indexes had been introduced before, but they haven't been
+ # captured in structure.sql. For installations that already have it,
+ # this is a no-op - others will get it retroactively with
+ # this migration.
+
+ tables = Set.new
+
+ INDEXES.each do |(table, name, expression)|
+ unless index_name_exists?(table, name)
+ add_concurrent_index table, expression, name: name
+ tables.add(table)
+ end
+ end
+
+ # Rebuild statistics on affected tables only
+ tables.each do |table|
+ execute("ANALYZE #{table}")
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20201103110018_schedule_merge_request_cleanup_schedules_backfill.rb b/db/post_migrate/20201103110018_schedule_merge_request_cleanup_schedules_backfill.rb
new file mode 100644
index 00000000000..77057205b09
--- /dev/null
+++ b/db/post_migrate/20201103110018_schedule_merge_request_cleanup_schedules_backfill.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ScheduleMergeRequestCleanupSchedulesBackfill < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'BackfillMergeRequestCleanupSchedules'
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+ TEMP_INDEX_NAME = 'merge_requests_state_id_temp_index'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_requests, :id, name: TEMP_INDEX_NAME, where: "state_id IN (2, 3)"
+
+ eligible_mrs = Gitlab::BackgroundMigration::BackfillMergeRequestCleanupSchedules::MergeRequest.eligible
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ eligible_mrs,
+ MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ remove_concurrent_index_by_name :merge_requests, TEMP_INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20201103110018 b/db/schema_migrations/20201103110018
new file mode 100644
index 00000000000..82ab2fda543
--- /dev/null
+++ b/db/schema_migrations/20201103110018
@@ -0,0 +1 @@
+8a45a6186d7e18f1dea43593dc0226860fc2e8d3ae386f50a022958b758c7c75 \ No newline at end of file
diff --git a/db/schema_migrations/20201104204739 b/db/schema_migrations/20201104204739
new file mode 100644
index 00000000000..83794fd569f
--- /dev/null
+++ b/db/schema_migrations/20201104204739
@@ -0,0 +1 @@
+9431c771b14d61851e8e69b3a789f222463bbe460078a35c8ad3cbcf8df8b077 \ No newline at end of file
diff --git a/db/schema_migrations/20201110133629 b/db/schema_migrations/20201110133629
new file mode 100644
index 00000000000..487b5eab7c1
--- /dev/null
+++ b/db/schema_migrations/20201110133629
@@ -0,0 +1 @@
+83773b825db9b2671fd4ffb2c0d6733036737385ce7a933040011026b34ba1e1 \ No newline at end of file
diff --git a/db/schema_migrations/20201111152859 b/db/schema_migrations/20201111152859
new file mode 100644
index 00000000000..2d5b337b857
--- /dev/null
+++ b/db/schema_migrations/20201111152859
@@ -0,0 +1 @@
+4c5baa6a09a339fac544f830d5ef822b1e7e4eae8431bd91df5113125accbc77 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0d913c08f08..a7334cd10f2 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9918,6 +9918,26 @@ CREATE SEQUENCE bulk_import_entities_id_seq
ALTER SEQUENCE bulk_import_entities_id_seq OWNED BY bulk_import_entities.id;
+CREATE TABLE bulk_import_trackers (
+ id bigint NOT NULL,
+ bulk_import_entity_id bigint NOT NULL,
+ relation text NOT NULL,
+ next_page text,
+ has_next_page boolean DEFAULT false NOT NULL,
+ CONSTRAINT check_2d45cae629 CHECK ((char_length(relation) <= 255)),
+ CONSTRAINT check_40aeaa600b CHECK ((char_length(next_page) <= 255)),
+ CONSTRAINT check_next_page_requirement CHECK (((has_next_page IS FALSE) OR (next_page IS NOT NULL)))
+);
+
+CREATE SEQUENCE bulk_import_trackers_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE bulk_import_trackers_id_seq OWNED BY bulk_import_trackers.id;
+
CREATE TABLE bulk_imports (
id bigint NOT NULL,
user_id integer NOT NULL,
@@ -17621,6 +17641,8 @@ ALTER TABLE ONLY bulk_import_configurations ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY bulk_import_entities ALTER COLUMN id SET DEFAULT nextval('bulk_import_entities_id_seq'::regclass);
+ALTER TABLE ONLY bulk_import_trackers ALTER COLUMN id SET DEFAULT nextval('bulk_import_trackers_id_seq'::regclass);
+
ALTER TABLE ONLY bulk_imports ALTER COLUMN id SET DEFAULT nextval('bulk_imports_id_seq'::regclass);
ALTER TABLE ONLY chat_names ALTER COLUMN id SET DEFAULT nextval('chat_names_id_seq'::regclass);
@@ -18631,6 +18653,9 @@ ALTER TABLE ONLY bulk_import_configurations
ALTER TABLE ONLY bulk_import_entities
ADD CONSTRAINT bulk_import_entities_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY bulk_import_trackers
+ ADD CONSTRAINT bulk_import_trackers_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY bulk_imports
ADD CONSTRAINT bulk_imports_pkey PRIMARY KEY (id);
@@ -19914,6 +19939,8 @@ CREATE INDEX backup_labels_title_idx ON backup_labels USING btree (title);
CREATE INDEX backup_labels_type_project_id_idx ON backup_labels USING btree (type, project_id);
+CREATE UNIQUE INDEX bulk_import_trackers_uniq_relation_by_entity ON bulk_import_trackers USING btree (bulk_import_entity_id, relation);
+
CREATE INDEX ci_builds_gitlab_monitor_metrics ON ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text);
CREATE INDEX code_owner_approval_required ON protected_branches USING btree (project_id, code_owner_approval_required) WHERE (code_owner_approval_required = true);
@@ -21136,8 +21163,6 @@ CREATE INDEX index_merge_request_metrics_on_pipeline_id ON merge_request_metrics
CREATE INDEX index_merge_request_metrics_on_target_project_id ON merge_request_metrics USING btree (target_project_id);
-CREATE INDEX index_merge_request_metrics_on_target_project_id_merged_at ON merge_request_metrics USING btree (target_project_id, merged_at);
-
CREATE UNIQUE INDEX index_merge_request_reviewers_on_merge_request_id_and_user_id ON merge_request_reviewers USING btree (merge_request_id, user_id);
CREATE INDEX index_merge_request_reviewers_on_user_id ON merge_request_reviewers USING btree (user_id);
@@ -21228,6 +21253,8 @@ CREATE INDEX index_mr_cleanup_schedules_timestamps ON merge_request_cleanup_sche
CREATE UNIQUE INDEX index_mr_context_commits_on_merge_request_id_and_sha ON merge_request_context_commits USING btree (merge_request_id, sha);
+CREATE INDEX index_mr_metrics_on_target_project_id_merged_at_nulls_last ON merge_request_metrics USING btree (target_project_id, merged_at DESC NULLS LAST, id DESC);
+
CREATE UNIQUE INDEX index_namespace_aggregation_schedules_on_namespace_id ON namespace_aggregation_schedules USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON namespace_root_storage_statistics USING btree (namespace_id);
@@ -21326,12 +21353,24 @@ CREATE UNIQUE INDEX index_on_instance_statistics_recorded_at_and_identifier ON a
CREATE INDEX index_on_label_links_all_columns ON label_links USING btree (target_id, label_id, target_type);
+CREATE INDEX index_on_namespaces_lower_name ON namespaces USING btree (lower((name)::text));
+
+CREATE INDEX index_on_namespaces_lower_path ON namespaces USING btree (lower((path)::text));
+
+CREATE INDEX index_on_projects_lower_path ON projects USING btree (lower((path)::text));
+
+CREATE INDEX index_on_routes_lower_path ON routes USING btree (lower((path)::text));
+
CREATE UNIQUE INDEX index_on_segment_selections_group_id_segment_id ON analytics_devops_adoption_segment_selections USING btree (group_id, segment_id);
CREATE UNIQUE INDEX index_on_segment_selections_project_id_segment_id ON analytics_devops_adoption_segment_selections USING btree (project_id, segment_id);
CREATE INDEX index_on_segment_selections_segment_id ON analytics_devops_adoption_segment_selections USING btree (segment_id);
+CREATE INDEX index_on_users_lower_email ON users USING btree (lower((email)::text));
+
+CREATE INDEX index_on_users_lower_username ON users USING btree (lower((username)::text));
+
CREATE INDEX index_on_users_name_lower ON users USING btree (lower((name)::text));
CREATE INDEX index_open_project_tracker_data_on_service_id ON open_project_tracker_data USING btree (service_id);
@@ -22248,6 +22287,8 @@ CREATE UNIQUE INDEX merge_request_user_mentions_on_mr_id_and_note_id_index ON me
CREATE UNIQUE INDEX merge_request_user_mentions_on_mr_id_index ON merge_request_user_mentions USING btree (merge_request_id) WHERE (note_id IS NULL);
+CREATE INDEX merge_requests_state_id_temp_index ON merge_requests USING btree (id) WHERE (state_id = ANY (ARRAY[2, 3]));
+
CREATE INDEX note_mentions_temp_index ON notes USING btree (id, noteable_type) WHERE (note ~~ '%@%'::text);
CREATE UNIQUE INDEX one_canonical_wiki_page_slug_per_metadata ON wiki_page_slugs USING btree (wiki_page_meta_id) WHERE (canonical = true);
@@ -24122,6 +24163,9 @@ ALTER TABLE ONLY analytics_cycle_analytics_group_stages
ALTER TABLE ONLY metrics_dashboard_annotations
ADD CONSTRAINT fk_rails_aeb11a7643 FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE;
+ALTER TABLE ONLY bulk_import_trackers
+ ADD CONSTRAINT fk_rails_aed566d3f3 FOREIGN KEY (bulk_import_entity_id) REFERENCES bulk_import_entities(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY pool_repositories
ADD CONSTRAINT fk_rails_af3f8c5d62 FOREIGN KEY (shard_id) REFERENCES shards(id) ON DELETE RESTRICT;
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index ad6ce5501d6..e13412fabb8 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -2996,7 +2996,7 @@ input CommitCreateInput {
actions: [CommitAction!]!
"""
- Name of the branch
+ Name of the branch to commit into, it can be a new branch
"""
branch: String!
@@ -3014,6 +3014,11 @@ input CommitCreateInput {
Project full path the branch is associated with
"""
projectPath: ID!
+
+ """
+ If on a new branch, name of the original branch
+ """
+ startBranch: String
}
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index fca75870115..841224447d6 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -8114,7 +8114,7 @@
},
{
"name": "branch",
- "description": "Name of the branch",
+ "description": "Name of the branch to commit into, it can be a new branch",
"type": {
"kind": "NON_NULL",
"name": null,
@@ -8127,6 +8127,16 @@
"defaultValue": null
},
{
+ "name": "startBranch",
+ "description": "If on a new branch, name of the original branch",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "message",
"description": "Raw commit message",
"type": {
diff --git a/doc/user/group/img/create_new_project_from_group_v13_6.png b/doc/user/group/img/create_new_project_from_group_v13_6.png
new file mode 100644
index 00000000000..72d19817686
--- /dev/null
+++ b/doc/user/group/img/create_new_project_from_group_v13_6.png
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index d1149a95f4e..f05bf7b487e 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -235,7 +235,7 @@ There are two different ways to add a new project to a group:
- Select a group, and then click **New project**. You can then continue [creating your project](../../gitlab-basics/create-project.md).
- ![New project](img/create_new_project_from_group.png)
+ ![New project](img/create_new_project_from_group_v13_6.png)
- While you are creating a project, select a group namespace
you've already created from the dropdown menu.
diff --git a/doc/user/group/subgroups/img/create_subgroup_button.png b/doc/user/group/subgroups/img/create_subgroup_button.png
deleted file mode 100644
index d1355d4b2c3..00000000000
--- a/doc/user/group/subgroups/img/create_subgroup_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/subgroups/img/create_subgroup_button_v13_6.png b/doc/user/group/subgroups/img/create_subgroup_button_v13_6.png
new file mode 100644
index 00000000000..013aee6b0b4
--- /dev/null
+++ b/doc/user/group/subgroups/img/create_subgroup_button_v13_6.png
Binary files differ
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 031815d4d1a..268014a3cd2 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -99,10 +99,9 @@ creation is disabled by an administrator in their settings.
To create a subgroup:
-1. In the group's dashboard expand the **New project** split button, select
- **New subgroup** and click the **New subgroup** button.
+1. In the group's dashboard click the **New subgroup** button.
- ![Subgroups page](img/create_subgroup_button.png)
+ ![Subgroups page](img/create_subgroup_button_v13_6.png)
1. Create a new group like you would normally do. Notice that the immediate parent group
namespace is fixed under **Group path**. The visibility level can differ from
diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md
index 64bc6e48b81..f3d27147557 100644
--- a/doc/user/profile/notifications.md
+++ b/doc/user/profile/notifications.md
@@ -213,14 +213,7 @@ then that user will also be notified.
## Design email notifications
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217095) in GitLab 13.6.
-> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
-> - It's disabled on GitLab.com.
-> - It's not recommended for production use.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-design-email-notifications). **(CORE ONLY)**
-
-CAUTION: **Warning:**
-This feature might not be available to you. Check the **version history** note above for details.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217095) in GitLab 13.6.
Email notifications are sent to the participants when comments are made on a design.
@@ -230,25 +223,6 @@ The participants are:
- Authors of comments on the design.
- Anyone that is `@mentioned` in a comment on the design.
-### Enable or disable design email notifications **(CORE ONLY)**
-
-The design email notifications feature is under development and not ready for production use. It is
-deployed behind a feature flag that is **disabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can enable it.
-
-To enable it:
-
-```ruby
-Feature.enable(:design_management_design_notification_participants)
-```
-
-To disable it:
-
-```ruby
-Feature.disable(:design_management_design_notification_participants)
-```
-
## Filtering email
Notification email messages include GitLab-specific headers. You can filter the notification emails based on the content of these headers to better manage your notifications. For example, you could filter all emails for a specific project where you are being assigned either a merge request or issue.
diff --git a/doc/user/project/static_site_editor/index.md b/doc/user/project/static_site_editor/index.md
index 8a2f62ec7a2..47b18863e4d 100644
--- a/doc/user/project/static_site_editor/index.md
+++ b/doc/user/project/static_site_editor/index.md
@@ -80,6 +80,7 @@ codebase.
## Edit content
> - Support for modifying the default merge request title and description [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216861) in GitLab 13.5.
+> - Support for selecting a merge request template [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263252) in GitLab 13.6.
After setting up your project, you can start editing content directly from the Static Site Editor.
@@ -91,7 +92,9 @@ To edit a file:
wish to edit the raw Markdown instead, you can toggle the **Markdown** mode
in the bottom-right corner.
1. When you're done, click **Submit changes...**.
-1. (Optional) Adjust the default title and description of the merge request that will be submitted with your changes.
+1. (Optional) Adjust the default title and description of the merge request that will be submitted
+ with your changes. Alternatively, select a [merge request template](../../../user/project/description_templates.md#creating-merge-request-templates)
+ from the dropdown menu and edit it accordingly.
1. Click **Submit changes**.
1. A new merge request is automatically created and you can assign a colleague for review.
diff --git a/lib/api/entities/releases/link.rb b/lib/api/entities/releases/link.rb
index 654df2e2caf..c1d83a8924f 100644
--- a/lib/api/entities/releases/link.rb
+++ b/lib/api/entities/releases/link.rb
@@ -14,10 +14,8 @@ module API
def direct_asset_url
return object.url unless object.filepath
- release = object.release
- project = release.project
-
- Gitlab::Routing.url_helpers.project_release_url(project, release) << object.filepath
+ release = object.release.present
+ release.download_url(object.filepath)
end
end
end
diff --git a/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb b/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb
new file mode 100644
index 00000000000..8a58cf9b302
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Backfill merge request cleanup schedules of closed/merged merge requests
+ # without any corresponding records.
+ class BackfillMergeRequestCleanupSchedules
+ # Model used for migration added in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46782.
+ class MergeRequest < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'merge_requests'
+
+ def self.eligible
+ where('merge_requests.state_id IN (2, 3)')
+ end
+ end
+
+ def perform(start_id, end_id)
+ eligible_mrs = MergeRequest.eligible.where(id: start_id..end_id)
+ scheduled_at_column = "COALESCE(metrics.merged_at, COALESCE(metrics.latest_closed_at, merge_requests.updated_at)) + interval '14 days'"
+ query =
+ eligible_mrs
+ .select("merge_requests.id, #{scheduled_at_column}, NOW(), NOW()")
+ .joins('LEFT JOIN merge_request_metrics metrics ON metrics.merge_request_id = merge_requests.id')
+
+ result = ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO merge_request_cleanup_schedules (merge_request_id, scheduled_at, created_at, updated_at)
+ #{query.to_sql}
+ ON CONFLICT (merge_request_id) DO NOTHING;
+ SQL
+
+ ::Gitlab::BackgroundMigration::Logger.info(
+ message: 'Backfilled merge_request_cleanup_schedules records',
+ count: result.cmd_tuples
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index 9b109df9fe0..661189eea50 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -38,10 +38,6 @@ module Gitlab
::Feature.enabled?(:ci_disallow_to_create_merge_request_pipelines_in_target_project, target_project)
end
- def self.lint_creates_pipeline_with_dry_run?(project)
- ::Feature.enabled?(:ci_lint_creates_pipeline_with_dry_run, project, default_enabled: true)
- end
-
def self.project_transactionless_destroy?(project)
Feature.enabled?(:project_transactionless_destroy, project, default_enabled: false)
end
diff --git a/lib/gitlab/ci/lint.rb b/lib/gitlab/ci/lint.rb
index 44f2ac23ce3..fb795152abe 100644
--- a/lib/gitlab/ci/lint.rb
+++ b/lib/gitlab/ci/lint.rb
@@ -24,7 +24,7 @@ module Gitlab
end
def validate(content, dry_run: false)
- if dry_run && Gitlab::Ci::Features.lint_creates_pipeline_with_dry_run?(@project)
+ if dry_run
simulate_pipeline_creation(content)
else
static_validation(content)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a65fec265d3..82fdd0fe1cc 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13565,12 +13565,6 @@ msgstr ""
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
msgstr ""
-msgid "GroupsTree|Create a project in this group."
-msgstr ""
-
-msgid "GroupsTree|Create a subgroup in this group."
-msgstr ""
-
msgid "GroupsTree|Edit group"
msgstr ""
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 7639def98b7..38d919be4db 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -7,11 +7,8 @@ module QA
include Page::Component::GroupsFilter
view 'app/views/groups/_home_panel.html.haml' do
- element :new_project_or_subgroup_dropdown
- element :new_project_or_subgroup_dropdown_toggle
- element :new_project_option
- element :new_subgroup_option
- element :new_in_group_button
+ element :new_project_button
+ element :new_subgroup_button
end
view 'app/assets/javascripts/groups/constants.js' do
@@ -26,8 +23,9 @@ module QA
click_link name
end
- def has_new_project_or_subgroup_dropdown?
- has_element?(:new_project_or_subgroup_dropdown)
+ def has_new_project_and_new_subgroup_buttons?
+ has_element?(:new_project_button)
+ has_element?(:new_subgroup_button)
end
def has_subgroup?(name)
@@ -35,15 +33,11 @@ module QA
end
def go_to_new_subgroup
- select_kind :new_subgroup_option
-
- click_element :new_in_group_button
+ click_element :new_subgroup_button
end
def go_to_new_project
- select_kind :new_project_option
-
- click_element :new_in_group_button
+ click_element :new_project_button
end
def leave_group
@@ -51,23 +45,6 @@ module QA
click_element :leave_group_link
end
end
-
- private
-
- def select_kind(kind)
- QA::Support::Retrier.retry_on_exception(sleep_interval: 1.0) do
- within_element(:new_project_or_subgroup_dropdown) do
- # May need to click again because it is possible to click the button quicker than the JS is bound
- wait_until(reload: false) do
- click_element :new_project_or_subgroup_dropdown_toggle
-
- has_element?(kind)
- end
-
- click_element kind
- end
- end
- end
end
end
end
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index 1cb33a7c71c..2e29ec9a6a7 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -44,7 +44,7 @@ module QA
# Ensure that the group was actually created
group_show.wait_until(sleep_interval: 1) do
group_show.has_text?(path) &&
- group_show.has_new_project_or_subgroup_dropdown?
+ group_show.has_new_project_and_new_subgroup_buttons?
end
end
end
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index 24048aea838..c4e040b0287 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -123,20 +123,6 @@ RSpec.describe Projects::Ci::LintsController do
subject
end
-
- context 'when dry_run feature flag is disabled' do
- before do
- stub_feature_flags(ci_lint_creates_pipeline_with_dry_run: false)
- end
-
- it_behaves_like 'returns a successful validation'
-
- it 'runs validations through YamlProcessor' do
- expect(Gitlab::Ci::YamlProcessor).to receive(:new).and_call_original
-
- subject
- end
- end
end
end
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 420d818daeb..07fb03b39c6 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -201,102 +201,7 @@ RSpec.describe Projects::ReleasesController do
end
end
- context 'GET #downloads' do
- subject do
- get :downloads, params: { namespace_id: project.namespace, project_id: project, tag: tag, filepath: filepath }
- end
-
- before do
- sign_in(user)
- end
-
- let(:release) { create(:release, project: project, tag: tag ) }
- let!(:link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: 'https://downloads.example.com/bin/gitlab-linux-amd64') }
- let(:tag) { 'v11.9.0-rc2' }
-
- context 'valid filepath' do
- let(:filepath) { CGI.escape('/binaries/linux-amd64') }
-
- it 'redirects to the asset direct link' do
- subject
-
- expect(response).to redirect_to('https://downloads.example.com/bin/gitlab-linux-amd64')
- end
-
- it 'redirects with a status of 302' do
- subject
-
- expect(response).to have_gitlab_http_status(:redirect)
- end
- end
-
- context 'invalid filepath' do
- let(:filepath) { CGI.escape('/binaries/win32') }
-
- it 'is not found' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'GET #downloads' do
- subject do
- get :downloads, params: {
- namespace_id: project.namespace,
- project_id: project,
- tag: tag,
- filepath: filepath
- }
- end
-
- before do
- sign_in(user)
- end
-
- let(:release) { create(:release, project: project, tag: tag ) }
- let(:tag) { 'v11.9.0-rc2' }
- let(:db_filepath) { '/binaries/linux-amd64' }
- let!(:link) do
- create :release_link,
- release: release,
- name: 'linux-amd64 binaries',
- filepath: db_filepath,
- url: 'https://downloads.example.com/bin/gitlab-linux-amd64'
- end
-
- context 'valid filepath' do
- let(:filepath) { CGI.escape('/binaries/linux-amd64') }
-
- it 'redirects to the asset direct link' do
- subject
-
- expect(response).to redirect_to(link.url)
- end
- end
-
- context 'invalid filepath' do
- let(:filepath) { CGI.escape('/binaries/win32') }
-
- it 'is not found' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'ignores filepath extension' do
- let(:db_filepath) { '/binaries/linux-amd64.json' }
- let(:filepath) { CGI.escape(db_filepath) }
-
- it 'redirects to the asset direct link' do
- subject
-
- expect(response).to redirect_to(link.url)
- end
- end
- end
+ # `GET #downloads` is addressed in spec/requests/projects/releases_controller_spec.rb
private
diff --git a/spec/factories/bulk_import/trackers.rb b/spec/factories/bulk_import/trackers.rb
new file mode 100644
index 00000000000..7a1fa0849fc
--- /dev/null
+++ b/spec/factories/bulk_import/trackers.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :bulk_import_tracker, class: 'BulkImports::Tracker' do
+ association :entity, factory: :bulk_import_entity
+
+ relation { :relation }
+ has_next_page { false }
+ end
+end
diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb
index acac8724edf..1b23b8b4bf9 100644
--- a/spec/features/groups/container_registry_spec.rb
+++ b/spec/features/groups/container_registry_spec.rb
@@ -89,6 +89,20 @@ RSpec.describe 'Container Registry', :js do
end
end
+ context 'when an image has the same name as the subgroup' do
+ before do
+ stub_container_registry_tags(tags: %w[latest], with_manifest: true)
+ project.container_repositories << create(:container_repository, name: group.name)
+ visit_container_registry
+ end
+
+ it 'details page loads properly' do
+ find('a[data-testid="details-link"]').click
+
+ expect(page).to have_content 'latest'
+ end
+ end
+
def visit_container_registry
visit group_container_registries_path(group)
end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 304573ecd6e..97732374eb9 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -81,8 +81,7 @@ RSpec.describe 'Group show page' do
it 'allows creating subgroups' do
visit path
- expect(page)
- .to have_css("li[data-text='New subgroup']", visible: false)
+ expect(page).to have_link('New subgroup')
end
end
end
@@ -102,8 +101,7 @@ RSpec.describe 'Group show page' do
path = group_path(relaxed_group)
visit path
- expect(page)
- .to have_css("li[data-text='New subgroup']", visible: false)
+ expect(page).to have_link('New subgroup')
end
end
@@ -116,9 +114,7 @@ RSpec.describe 'Group show page' do
path = group_path(restricted_group)
visit path
- expect(page)
- .not_to have_selector("li[data-text='New subgroup']",
- visible: false)
+ expect(page).not_to have_link('New subgroup')
end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 8264ec2eddd..b9fd3a1a5cc 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -294,35 +294,43 @@ RSpec.describe 'Group' do
describe 'new subgroup / project button' do
let(:group) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS, subgroup_creation_level: Gitlab::Access::OWNER_SUBGROUP_ACCESS) }
- it 'new subgroup button is displayed without project creation permission' do
- visit group_path(group)
+ context 'when user has subgroup creation permissions but not project creation permissions' do
+ it 'only displays "New subgroup" button' do
+ visit group_path(group)
- page.within '.group-buttons' do
- expect(page).to have_link('New subgroup')
+ page.within '[data-testid="group-buttons"]' do
+ expect(page).to have_link('New subgroup')
+ expect(page).not_to have_link('New project')
+ end
end
end
- it 'new subgroup button is displayed together with new project button when having project creation permission' do
- group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
- visit group_path(group)
+ context 'when user has project creation permissions but not subgroup creation permissions' do
+ it 'only displays "New project" button' do
+ group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ user = create(:user)
- page.within '.group-buttons' do
- expect(page).to have_css("li[data-text='New subgroup']", visible: false)
- expect(page).to have_css("li[data-text='New project']", visible: false)
+ group.add_maintainer(user)
+ sign_out(:user)
+ sign_in(user)
+
+ visit group_path(group)
+ page.within '[data-testid="group-buttons"]' do
+ expect(page).to have_link('New project')
+ expect(page).not_to have_link('New subgroup')
+ end
end
end
- it 'new project button is displayed without subgroup creation permission' do
- group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
- user = create(:user)
-
- group.add_maintainer(user)
- sign_out(:user)
- sign_in(user)
+ context 'when user has project and subgroup creation permissions' do
+ it 'displays "New subgroup" and "New project" buttons' do
+ group.update!(project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ visit group_path(group)
- visit group_path(group)
- page.within '.group-buttons' do
- expect(page).to have_link('New project')
+ page.within '[data-testid="group-buttons"]' do
+ expect(page).to have_link('New subgroup')
+ expect(page).to have_link('New project')
+ end
end
end
end
diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb
index 7514a26f020..45bf35a6aab 100644
--- a/spec/features/projects/container_registry_spec.rb
+++ b/spec/features/projects/container_registry_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe 'Container Registry', :js do
create(:container_repository, name: 'my/image')
end
+ let(:nameless_container_repository) do
+ create(:container_repository, name: '')
+ end
+
before do
sign_in(user)
project.add_developer(user)
@@ -96,6 +100,20 @@ RSpec.describe 'Container Registry', :js do
end
end
+ describe 'image repo details when image has no name' do
+ before do
+ stub_container_registry_tags(tags: %w[latest], with_manifest: true)
+ project.container_repositories << nameless_container_repository
+ visit_container_registry
+ end
+
+ it 'renders correctly' do
+ find('a[data-testid="details-link"]').click
+
+ expect(page).to have_content 'latest'
+ end
+ end
+
context 'when there are more than 10 images' do
before do
create_list(:container_repository, 12, project: project)
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 6ca5073c5a4..aabbc8cea7b 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe 'User views releases', :js do
context 'when there is a link as an asset' do
let!(:release_link) { create(:release_link, release: release_v1, url: url ) }
let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
- let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release_v1) << release_link.filepath }
+ let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release_v1) << "/downloads#{release_link.filepath}" }
it 'sees the link' do
page.within("##{release_v1.tag} .js-assets-list") do
diff --git a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js b/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
index c7a9db82bea..04ecefa7140 100644
--- a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
+++ b/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
@@ -24,11 +24,14 @@ const mockIntegrations = [
describe('AlertIntegrationsList', () => {
let wrapper;
- function mountComponent(propsData = {}) {
+ function mountComponent({ data = {}, props = {} } = {}) {
wrapper = mount(AlertIntegrationsList, {
+ data() {
+ return { ...data };
+ },
propsData: {
integrations: mockIntegrations,
- ...propsData,
+ ...props,
},
stubs: {
GlIcon: true,
@@ -57,7 +60,7 @@ describe('AlertIntegrationsList', () => {
});
it('renders an empty state when no integrations provided', () => {
- mountComponent({ integrations: [] });
+ mountComponent({ props: { integrations: [] } });
expect(findTableComponent().text()).toContain(i18n.emptyState);
});
@@ -66,7 +69,7 @@ describe('AlertIntegrationsList', () => {
});
it('renders an highlighted row when a current integration is selected to edit', () => {
- mountComponent({ currentIntegration: { id: '1' } });
+ mountComponent({ data: { currentIntegration: { id: '1' } } });
expect(
findTableComponentRows()
.at(0)
diff --git a/spec/frontend/alerts_settings/alerts_settings_form_new_spec.js b/spec/frontend/alerts_settings/alerts_settings_form_new_spec.js
index d5a1239f701..97a53c20b6b 100644
--- a/spec/frontend/alerts_settings/alerts_settings_form_new_spec.js
+++ b/spec/frontend/alerts_settings/alerts_settings_form_new_spec.js
@@ -152,9 +152,9 @@ describe('AlertsSettingsFormNew', () => {
createComponent({
data: {
selectedIntegration: typeSet.http,
+ currentIntegration: { id: '1', name: 'Test integration pre' },
},
props: {
- currentIntegration: { id: '1', name: 'Test integration pre' },
loading: false,
},
});
@@ -183,9 +183,9 @@ describe('AlertsSettingsFormNew', () => {
createComponent({
data: {
selectedIntegration: typeSet.prometheus,
+ currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
},
props: {
- currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
loading: false,
},
});
@@ -219,10 +219,10 @@ describe('AlertsSettingsFormNew', () => {
createComponent({
data: {
selectedIntegration: typeSet.http,
+ currentIntegration: { id: '1', name: 'Test' },
active: true,
},
props: {
- currentIntegration: { id: '1', name: 'Test' },
loading: false,
},
});
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
index e85ee39f165..9f7a2758ae1 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
@@ -19,7 +19,7 @@ describe('Image List Row', () => {
let wrapper;
const item = imagesListResponse.data[0];
- const findDetailsLink = () => wrapper.find('[data-testid="detailsLink"]');
+ const findDetailsLink = () => wrapper.find('[data-testid="details-link"]');
const findTagsCount = () => wrapper.find('[data-testid="tagsCount"]');
const findDeleteBtn = () => wrapper.find(DeleteButton);
const findClipboardButton = () => wrapper.find(ClipboardButton);
diff --git a/spec/frontend/registry/explorer/stores/actions_spec.js b/spec/frontend/registry/explorer/stores/actions_spec.js
index 17ce6a7054d..dcd4d8015a4 100644
--- a/spec/frontend/registry/explorer/stores/actions_spec.js
+++ b/spec/frontend/registry/explorer/stores/actions_spec.js
@@ -8,6 +8,11 @@ import * as actions from '~/registry/explorer/stores/actions';
import * as types from '~/registry/explorer/stores/mutation_types';
import { reposServerResponse, registryServerResponse } from '../mock_data';
import * as utils from '~/registry/explorer/utils';
+import {
+ FETCH_IMAGES_LIST_ERROR_MESSAGE,
+ FETCH_TAGS_LIST_ERROR_MESSAGE,
+ FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
+} from '~/registry/explorer/constants/index';
jest.mock('~/flash.js');
jest.mock('~/registry/explorer/utils');
@@ -129,7 +134,7 @@ describe('Actions RegistryExplorer Store', () => {
],
[],
() => {
- expect(createFlash).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
done();
},
);
@@ -169,7 +174,7 @@ describe('Actions RegistryExplorer Store', () => {
],
[],
() => {
- expect(createFlash).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: FETCH_TAGS_LIST_ERROR_MESSAGE });
done();
},
);
@@ -261,7 +266,7 @@ describe('Actions RegistryExplorer Store', () => {
],
[],
() => {
- expect(createFlash).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: FETCH_IMAGE_DETAILS_ERROR_MESSAGE });
done();
},
);
diff --git a/spec/frontend/registry/explorer/utils_spec.js b/spec/frontend/registry/explorer/utils_spec.js
index 365bfc9ea19..0cd4a1cec29 100644
--- a/spec/frontend/registry/explorer/utils_spec.js
+++ b/spec/frontend/registry/explorer/utils_spec.js
@@ -16,6 +16,20 @@ describe('Utils', () => {
expect(pathGenerator(imageDetails, '/foo')).toBe('/foo/bar/registry/repository/1/tags/foo');
});
+ it.each`
+ path | name | result
+ ${'foo/foo'} | ${''} | ${'/foo/foo/registry/repository/1/tags?format=json'}
+ ${'foo/foo/foo'} | ${'foo'} | ${'/foo/foo/registry/repository/1/tags?format=json'}
+ ${'baz/foo/foo/foo'} | ${'foo'} | ${'/baz/foo/foo/registry/repository/1/tags?format=json'}
+ ${'baz/foo/foo/foo'} | ${'foo'} | ${'/baz/foo/foo/registry/repository/1/tags?format=json'}
+ ${'foo/foo/baz/foo/foo'} | ${'foo/foo'} | ${'/foo/foo/baz/registry/repository/1/tags?format=json'}
+ ${'foo/foo/baz/foo/bar'} | ${'foo/bar'} | ${'/foo/foo/baz/registry/repository/1/tags?format=json'}
+ ${'baz/foo/foo'} | ${'foo'} | ${'/baz/foo/registry/repository/1/tags?format=json'}
+ ${'baz/foo/bar'} | ${'foo'} | ${'/baz/foo/bar/registry/repository/1/tags?format=json'}
+ `('returns the correct path when path is $path and name is $name', ({ name, path, result }) => {
+ expect(pathGenerator({ id: 1, name, path })).toBe(result);
+ });
+
it('returns the url unchanged when imageDetails have no name', () => {
const imageDetailsWithoutName = {
path: 'foo/bar/baz',
diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap
index aefd1edb87e..f49d3d7b716 100644
--- a/spec/frontend/releases/__snapshots__/util_spec.js.snap
+++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap
@@ -18,7 +18,7 @@ Object {
"count": 8,
"links": Array [
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-3",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/awesome-app-3",
"external": true,
"id": "gid://gitlab/Releases::Link/13",
"linkType": "image",
@@ -26,7 +26,7 @@ Object {
"url": "https://example.com/image",
},
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-2",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/awesome-app-2",
"external": true,
"id": "gid://gitlab/Releases::Link/12",
"linkType": "package",
@@ -34,7 +34,7 @@ Object {
"url": "https://example.com/package",
},
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-1",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/awesome-app-1",
"external": false,
"id": "gid://gitlab/Releases::Link/11",
"linkType": "runbook",
@@ -42,7 +42,7 @@ Object {
"url": "http://localhost/releases-namespace/releases-project/runbook",
},
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/linux-amd64",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/linux-amd64",
"external": true,
"id": "gid://gitlab/Releases::Link/10",
"linkType": "other",
@@ -146,7 +146,7 @@ Object {
"count": 8,
"links": Array [
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-3",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/awesome-app-3",
"external": true,
"id": "gid://gitlab/Releases::Link/13",
"linkType": "image",
@@ -154,7 +154,7 @@ Object {
"url": "https://example.com/image",
},
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-2",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/awesome-app-2",
"external": true,
"id": "gid://gitlab/Releases::Link/12",
"linkType": "package",
@@ -162,7 +162,7 @@ Object {
"url": "https://example.com/package",
},
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-1",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/awesome-app-1",
"external": false,
"id": "gid://gitlab/Releases::Link/11",
"linkType": "runbook",
@@ -170,7 +170,7 @@ Object {
"url": "http://localhost/releases-namespace/releases-project/runbook",
},
Object {
- "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/linux-amd64",
+ "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/downloads/binaries/linux-amd64",
"external": true,
"id": "gid://gitlab/Releases::Link/10",
"linkType": "other",
diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb
index fb1baafe7bd..82a5e3a62f5 100644
--- a/spec/graphql/mutations/commits/create_spec.rb
+++ b/spec/graphql/mutations/commits/create_spec.rb
@@ -5,8 +5,9 @@ require 'spec_helper'
RSpec.describe Mutations::Commits::Create do
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
- let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+
let(:context) do
GraphQL::Query::Context.new(
query: OpenStruct.new(schema: nil),
@@ -18,9 +19,10 @@ RSpec.describe Mutations::Commits::Create do
specify { expect(described_class).to require_graphql_authorizations(:push_code) }
describe '#resolve' do
- subject { mutation.resolve(project_path: project.full_path, branch: branch, message: message, actions: actions) }
+ subject { mutation.resolve(project_path: project.full_path, branch: branch, start_branch: start_branch, message: message, actions: actions) }
let(:branch) { 'master' }
+ let(:start_branch) { nil }
let(:message) { 'Commit message' }
let(:actions) do
[
@@ -142,6 +144,29 @@ RSpec.describe Mutations::Commits::Create do
end
end
+ context 'when branch does not exist and a start branch is provided' do
+ let(:branch) { 'my-branch' }
+ let(:start_branch) { 'master' }
+ let(:actions) do
+ [
+ {
+ action: 'create',
+ file_path: 'ANOTHER_FILE.md',
+ content: 'Bye'
+ }
+ ]
+ end
+
+ it 'returns a new commit' do
+ expect(mutated_commit).to have_attributes(message: message, project: project)
+ expect(subject[:errors]).to be_empty
+
+ expect_to_contain_deltas([
+ a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md')
+ ])
+ end
+ end
+
context 'when message is not set' do
let(:message) { nil }
diff --git a/spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb b/spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb
new file mode 100644
index 00000000000..c2daa35703d
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillMergeRequestCleanupSchedules, schema: 20201103110018 do
+ let(:merge_requests) { table(:merge_requests) }
+ let(:cleanup_schedules) { table(:merge_request_cleanup_schedules) }
+ let(:metrics) { table(:merge_request_metrics) }
+
+ let(:namespace) { table(:namespaces).create!(name: 'name', path: 'path') }
+ let(:project) { table(:projects).create!(namespace_id: namespace.id) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ let!(:open_mr) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master') }
+
+ let!(:closed_mr_1) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) }
+ let!(:closed_mr_2) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) }
+ let!(:closed_mr_1_metrics) { metrics.create!(merge_request_id: closed_mr_1.id, target_project_id: project.id, latest_closed_at: Time.current, created_at: Time.current, updated_at: Time.current) }
+ let!(:closed_mr_2_metrics) { metrics.create!(merge_request_id: closed_mr_2.id, target_project_id: project.id, latest_closed_at: Time.current, created_at: Time.current, updated_at: Time.current) }
+ let!(:closed_mr_2_cleanup_schedule) { cleanup_schedules.create!(merge_request_id: closed_mr_2.id, scheduled_at: Time.current) }
+
+ let!(:merged_mr_1) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 3) }
+ let!(:merged_mr_2) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 3, updated_at: Time.current) }
+ let!(:merged_mr_1_metrics) { metrics.create!(merge_request_id: merged_mr_1.id, target_project_id: project.id, merged_at: Time.current, created_at: Time.current, updated_at: Time.current) }
+
+ let!(:closed_mr_3) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) }
+ let!(:closed_mr_3_metrics) { metrics.create!(merge_request_id: closed_mr_3.id, target_project_id: project.id, latest_closed_at: Time.current, created_at: Time.current, updated_at: Time.current) }
+
+ it 'creates records for all closed and merged merge requests in range' do
+ expect(Gitlab::BackgroundMigration::Logger).to receive(:info).with(
+ message: 'Backfilled merge_request_cleanup_schedules records',
+ count: 3
+ )
+
+ subject.perform(open_mr.id, merged_mr_2.id)
+
+ aggregate_failures do
+ expect(cleanup_schedules.all.pluck(:merge_request_id))
+ .to contain_exactly(closed_mr_1.id, closed_mr_2.id, merged_mr_1.id, merged_mr_2.id)
+ expect(cleanup_schedules.find_by(merge_request_id: closed_mr_1.id).scheduled_at.to_s)
+ .to eq((closed_mr_1_metrics.latest_closed_at + 14.days).to_s)
+ expect(cleanup_schedules.find_by(merge_request_id: closed_mr_2.id).scheduled_at.to_s)
+ .to eq(closed_mr_2_cleanup_schedule.scheduled_at.to_s)
+ expect(cleanup_schedules.find_by(merge_request_id: merged_mr_1.id).scheduled_at.to_s)
+ .to eq((merged_mr_1_metrics.merged_at + 14.days).to_s)
+ expect(cleanup_schedules.find_by(merge_request_id: merged_mr_2.id).scheduled_at.to_s)
+ .to eq((merged_mr_2.updated_at + 14.days).to_s)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_merge_request_cleanup_schedules_backfill_spec.rb b/spec/migrations/schedule_merge_request_cleanup_schedules_backfill_spec.rb
new file mode 100644
index 00000000000..fa8dd38619a
--- /dev/null
+++ b/spec/migrations/schedule_merge_request_cleanup_schedules_backfill_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe ScheduleMergeRequestCleanupSchedulesBackfill, :sidekiq, schema: 20201023114628 do
+ let(:merge_requests) { table(:merge_requests) }
+ let(:cleanup_schedules) { table(:merge_request_cleanup_schedules) }
+
+ let(:namespace) { table(:namespaces).create!(name: 'name', path: 'path') }
+ let(:project) { table(:projects).create!(namespace_id: namespace.id) }
+
+ describe '#up' do
+ let!(:open_mr) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master') }
+
+ let!(:closed_mr_1) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) }
+ let!(:closed_mr_2) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) }
+
+ let!(:merged_mr_1) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 3) }
+ let!(:merged_mr_2) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 3) }
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+ end
+
+ it 'schdules BackfillMergeRequestCleanupSchedules background jobs' do
+ Sidekiq::Testing.fake! do
+ migrate!
+
+ aggregate_failures do
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(2.minutes, closed_mr_1.id, closed_mr_2.id)
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(4.minutes, merged_mr_1.id, merged_mr_2.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/bulk_imports/tracker_spec.rb b/spec/models/bulk_imports/tracker_spec.rb
new file mode 100644
index 00000000000..8eb5a6c27dd
--- /dev/null
+++ b/spec/models/bulk_imports/tracker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Tracker, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:entity).required }
+ end
+
+ describe 'validations' do
+ before do
+ create(:bulk_import_tracker)
+ end
+
+ it { is_expected.to validate_presence_of(:relation) }
+ it { is_expected.to validate_uniqueness_of(:relation).scoped_to(:bulk_import_entity_id) }
+
+ context 'when has_next_page is true' do
+ it "validates presence of `next_page`" do
+ tracker = build(:bulk_import_tracker, has_next_page: true)
+
+ expect(tracker).not_to be_valid
+ expect(tracker.errors).to include(:next_page)
+ end
+ end
+ end
+end
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index 946541a0602..d3ce2f2d48f 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -375,14 +375,6 @@ RSpec.describe DesignManagement::Design do
end
it { is_expected.to contain_exactly(version_author, note_author, mentioned_user) }
-
- context 'when the feature flag is disabled' do
- before do
- stub_feature_flags(design_management_design_notification_participants: false)
- end
-
- it { is_expected.to be_empty }
- end
end
end
diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb
index ac4fa7cfe83..375d4f10b40 100644
--- a/spec/requests/api/graphql/mutations/commits/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb
@@ -23,6 +23,18 @@ RSpec.describe 'Creation of a new commit' do
let(:mutation) { graphql_mutation(:commit_create, input) }
let(:mutation_response) { graphql_mutation_response(:commit_create) }
+ shared_examples 'a commit is successful' do
+ it 'creates a new commit' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+
+ expect(mutation_response['commit']).to include(
+ 'title' => message
+ )
+ end
+ end
+
context 'the user is not allowed to create a commit' do
it_behaves_like 'a mutation that returns a top-level access error'
end
@@ -32,14 +44,7 @@ RSpec.describe 'Creation of a new commit' do
project.add_developer(current_user)
end
- it 'creates a new commit' do
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['commit']).to include(
- 'title' => message
- )
- end
+ it_behaves_like 'a commit is successful'
context 'when branch is not correct' do
let(:branch) { 'unknown' }
@@ -47,5 +52,22 @@ RSpec.describe 'Creation of a new commit' do
it_behaves_like 'a mutation that returns errors in the response',
errors: ['You can only create or edit files when you are on a branch']
end
+
+ context 'when branch is new, and a start_branch is defined' do
+ let(:input) { { project_path: project.full_path, branch: branch, start_branch: start_branch, message: message, actions: actions } }
+ let(:branch) { 'new-branch' }
+ let(:start_branch) { 'master' }
+ let(:actions) do
+ [
+ {
+ action: 'CREATE',
+ filePath: 'ANOTHER_FILE.md',
+ content: 'Bye'
+ }
+ ]
+ end
+
+ it_behaves_like 'a commit is successful'
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/releases/create_spec.rb b/spec/requests/api/graphql/mutations/releases/create_spec.rb
index 2402cf62a49..d745eb3083d 100644
--- a/spec/requests/api/graphql/mutations/releases/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/releases/create_spec.rb
@@ -121,7 +121,7 @@ RSpec.describe 'Creation of a new release' do
create_release
release = mutation_response[:release]
- expected_direct_asset_url = Gitlab::Routing.url_helpers.project_release_url(project, Release.find_by(tag: tag_name)) << asset_link[:directAssetPath]
+ expected_direct_asset_url = Gitlab::Routing.url_helpers.project_release_url(project, Release.find_by(tag: tag_name)) << "/downloads#{asset_link[:directAssetPath]}"
expected_attributes = {
tagName: tag_name,
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index 57b620dbdf7..57dbe258ce4 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -147,7 +147,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
'name' => link.name,
'url' => link.url,
'external' => link.external?,
- 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << link.filepath : link.url
+ 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url
}
end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index 82d0d64eba4..c03dd0331cf 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -146,7 +146,7 @@ RSpec.describe API::Release::Links do
specify do
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{link.id}", maintainer)
- expect(json_response['direct_asset_url']).to eq("http://localhost/#{project.namespace.path}/#{project.name}/-/releases/#{release.tag}/bin/bigfile.exe")
+ expect(json_response['direct_asset_url']).to eq("http://localhost/#{project.namespace.path}/#{project.name}/-/releases/#{release.tag}/downloads/bin/bigfile.exe")
end
end
diff --git a/spec/requests/projects/releases_controller_spec.rb b/spec/requests/projects/releases_controller_spec.rb
new file mode 100644
index 00000000000..8e492125ace
--- /dev/null
+++ b/spec/requests/projects/releases_controller_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Projects::ReleasesController' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ login_as(user)
+ end
+
+ # Added as a request spec because of https://gitlab.com/gitlab-org/gitlab/-/issues/232386
+ describe 'GET #downloads' do
+ context 'filepath redirection' do
+ let_it_be(:release) { create(:release, project: project, tag: 'v11.9.0-rc2' ) }
+ let!(:link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: filepath, url: 'https://aws.example.com/s3/project/bin/hello-darwin-amd64') }
+ let_it_be(:url) { "#{project_releases_path(project)}/#{release.tag}/downloads/bin/darwin-amd64" }
+
+ let(:subject) { get url }
+
+ context 'valid filepath' do
+ let(:filepath) { '/bin/darwin-amd64' }
+
+ it 'redirects to the asset direct link' do
+ subject
+
+ expect(response).to redirect_to('https://aws.example.com/s3/project/bin/hello-darwin-amd64')
+ end
+
+ it 'redirects with a status of 302' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ end
+ end
+
+ context 'invalid filepath' do
+ let(:filepath) { '/binaries/win32' }
+
+ it 'is not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'invalid filepath' do
+ let(:invalid_filepath) { 'bin/darwin-amd64' }
+
+ let(:subject) { create(:release_link, name: 'linux-amd64 binaries', filepath: invalid_filepath, url: 'https://aws.example.com/s3/project/bin/hello-darwin-amd64') }
+
+ it 'cannot create an invalid filepath' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index e4ec3e56c20..6f7225fadbf 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -867,23 +867,6 @@ RSpec.describe NotificationService, :mailer do
should_not_email(non_member_and_mentioned)
should_not_email(note.author)
end
-
- context 'when the feature flag is disabled' do
- before do
- stub_feature_flags(design_management_design_notification_participants: false)
- end
-
- it 'sends a new note notification only to the mentioned member', :aggregate_failures do
- notification.new_note(note)
-
- should_email(member_and_mentioned)
- should_not_email(design.authors.first)
- should_not_email(member_and_author_of_second_note)
- should_not_email(member_and_not_mentioned)
- should_not_email(non_member_and_mentioned)
- should_not_email(note.author)
- end
- end
end
context 'design management is disabled' do