summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-13 21:08:59 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-13 21:08:59 +0000
commitd466ee5042520ad078fe050cb078d81dc2ebe196 (patch)
tree5648eb1aee8aeff5b5c5ff4669a184fd7676f778
parent6a9d7c009e4e5975a89bcc3e458da4b3ec484bd1 (diff)
downloadgitlab-ce-d466ee5042520ad078fe050cb078d81dc2ebe196.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--CHANGELOG-EE.md4
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/ide/components/error_message.vue41
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue3
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue4
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_component_mixin.js4
-rw-r--r--app/assets/javascripts/pipelines/stores/pipeline_store.js3
-rw-r--r--app/assets/javascripts/serverless/components/function_details.vue4
-rw-r--r--app/assets/javascripts/serverless/components/function_row.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue117
-rw-r--r--app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue20
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/repository.rb12
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb16
-rw-r--r--changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml5
-rw-r--r--changelogs/unreleased/35671-api-repository-commits-with-order.yml5
-rw-r--r--changelogs/unreleased/36854-webide-use-gl-alert.yml5
-rw-r--r--changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml5
-rw-r--r--changelogs/unreleased/leipert-fix-user-label-bug.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-15.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-23.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-26.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-28.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-30.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-32.yml5
-rw-r--r--changelogs/unreleased/resolve_gitlab_issue_196651.yml5
-rw-r--r--db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb43
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/user/project/operations/error_tracking.md5
-rw-r--r--lib/api/commits.rb5
-rw-r--r--lib/api/entities.rb253
-rw-r--r--lib/api/entities/badge.rb12
-rw-r--r--lib/api/entities/basic_badge_details.rb17
-rw-r--r--lib/api/entities/cluster.rb14
-rw-r--r--lib/api/entities/cluster_group.rb9
-rw-r--r--lib/api/entities/cluster_project.rb9
-rw-r--r--lib/api/entities/deployment.rb13
-rw-r--r--lib/api/entities/environment.rb11
-rw-r--r--lib/api/entities/environment_basic.rb9
-rw-r--r--lib/api/entities/group_access.rb8
-rw-r--r--lib/api/entities/job_request/artifacts.rb17
-rw-r--r--lib/api/entities/job_request/cache.rb11
-rw-r--r--lib/api/entities/job_request/credentials.rb11
-rw-r--r--lib/api/entities/job_request/dependency.rb12
-rw-r--r--lib/api/entities/job_request/git_info.rb14
-rw-r--r--lib/api/entities/job_request/image.rb12
-rw-r--r--lib/api/entities/job_request/job_info.rb12
-rw-r--r--lib/api/entities/job_request/port.rb11
-rw-r--r--lib/api/entities/job_request/response.rb35
-rw-r--r--lib/api/entities/job_request/runner_info.rb12
-rw-r--r--lib/api/entities/job_request/service.rb11
-rw-r--r--lib/api/entities/job_request/step.rb11
-rw-r--r--lib/api/entities/license.rb14
-rw-r--r--lib/api/entities/license_basic.rb11
-rw-r--r--lib/api/entities/member_access.rb14
-rw-r--r--lib/api/entities/namespace.rb15
-rw-r--r--lib/api/entities/notification_setting.rb14
-rw-r--r--lib/api/entities/pages_domain.rb20
-rw-r--r--lib/api/entities/pages_domain_basic.rb22
-rw-r--r--lib/api/entities/pages_domain_certificate.rb12
-rw-r--r--lib/api/entities/project_access.rb8
-rw-r--r--lib/api/entities/resource_label_event.rb19
-rw-r--r--lib/api/entities/suggestion.rb15
-rw-r--r--lib/gitlab/background_migration/recalculate_project_authorizations.rb42
-rw-r--r--lib/gitlab/diff/file.rb6
-rw-r--r--lib/gitlab/git/commit.rb3
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb1
-rw-r--r--lib/gitlab/project_authorizations.rb12
-rw-r--r--package.json1
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb2
-rw-r--r--spec/factories/project_authorizations.rb9
-rw-r--r--spec/frontend/ide/components/error_message_spec.js29
-rw-r--r--spec/javascripts/ide/components/ide_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js211
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb243
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb15
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb103
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_spec.rb57
-rw-r--r--spec/models/project_spec.rb17
-rw-r--r--spec/models/repository_spec.rb8
-rw-r--r--spec/requests/api/commits_spec.rb35
-rw-r--r--spec/requests/api/issues/issues_spec.rb9
-rw-r--r--spec/requests/api/merge_requests_spec.rb290
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb36
-rw-r--r--spec/support/helpers/api_helpers.rb11
-rw-r--r--yarn.lock5
91 files changed, 1483 insertions, 728 deletions
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md
index 3e09b0ef702..f1224820fe4 100644
--- a/CHANGELOG-EE.md
+++ b/CHANGELOG-EE.md
@@ -106,6 +106,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Remove "creations" in gitlab_subscription_histories on gitlab.com. !22278
+## 12.6.7
+
+- No changes.
+
## 12.6.6
- No changes.
diff --git a/Gemfile b/Gemfile
index c203a98567c..031389dcc90 100644
--- a/Gemfile
+++ b/Gemfile
@@ -455,7 +455,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 1.85.0'
+gem 'gitaly', '~> 1.86.0'
gem 'grpc', '~> 1.24.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 276f9da13c7..817648c606f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -375,7 +375,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.5.0)
- gitaly (1.85.0)
+ gitaly (1.86.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-chronic (0.10.5)
@@ -1230,7 +1230,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly (~> 1.85.0)
+ gitaly (~> 1.86.0)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.9.1)
diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue
index 500f6737839..d36adbd798e 100644
--- a/app/assets/javascripts/ide/components/error_message.vue
+++ b/app/assets/javascripts/ide/components/error_message.vue
@@ -1,9 +1,10 @@
<script>
import { mapActions } from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
+ GlAlert,
GlLoadingIcon,
},
props: {
@@ -17,9 +18,14 @@ export default {
isLoading: false,
};
},
+ computed: {
+ canDismiss() {
+ return !this.message.action;
+ },
+ },
methods: {
...mapActions(['setErrorMessage']),
- clickAction() {
+ doAction() {
if (this.isLoading) return;
this.isLoading = true;
@@ -33,28 +39,23 @@ export default {
this.isLoading = false;
});
},
- clickFlash() {
- if (!this.message.action) {
- this.setErrorMessage(null);
- }
+ dismiss() {
+ this.setErrorMessage(null);
},
},
};
</script>
<template>
- <div class="flash-container flash-container-page" @click="clickFlash">
- <div class="flash-alert" data-qa-selector="flash_alert">
- <span v-html="message.text"> </span>
- <button
- v-if="message.action"
- type="button"
- class="flash-action text-white p-0 border-top-0 border-right-0 border-left-0 bg-transparent"
- @click.stop.prevent="clickAction"
- >
- {{ message.actionText }}
- <gl-loading-icon v-show="isLoading" inline />
- </button>
- </div>
- </div>
+ <gl-alert
+ data-qa-selector="flash_alert"
+ variant="danger"
+ :dismissible="canDismiss"
+ :primary-button-text="message.actionText"
+ @dismiss="dismiss"
+ @primaryAction="doAction"
+ >
+ <span v-html="message.text"></span>
+ <gl-loading-icon v-show="isLoading" inline class="vertical-align-middle ml-1" />
+ </gl-alert>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 4dc6e51d2fc..6a836adba01 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,5 +1,4 @@
<script>
-import _ from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from './stage_column_component.vue';
import GraphMixin from '../../mixins/graph_component_mixin';
@@ -70,7 +69,7 @@ export default {
expandedTriggeredBy() {
return (
this.pipeline.triggered_by &&
- _.isArray(this.pipeline.triggered_by) &&
+ Array.isArray(this.pipeline.triggered_by) &&
this.pipeline.triggered_by.find(el => el.isExpanded)
);
},
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index db7714808fd..3d3dabbdf22 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty, escape as esc } from 'lodash';
import stageColumnMixin from '../../mixins/stage_column_mixin';
import JobItem from './job_item.vue';
import JobGroupDropdown from './job_group_dropdown.vue';
@@ -39,12 +39,12 @@ export default {
},
computed: {
hasAction() {
- return !_.isEmpty(this.action);
+ return !isEmpty(this.action);
},
},
methods: {
groupId(group) {
- return `ci-badge-${_.escape(group.name)}`;
+ return `ci-badge-${esc(group.name)}`;
},
pipelineActionRequestComplete() {
this.$emit('refreshPipelineGraph');
diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
index 6ca96bbba5e..f604edd8859 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import { GlLink } from '@gitlab/ui';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@@ -43,7 +43,7 @@ export default {
);
},
hasRef() {
- return !_.isEmpty(this.pipeline.ref);
+ return !isEmpty(this.pipeline.ref);
},
},
methods: {
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 743c3ea271d..0c9d242f509 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -1,11 +1,11 @@
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
-import _ from 'underscore';
+import { escape } from 'lodash';
import { __, sprintf } from '~/locale';
import popover from '~/vue_shared/directives/popover';
const popoverTitle = sprintf(
- _.escape(
+ escape(
__(
`This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}`,
),
@@ -49,7 +49,7 @@ export default {
href="${this.autoDevopsHelpPath}"
target="_blank"
rel="noopener noreferrer nofollow">
- ${_.escape(__('Learn more about Auto DevOps'))}
+ ${escape(__('Learn more about Auto DevOps'))}
</a>`,
};
},
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index d730ef41b1a..accd6bf71f4 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEqual } from 'lodash';
import { __, sprintf, s__ } from '../../locale';
import createFlash from '../../flash';
import PipelinesService from '../services/pipelines_service';
@@ -218,7 +218,7 @@ export default {
successCallback(resp) {
// Because we are polling & the user is interacting verify if the response received
// matches the last request made
- if (_.isEqual(resp.config.params, this.requestData)) {
+ if (isEqual(resp.config.params, this.requestData)) {
this.store.storeCount(resp.data.count);
this.store.storePagination(resp.headers);
this.setCommonData(resp.data.pipelines);
diff --git a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
index f383a4b3368..53b7a174517 100644
--- a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
+++ b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
@@ -1,4 +1,4 @@
-import _ from 'underscore';
+import { escape } from 'lodash';
export default {
props: {
@@ -18,7 +18,7 @@ export default {
},
methods: {
capitalizeStageName(name) {
- const escapedName = _.escape(name);
+ const escapedName = escape(name);
return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
},
isFirstColumn(index) {
diff --git a/app/assets/javascripts/pipelines/stores/pipeline_store.js b/app/assets/javascripts/pipelines/stores/pipeline_store.js
index 441c9f3c25f..69e3579a3c7 100644
--- a/app/assets/javascripts/pipelines/stores/pipeline_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipeline_store.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import _ from 'underscore';
export default class PipelineStore {
constructor() {
@@ -61,7 +60,7 @@ export default class PipelineStore {
Vue.set(newPipeline, 'isLoading', false);
if (newPipeline.triggered_by) {
- if (!_.isArray(newPipeline.triggered_by)) {
+ if (!Array.isArray(newPipeline.triggered_by)) {
Object.assign(newPipeline, { triggered_by: [newPipeline.triggered_by] });
}
this.parseTriggeredByPipelines(oldPipeline, newPipeline.triggered_by[0]);
diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue
index d542dad8119..2ac57ac5bcb 100644
--- a/app/assets/javascripts/serverless/components/function_details.vue
+++ b/app/assets/javascripts/serverless/components/function_details.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isString } from 'lodash';
import { mapState, mapActions, mapGetters } from 'vuex';
import PodBox from './pod_box.vue';
import Url from './url.vue';
@@ -42,7 +42,7 @@ export default {
return this.func.name;
},
description() {
- return _.isString(this.func.description) ? this.func.description : '';
+ return isString(this.func.description) ? this.func.description : '';
},
funcUrl() {
return this.func.url;
diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue
index dca0e01b250..bbafdd7f8f1 100644
--- a/app/assets/javascripts/serverless/components/function_row.vue
+++ b/app/assets/javascripts/serverless/components/function_row.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isString } from 'lodash';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import Url from './url.vue';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -20,7 +20,7 @@ export default {
return this.func.name;
},
description() {
- if (!_.isString(this.func.description)) {
+ if (!isString(this.func.description)) {
return '';
}
diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue
index 932ca8e002e..9ac687f5e2c 100644
--- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue
@@ -5,6 +5,7 @@ import { __, sprintf } from '~/locale';
import { convertToFixedRange, isEqualTimeRanges, findTimeRange } from '~/lib/utils/datetime_range';
import Icon from '~/vue_shared/components/icon.vue';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import DateTimePickerInput from './date_time_picker_input.vue';
import {
defaultTimeRanges,
@@ -24,6 +25,7 @@ const events = {
export default {
components: {
Icon,
+ TooltipOnTruncate,
DateTimePickerInput,
GlFormGroup,
GlButton,
@@ -149,61 +151,68 @@ export default {
};
</script>
<template>
- <gl-dropdown
- :text="timeWindowText"
- class="date-time-picker"
- menu-class="date-time-picker-menu"
- v-bind="$attrs"
- toggle-class="w-100 text-truncate"
+ <tooltip-on-truncate
+ :title="timeWindowText"
+ :truncate-target="elem => elem.querySelector('.date-time-picker-toggle')"
+ placement="top"
+ class="d-inline-block"
>
- <div class="d-flex justify-content-between gl-p-2">
- <gl-form-group
- :label="__('Custom range')"
- label-for="custom-from-time"
- label-class="gl-pb-1"
- class="custom-time-range-form-group col-md-7 gl-pl-1 gl-pr-0 m-0"
- >
- <div class="gl-pt-2">
- <date-time-picker-input
- id="custom-time-from"
- v-model="startInput"
- :label="__('From')"
- :state="startInputValid"
- />
- <date-time-picker-input
- id="custom-time-to"
- v-model="endInput"
- :label="__('To')"
- :state="endInputValid"
- />
- </div>
- <gl-form-group>
- <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button>
- <gl-button variant="success" :disabled="!isValid" @click="setFixedRange()">
- {{ __('Apply') }}
- </gl-button>
+ <gl-dropdown
+ :text="timeWindowText"
+ v-bind="$attrs"
+ class="date-time-picker w-100"
+ menu-class="date-time-picker-menu"
+ toggle-class="date-time-picker-toggle text-truncate"
+ >
+ <div class="d-flex justify-content-between gl-p-2">
+ <gl-form-group
+ :label="__('Custom range')"
+ label-for="custom-from-time"
+ label-class="gl-pb-1"
+ class="custom-time-range-form-group col-md-7 gl-pl-1 gl-pr-0 m-0"
+ >
+ <div class="gl-pt-2">
+ <date-time-picker-input
+ id="custom-time-from"
+ v-model="startInput"
+ :label="__('From')"
+ :state="startInputValid"
+ />
+ <date-time-picker-input
+ id="custom-time-to"
+ v-model="endInput"
+ :label="__('To')"
+ :state="endInputValid"
+ />
+ </div>
+ <gl-form-group>
+ <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button>
+ <gl-button variant="success" :disabled="!isValid" @click="setFixedRange()">
+ {{ __('Apply') }}
+ </gl-button>
+ </gl-form-group>
</gl-form-group>
- </gl-form-group>
- <gl-form-group label-for="group-id-dropdown" class="col-md-5 gl-pl-1 gl-pr-1 m-0">
- <template #label>
- <span class="gl-pl-5">{{ __('Quick range') }}</span>
- </template>
+ <gl-form-group label-for="group-id-dropdown" class="col-md-5 gl-pl-1 gl-pr-1 m-0">
+ <template #label>
+ <span class="gl-pl-5">{{ __('Quick range') }}</span>
+ </template>
- <gl-dropdown-item
- v-for="(option, index) in options"
- :key="index"
- :active="isOptionActive(option)"
- active-class="active"
- @click="setQuickRange(option)"
- >
- <icon
- name="mobile-issue-close"
- class="align-bottom"
- :class="{ invisible: !isOptionActive(option) }"
- />
- {{ option.label }}
- </gl-dropdown-item>
- </gl-form-group>
- </div>
- </gl-dropdown>
+ <gl-dropdown-item
+ v-for="(option, index) in options"
+ :key="index"
+ :active="isOptionActive(option)"
+ active-class="active"
+ @click="setQuickRange(option)"
+ >
+ <icon
+ name="mobile-issue-close"
+ class="align-bottom"
+ :class="{ invisible: !isOptionActive(option) }"
+ />
+ {{ option.label }}
+ </gl-dropdown-item>
+ </gl-form-group>
+ </div>
+ </gl-dropdown>
+ </tooltip-on-truncate>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
index 69eb791d195..4ea3d162da2 100644
--- a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
+++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isFunction } from 'lodash';
import tooltip from '../directives/tooltip';
export default {
@@ -28,16 +28,18 @@ export default {
showTooltip: false,
};
},
+ watch: {
+ title() {
+ // Wait on $nextTick in case of slot width changes
+ this.$nextTick(this.updateTooltip);
+ },
+ },
mounted() {
- const target = this.selectTarget();
-
- if (target && target.scrollWidth > target.offsetWidth) {
- this.showTooltip = true;
- }
+ this.updateTooltip();
},
methods: {
selectTarget() {
- if (_.isFunction(this.truncateTarget)) {
+ if (isFunction(this.truncateTarget)) {
return this.truncateTarget(this.$el);
} else if (this.truncateTarget === 'child') {
return this.$el.childNodes[0];
@@ -45,6 +47,10 @@ export default {
return this.$el;
},
+ updateTooltip() {
+ const target = this.selectTarget();
+ this.showTooltip = Boolean(target && target.scrollWidth > target.offsetWidth);
+ },
},
};
</script>
diff --git a/app/models/project.rb b/app/models/project.rb
index bc652a19986..a215b6c881c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -901,7 +901,9 @@ class Project < ApplicationRecord
if Gitlab::UrlSanitizer.valid?(value)
import_url = Gitlab::UrlSanitizer.new(value)
super(import_url.sanitized_url)
- create_or_update_import_data(credentials: import_url.credentials)
+
+ credentials = import_url.credentials.to_h.transform_values { |value| CGI.unescape(value.to_s) }
+ create_or_update_import_data(credentials: credentials)
else
super(value)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c439d0700f1..37aceeae5f8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -134,15 +134,6 @@ class Repository
end
end
- # the opts are:
- # - :path
- # - :limit
- # - :offset
- # - :skip_merges
- # - :after
- # - :before
- # - :all
- # - :first_parent
def commits(ref = nil, opts = {})
options = {
repo: raw_repository,
@@ -155,7 +146,8 @@ class Repository
after: opts[:after],
before: opts[:before],
all: !!opts[:all],
- first_parent: !!opts[:first_parent]
+ first_parent: !!opts[:first_parent],
+ order: opts[:order]
}
commits = Gitlab::Git::Commit.where(options)
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index ae67b4f5256..0e7a4821bdf 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -19,8 +19,10 @@ module Users
LEASE_TIMEOUT = 1.minute.to_i
# user - The User for which to refresh the authorized projects.
- def initialize(user)
+ def initialize(user, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil)
@user = user
+ @incorrect_auth_found_callback = incorrect_auth_found_callback
+ @missing_auth_found_callback = missing_auth_found_callback
# We need an up to date User object that has access to all relations that
# may have been created earlier. The only way to ensure this is to reload
@@ -55,6 +57,10 @@ module Users
# rows not in the new list or with a different access level should be
# removed.
if !fresh[project_id] || fresh[project_id] != row.access_level
+ if incorrect_auth_found_callback
+ incorrect_auth_found_callback.call(project_id, row.access_level)
+ end
+
array << row.project_id
end
end
@@ -63,6 +69,10 @@ module Users
# rows not in the old list or with a different access level should be
# added.
if !current[project_id] || current[project_id].access_level != level
+ if missing_auth_found_callback
+ missing_auth_found_callback.call(project_id, level)
+ end
+
array << [user.id, project_id, level]
end
end
@@ -104,5 +114,9 @@ module Users
def fresh_authorizations
Gitlab::ProjectAuthorizations.new(user).calculate
end
+
+ private
+
+ attr_reader :incorrect_auth_found_callback, :missing_auth_found_callback
end
end
diff --git a/changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml b/changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml
new file mode 100644
index 00000000000..93d1f7537fd
--- /dev/null
+++ b/changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml
@@ -0,0 +1,5 @@
+---
+title: Add tooltip when dates in date picker are too long
+merge_request: 24664
+author:
+type: added
diff --git a/changelogs/unreleased/35671-api-repository-commits-with-order.yml b/changelogs/unreleased/35671-api-repository-commits-with-order.yml
new file mode 100644
index 00000000000..3ca59a2265f
--- /dev/null
+++ b/changelogs/unreleased/35671-api-repository-commits-with-order.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Ability to list commits in order (--topo-order)'
+merge_request: 24702
+author:
+type: added
diff --git a/changelogs/unreleased/36854-webide-use-gl-alert.yml b/changelogs/unreleased/36854-webide-use-gl-alert.yml
new file mode 100644
index 00000000000..4f6cc63de3f
--- /dev/null
+++ b/changelogs/unreleased/36854-webide-use-gl-alert.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Web IDE alert message look and feel
+merge_request: 23300
+author: Sean Nichols
+type: fixed
diff --git a/changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml b/changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml
new file mode 100644
index 00000000000..5118be38704
--- /dev/null
+++ b/changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid double encoding of credential while importing a Project by URL
+merge_request: 24514
+author:
+type: fixed
diff --git a/changelogs/unreleased/leipert-fix-user-label-bug.yml b/changelogs/unreleased/leipert-fix-user-label-bug.yml
deleted file mode 100644
index 67e1a4011af..00000000000
--- a/changelogs/unreleased/leipert-fix-user-label-bug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix autocomplete limitation bug
-merge_request: 25127
-author:
-type: fixed
diff --git a/changelogs/unreleased/refactoring-entities-file-15.yml b/changelogs/unreleased/refactoring-entities-file-15.yml
new file mode 100644
index 00000000000..89c2da2cf86
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-15.yml
@@ -0,0 +1,5 @@
+---
+title: Separate access entities into own class files
+merge_request: 24845
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/refactoring-entities-file-23.yml b/changelogs/unreleased/refactoring-entities-file-23.yml
new file mode 100644
index 00000000000..c771b80811f
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-23.yml
@@ -0,0 +1,5 @@
+---
+title: Separate environment entities into own class files
+merge_request: 24951
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/refactoring-entities-file-26.yml b/changelogs/unreleased/refactoring-entities-file-26.yml
new file mode 100644
index 00000000000..050ab6678a5
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-26.yml
@@ -0,0 +1,5 @@
+---
+title: Separate JobRequest entities into own class files
+merge_request: 24977
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/refactoring-entities-file-28.yml b/changelogs/unreleased/refactoring-entities-file-28.yml
new file mode 100644
index 00000000000..604dfa7627d
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-28.yml
@@ -0,0 +1,5 @@
+---
+title: Separate page domain entities into own class files
+merge_request: 24987
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/refactoring-entities-file-30.yml b/changelogs/unreleased/refactoring-entities-file-30.yml
new file mode 100644
index 00000000000..c60c63b9843
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-30.yml
@@ -0,0 +1,5 @@
+---
+title: Separate badge entities into own class files
+merge_request: 25116
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/refactoring-entities-file-32.yml b/changelogs/unreleased/refactoring-entities-file-32.yml
new file mode 100644
index 00000000000..e5116a80263
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-32.yml
@@ -0,0 +1,5 @@
+---
+title: Separate cluster entities into own class files
+merge_request: 25121
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/resolve_gitlab_issue_196651.yml b/changelogs/unreleased/resolve_gitlab_issue_196651.yml
new file mode 100644
index 00000000000..169d8e50383
--- /dev/null
+++ b/changelogs/unreleased/resolve_gitlab_issue_196651.yml
@@ -0,0 +1,5 @@
+---
+title: Replace underscore with lodash for ./app/assets/javascripts/serverless
+merge_request: 25011
+author: Tobias Spagert
+type: other
diff --git a/db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb b/db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb
new file mode 100644
index 00000000000..83b58300115
--- /dev/null
+++ b/db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class ScheduleRecalculateProjectAuthorizations < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'RecalculateProjectAuthorizations'
+ BATCH_SIZE = 2_500
+ DELAY_INTERVAL = 2.minutes.to_i
+
+ disable_ddl_transaction!
+
+ class Namespace < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'namespaces'
+ end
+
+ class ProjectAuthorization < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'project_authorizations'
+ end
+
+ def up
+ say "Scheduling #{MIGRATION} jobs"
+
+ max_group_id = Namespace.where(type: 'Group').maximum(:id)
+ project_authorizations = ProjectAuthorization.where('project_id <= ?', max_group_id)
+ .select(:user_id)
+ .distinct
+
+ project_authorizations.each_batch(of: BATCH_SIZE, column: :user_id) do |authorizations, index|
+ delay = index * DELAY_INTERVAL
+ user_ids = authorizations.map(&:user_id)
+ BackgroundMigrationWorker.perform_in(delay, MIGRATION, [user_ids])
+ end
+ end
+
+ def down
+ end
+end
diff --git a/doc/api/commits.md b/doc/api/commits.md
index fb090f51a2e..eb3fb7b2195 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -18,6 +18,7 @@ GET /projects/:id/repository/commits
| `all` | boolean | no | Retrieve every commit from the repository |
| `with_stats` | boolean | no | Stats about each commit will be added to the response |
| `first_parent` | boolean | no | Follow only the first parent commit upon seeing a merge commit |
+| `order` | string | no | List commits in order. Possible value: [`topo`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---topo-order). |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md
index e87b5d03438..e7565835be7 100644
--- a/doc/user/project/operations/error_tracking.md
+++ b/doc/user/project/operations/error_tracking.md
@@ -84,5 +84,6 @@ Ignoring an error will prevent it from appearing in the [Error Tracking List](#e
From within the [Error Details](#error-details) page you can resolve a Sentry error by
clicking the **Resolve** button near the top of the page.
-Marking an error as resolved indicates that the error has stopped firing events. If another event
-occurs, the error reverts to unresolved.
+Marking an error as resolved indicates that the error has stopped firing events. If a GitLab issue is linked to the error, then the issue will be closed.
+
+If another event occurs, the error reverts to unresolved.
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 9dcf9b015aa..4e04a99e97c 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -38,6 +38,7 @@ module API
optional :all, type: Boolean, desc: 'Every commit will be returned'
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges'
+ optional :order, type: String, desc: 'List commits in order', values: %w[topo]
use :pagination
end
get ':id/repository/commits' do
@@ -49,6 +50,7 @@ module API
all = params[:all]
with_stats = params[:with_stats]
first_parent = params[:first_parent]
+ order = params[:order]
commits = user_project.repository.commits(ref,
path: path,
@@ -57,7 +59,8 @@ module API
before: before,
after: after,
all: all,
- first_parent: first_parent)
+ first_parent: first_parent,
+ order: order)
commit_count =
if all || path || before || after || first_parent
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 5128ffa6a1f..b9805973c54 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -129,40 +129,6 @@ module API
end
end
- class Namespace < NamespaceBasic
- expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
- namespace.users_with_descendants.count
- end
-
- def expose_members_count_with_descendants?(namespace, opts)
- namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
- end
- end
-
- class MemberAccess < Grape::Entity
- expose :access_level
- expose :notification_level do |member, options|
- if member.notification_setting
- ::NotificationSetting.levels[member.notification_setting.level]
- end
- end
- end
-
- class ProjectAccess < MemberAccess
- end
-
- class GroupAccess < MemberAccess
- end
-
- class NotificationSetting < Grape::Entity
- expose :level
- expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
- ::NotificationSetting.email_events.each do |event|
- expose event
- end
- end
- end
-
class Trigger < Grape::Entity
include ::API::Helpers::Presentable
@@ -204,39 +170,6 @@ module API
expose :variables, using: Entities::Variable
end
- class EnvironmentBasic < Grape::Entity
- expose :id, :name, :slug, :external_url
- end
-
- class Deployment < Grape::Entity
- expose :id, :iid, :ref, :sha, :created_at, :updated_at
- expose :user, using: Entities::UserBasic
- expose :environment, using: Entities::EnvironmentBasic
- expose :deployable, using: Entities::Job
- expose :status
- end
-
- class Environment < EnvironmentBasic
- expose :project, using: Entities::BasicProjectDetails
- expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true }
- expose :state
- end
-
- class LicenseBasic < Grape::Entity
- expose :key, :name, :nickname
- expose :url, as: :html_url
- expose(:source_url) { |license| license.meta['source'] }
- end
-
- class License < LicenseBasic
- expose :popular?, as: :popular
- expose(:description) { |license| license.meta['description'] }
- expose(:conditions) { |license| license.meta['conditions'] }
- expose(:permissions) { |license| license.meta['permissions'] }
- expose(:limitations) { |license| license.meta['limitations'] }
- expose :content
- end
-
class ImpersonationToken < PersonalAccessToken
expose :impersonation
end
@@ -267,93 +200,6 @@ module API
end
end
- module JobRequest
- class JobInfo < Grape::Entity
- expose :name, :stage
- expose :project_id, :project_name
- end
-
- class GitInfo < Grape::Entity
- expose :repo_url, :ref, :sha, :before_sha
- expose :ref_type
- expose :refspecs
- expose :git_depth, as: :depth
- end
-
- class RunnerInfo < Grape::Entity
- expose :metadata_timeout, as: :timeout
- expose :runner_session_url
- end
-
- class Step < Grape::Entity
- expose :name, :script, :timeout, :when, :allow_failure
- end
-
- class Port < Grape::Entity
- expose :number, :protocol, :name
- end
-
- class Image < Grape::Entity
- expose :name, :entrypoint
- expose :ports, using: JobRequest::Port
- end
-
- class Service < Image
- expose :alias, :command
- end
-
- class Artifacts < Grape::Entity
- expose :name
- expose :untracked
- expose :paths
- expose :when
- expose :expire_in
- expose :artifact_type
- expose :artifact_format
- end
-
- class Cache < Grape::Entity
- expose :key, :untracked, :paths, :policy
- end
-
- class Credentials < Grape::Entity
- expose :type, :url, :username, :password
- end
-
- class Dependency < Grape::Entity
- expose :id, :name, :token
- expose :artifacts_file, using: JobArtifactFile, if: ->(job, _) { job.artifacts? }
- end
-
- class Response < Grape::Entity
- expose :id
- expose :token
- expose :allow_git_fetch
-
- expose :job_info, using: JobInfo do |model|
- model
- end
-
- expose :git_info, using: GitInfo do |model|
- model
- end
-
- expose :runner_info, using: RunnerInfo do |model|
- model
- end
-
- expose :variables
- expose :steps, using: Step
- expose :image, using: Image
- expose :services, using: Service
- expose :artifacts, using: Artifacts
- expose :cache, using: Cache
- expose :credentials, using: Credentials
- expose :all_dependencies, as: :dependencies, using: Dependency
- expose :features
- end
- end
-
class UserAgentDetail < Grape::Entity
expose :user_agent
expose :ip_address
@@ -370,45 +216,6 @@ module API
expose :expiration
end
- class PagesDomainCertificate < Grape::Entity
- expose :subject
- expose :expired?, as: :expired
- expose :certificate
- expose :certificate_text
- end
-
- class PagesDomainBasic < Grape::Entity
- expose :domain
- expose :url
- expose :project_id
- expose :verified?, as: :verified
- expose :verification_code, as: :verification_code
- expose :enabled_until
- expose :auto_ssl_enabled
-
- expose :certificate,
- as: :certificate_expiration,
- if: ->(pages_domain, _) { pages_domain.certificate? },
- using: PagesDomainCertificateExpiration do |pages_domain|
- pages_domain
- end
- end
-
- class PagesDomain < Grape::Entity
- expose :domain
- expose :url
- expose :verified?, as: :verified
- expose :verification_code, as: :verification_code
- expose :enabled_until
- expose :auto_ssl_enabled
-
- expose :certificate,
- if: ->(pages_domain, _) { pages_domain.certificate? },
- using: PagesDomainCertificate do |pages_domain|
- pages_domain
- end
- end
-
class Application < Grape::Entity
expose :id
expose :uid, as: :application_id
@@ -437,49 +244,6 @@ module API
expose :project_id
end
- class BasicBadgeDetails < Grape::Entity
- expose :name
- expose :link_url
- expose :image_url
- expose :rendered_link_url do |badge, options|
- badge.rendered_link_url(options.fetch(:project, nil))
- end
- expose :rendered_image_url do |badge, options|
- badge.rendered_image_url(options.fetch(:project, nil))
- end
- end
-
- class Badge < BasicBadgeDetails
- expose :id
- expose :kind do |badge|
- badge.type == 'ProjectBadge' ? 'project' : 'group'
- end
- end
-
- class ResourceLabelEvent < Grape::Entity
- expose :id
- expose :user, using: Entities::UserBasic
- expose :created_at
- expose :resource_type do |event, options|
- event.issuable.class.name
- end
- expose :resource_id do |event, options|
- event.issuable.id
- end
- expose :label, using: Entities::LabelBasic
- expose :action
- end
-
- class Suggestion < Grape::Entity
- expose :id
- expose :from_line
- expose :to_line
- expose :appliable?, as: :appliable
- expose :applied
- expose :from_content
- expose :to_content
- end
-
module Platform
class Kubernetes < Grape::Entity
expose :api_url
@@ -501,23 +265,6 @@ module API
end
end
- class Cluster < Grape::Entity
- expose :id, :name, :created_at, :domain
- expose :provider_type, :platform_type, :environment_scope, :cluster_type
- expose :user, using: Entities::UserBasic
- expose :platform_kubernetes, using: Entities::Platform::Kubernetes
- expose :provider_gcp, using: Entities::Provider::Gcp
- expose :management_project, using: Entities::ProjectIdentity
- end
-
- class ClusterProject < Cluster
- expose :project, using: Entities::BasicProjectDetails
- end
-
- class ClusterGroup < Cluster
- expose :group, using: Entities::BasicGroupDetails
- end
-
module InternalPostReceive
class Message < Grape::Entity
expose :message
diff --git a/lib/api/entities/badge.rb b/lib/api/entities/badge.rb
new file mode 100644
index 00000000000..1e3e2ec469a
--- /dev/null
+++ b/lib/api/entities/badge.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Badge < Entities::BasicBadgeDetails
+ expose :id
+ expose :kind do |badge|
+ badge.type == 'ProjectBadge' ? 'project' : 'group'
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/basic_badge_details.rb b/lib/api/entities/basic_badge_details.rb
new file mode 100644
index 00000000000..273dc57fe67
--- /dev/null
+++ b/lib/api/entities/basic_badge_details.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class BasicBadgeDetails < Grape::Entity
+ expose :name
+ expose :link_url
+ expose :image_url
+ expose :rendered_link_url do |badge, options|
+ badge.rendered_link_url(options.fetch(:project, nil))
+ end
+ expose :rendered_image_url do |badge, options|
+ badge.rendered_image_url(options.fetch(:project, nil))
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/cluster.rb b/lib/api/entities/cluster.rb
new file mode 100644
index 00000000000..4cb54e988ce
--- /dev/null
+++ b/lib/api/entities/cluster.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Cluster < Grape::Entity
+ expose :id, :name, :created_at, :domain
+ expose :provider_type, :platform_type, :environment_scope, :cluster_type
+ expose :user, using: Entities::UserBasic
+ expose :platform_kubernetes, using: Entities::Platform::Kubernetes
+ expose :provider_gcp, using: Entities::Provider::Gcp
+ expose :management_project, using: Entities::ProjectIdentity
+ end
+ end
+end
diff --git a/lib/api/entities/cluster_group.rb b/lib/api/entities/cluster_group.rb
new file mode 100644
index 00000000000..8f71438cf3d
--- /dev/null
+++ b/lib/api/entities/cluster_group.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class ClusterGroup < Entities::Cluster
+ expose :group, using: Entities::BasicGroupDetails
+ end
+ end
+end
diff --git a/lib/api/entities/cluster_project.rb b/lib/api/entities/cluster_project.rb
new file mode 100644
index 00000000000..2fd3e35e2a2
--- /dev/null
+++ b/lib/api/entities/cluster_project.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class ClusterProject < Entities::Cluster
+ expose :project, using: Entities::BasicProjectDetails
+ end
+ end
+end
diff --git a/lib/api/entities/deployment.rb b/lib/api/entities/deployment.rb
new file mode 100644
index 00000000000..3a97d3e3c09
--- /dev/null
+++ b/lib/api/entities/deployment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Deployment < Grape::Entity
+ expose :id, :iid, :ref, :sha, :created_at, :updated_at
+ expose :user, using: Entities::UserBasic
+ expose :environment, using: Entities::EnvironmentBasic
+ expose :deployable, using: Entities::Job
+ expose :status
+ end
+ end
+end
diff --git a/lib/api/entities/environment.rb b/lib/api/entities/environment.rb
new file mode 100644
index 00000000000..cb39ce1b13a
--- /dev/null
+++ b/lib/api/entities/environment.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Environment < Entities::EnvironmentBasic
+ expose :project, using: Entities::BasicProjectDetails
+ expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true }
+ expose :state
+ end
+ end
+end
diff --git a/lib/api/entities/environment_basic.rb b/lib/api/entities/environment_basic.rb
new file mode 100644
index 00000000000..061d4739874
--- /dev/null
+++ b/lib/api/entities/environment_basic.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class EnvironmentBasic < Grape::Entity
+ expose :id, :name, :slug, :external_url
+ end
+ end
+end
diff --git a/lib/api/entities/group_access.rb b/lib/api/entities/group_access.rb
new file mode 100644
index 00000000000..5e53e9645c2
--- /dev/null
+++ b/lib/api/entities/group_access.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class GroupAccess < MemberAccess
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/artifacts.rb b/lib/api/entities/job_request/artifacts.rb
new file mode 100644
index 00000000000..c6871fdd875
--- /dev/null
+++ b/lib/api/entities/job_request/artifacts.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Artifacts < Grape::Entity
+ expose :name
+ expose :untracked
+ expose :paths
+ expose :when
+ expose :expire_in
+ expose :artifact_type
+ expose :artifact_format
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/cache.rb b/lib/api/entities/job_request/cache.rb
new file mode 100644
index 00000000000..a75affbaf84
--- /dev/null
+++ b/lib/api/entities/job_request/cache.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Cache < Grape::Entity
+ expose :key, :untracked, :paths, :policy
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/credentials.rb b/lib/api/entities/job_request/credentials.rb
new file mode 100644
index 00000000000..cdac5566cbd
--- /dev/null
+++ b/lib/api/entities/job_request/credentials.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Credentials < Grape::Entity
+ expose :type, :url, :username, :password
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/dependency.rb b/lib/api/entities/job_request/dependency.rb
new file mode 100644
index 00000000000..64d779f6575
--- /dev/null
+++ b/lib/api/entities/job_request/dependency.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Dependency < Grape::Entity
+ expose :id, :name, :token
+ expose :artifacts_file, using: Entities::JobArtifactFile, if: ->(job, _) { job.artifacts? }
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/git_info.rb b/lib/api/entities/job_request/git_info.rb
new file mode 100644
index 00000000000..e07099263b5
--- /dev/null
+++ b/lib/api/entities/job_request/git_info.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class GitInfo < Grape::Entity
+ expose :repo_url, :ref, :sha, :before_sha
+ expose :ref_type
+ expose :refspecs
+ expose :git_depth, as: :depth
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/image.rb b/lib/api/entities/job_request/image.rb
new file mode 100644
index 00000000000..47f4542d2d5
--- /dev/null
+++ b/lib/api/entities/job_request/image.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Image < Grape::Entity
+ expose :name, :entrypoint
+ expose :ports, using: Entities::JobRequest::Port
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/job_info.rb b/lib/api/entities/job_request/job_info.rb
new file mode 100644
index 00000000000..09c13aa8471
--- /dev/null
+++ b/lib/api/entities/job_request/job_info.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class JobInfo < Grape::Entity
+ expose :name, :stage
+ expose :project_id, :project_name
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/port.rb b/lib/api/entities/job_request/port.rb
new file mode 100644
index 00000000000..ee427da8657
--- /dev/null
+++ b/lib/api/entities/job_request/port.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Port < Grape::Entity
+ expose :number, :protocol, :name
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/response.rb b/lib/api/entities/job_request/response.rb
new file mode 100644
index 00000000000..fdacd3af2da
--- /dev/null
+++ b/lib/api/entities/job_request/response.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Response < Grape::Entity
+ expose :id
+ expose :token
+ expose :allow_git_fetch
+
+ expose :job_info, using: Entities::JobRequest::JobInfo do |model|
+ model
+ end
+
+ expose :git_info, using: Entities::JobRequest::GitInfo do |model|
+ model
+ end
+
+ expose :runner_info, using: Entities::JobRequest::RunnerInfo do |model|
+ model
+ end
+
+ expose :variables
+ expose :steps, using: Entities::JobRequest::Step
+ expose :image, using: Entities::JobRequest::Image
+ expose :services, using: Entities::JobRequest::Service
+ expose :artifacts, using: Entities::JobRequest::Artifacts
+ expose :cache, using: Entities::JobRequest::Cache
+ expose :credentials, using: Entities::JobRequest::Credentials
+ expose :all_dependencies, as: :dependencies, using: Entities::JobRequest::Dependency
+ expose :features
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/runner_info.rb b/lib/api/entities/job_request/runner_info.rb
new file mode 100644
index 00000000000..e6d2e8d9e85
--- /dev/null
+++ b/lib/api/entities/job_request/runner_info.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class RunnerInfo < Grape::Entity
+ expose :metadata_timeout, as: :timeout
+ expose :runner_session_url
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/service.rb b/lib/api/entities/job_request/service.rb
new file mode 100644
index 00000000000..9ad5abf4e9e
--- /dev/null
+++ b/lib/api/entities/job_request/service.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Service < Entities::JobRequest::Image
+ expose :alias, :command
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/step.rb b/lib/api/entities/job_request/step.rb
new file mode 100644
index 00000000000..498dd017fb4
--- /dev/null
+++ b/lib/api/entities/job_request/step.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module JobRequest
+ class Step < Grape::Entity
+ expose :name, :script, :timeout, :when, :allow_failure
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/license.rb b/lib/api/entities/license.rb
new file mode 100644
index 00000000000..d7a414344c1
--- /dev/null
+++ b/lib/api/entities/license.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class License < Entities::LicenseBasic
+ expose :popular?, as: :popular
+ expose(:description) { |license| license.meta['description'] }
+ expose(:conditions) { |license| license.meta['conditions'] }
+ expose(:permissions) { |license| license.meta['permissions'] }
+ expose(:limitations) { |license| license.meta['limitations'] }
+ expose :content
+ end
+ end
+end
diff --git a/lib/api/entities/license_basic.rb b/lib/api/entities/license_basic.rb
new file mode 100644
index 00000000000..08af68785a9
--- /dev/null
+++ b/lib/api/entities/license_basic.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class LicenseBasic < Grape::Entity
+ expose :key, :name, :nickname
+ expose :url, as: :html_url
+ expose(:source_url) { |license| license.meta['source'] }
+ end
+ end
+end
diff --git a/lib/api/entities/member_access.rb b/lib/api/entities/member_access.rb
new file mode 100644
index 00000000000..097c72bf617
--- /dev/null
+++ b/lib/api/entities/member_access.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class MemberAccess < Grape::Entity
+ expose :access_level
+ expose :notification_level do |member, options|
+ if member.notification_setting
+ ::NotificationSetting.levels[member.notification_setting.level]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/namespace.rb b/lib/api/entities/namespace.rb
new file mode 100644
index 00000000000..b21dd29c7b4
--- /dev/null
+++ b/lib/api/entities/namespace.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Namespace < Entities::NamespaceBasic
+ expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
+ namespace.users_with_descendants.count
+ end
+
+ def expose_members_count_with_descendants?(namespace, opts)
+ namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/notification_setting.rb b/lib/api/entities/notification_setting.rb
new file mode 100644
index 00000000000..cdff4f2f5c5
--- /dev/null
+++ b/lib/api/entities/notification_setting.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class NotificationSetting < Grape::Entity
+ expose :level
+ expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
+ ::NotificationSetting.email_events.each do |event|
+ expose event
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/pages_domain.rb b/lib/api/entities/pages_domain.rb
new file mode 100644
index 00000000000..87af8c7b0a4
--- /dev/null
+++ b/lib/api/entities/pages_domain.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class PagesDomain < Grape::Entity
+ expose :domain
+ expose :url
+ expose :verified?, as: :verified
+ expose :verification_code, as: :verification_code
+ expose :enabled_until
+ expose :auto_ssl_enabled
+
+ expose :certificate,
+ if: ->(pages_domain, _) { pages_domain.certificate? },
+ using: Entities::PagesDomainCertificate do |pages_domain|
+ pages_domain
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/pages_domain_basic.rb b/lib/api/entities/pages_domain_basic.rb
new file mode 100644
index 00000000000..6f8901fe715
--- /dev/null
+++ b/lib/api/entities/pages_domain_basic.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class PagesDomainBasic < Grape::Entity
+ expose :domain
+ expose :url
+ expose :project_id
+ expose :verified?, as: :verified
+ expose :verification_code, as: :verification_code
+ expose :enabled_until
+ expose :auto_ssl_enabled
+
+ expose :certificate,
+ as: :certificate_expiration,
+ if: ->(pages_domain, _) { pages_domain.certificate? },
+ using: Entities::PagesDomainCertificateExpiration do |pages_domain|
+ pages_domain
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/pages_domain_certificate.rb b/lib/api/entities/pages_domain_certificate.rb
new file mode 100644
index 00000000000..82c4729d454
--- /dev/null
+++ b/lib/api/entities/pages_domain_certificate.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class PagesDomainCertificate < Grape::Entity
+ expose :subject
+ expose :expired?, as: :expired
+ expose :certificate
+ expose :certificate_text
+ end
+ end
+end
diff --git a/lib/api/entities/project_access.rb b/lib/api/entities/project_access.rb
new file mode 100644
index 00000000000..29f85fda620
--- /dev/null
+++ b/lib/api/entities/project_access.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class ProjectAccess < Entities::MemberAccess
+ end
+ end
+end
diff --git a/lib/api/entities/resource_label_event.rb b/lib/api/entities/resource_label_event.rb
new file mode 100644
index 00000000000..890264abf93
--- /dev/null
+++ b/lib/api/entities/resource_label_event.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class ResourceLabelEvent < Grape::Entity
+ expose :id
+ expose :user, using: Entities::UserBasic
+ expose :created_at
+ expose :resource_type do |event, options|
+ event.issuable.class.name
+ end
+ expose :resource_id do |event, options|
+ event.issuable.id
+ end
+ expose :label, using: Entities::LabelBasic
+ expose :action
+ end
+ end
+end
diff --git a/lib/api/entities/suggestion.rb b/lib/api/entities/suggestion.rb
new file mode 100644
index 00000000000..59f94099d7f
--- /dev/null
+++ b/lib/api/entities/suggestion.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Suggestion < Grape::Entity
+ expose :id
+ expose :from_line
+ expose :to_line
+ expose :appliable?, as: :appliable
+ expose :applied
+ expose :from_content
+ expose :to_content
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/recalculate_project_authorizations.rb b/lib/gitlab/background_migration/recalculate_project_authorizations.rb
new file mode 100644
index 00000000000..3d2ce9fc10c
--- /dev/null
+++ b/lib/gitlab/background_migration/recalculate_project_authorizations.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop:disable Style/Documentation
+ class RecalculateProjectAuthorizations
+ def perform(user_ids)
+ user_ids.each do |user_id|
+ user = User.find_by(id: user_id)
+
+ next unless user
+
+ service = Users::RefreshAuthorizedProjectsService.new(
+ user,
+ incorrect_auth_found_callback:
+ ->(project_id, access_level) do
+ logger.info(message: 'Removing ProjectAuthorizations',
+ user_id: user.id,
+ project_id: project_id,
+ access_level: access_level)
+ end,
+ missing_auth_found_callback:
+ ->(project_id, access_level) do
+ logger.info(message: 'Creating ProjectAuthorizations',
+ user_id: user.id,
+ project_id: project_id,
+ access_level: access_level)
+ end
+ )
+
+ service.execute
+ end
+ end
+
+ private
+
+ def logger
+ @logger ||= Gitlab::BackgroundMigration::Logger.build
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 17a7be311ca..4fc5bfddf0c 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -353,11 +353,7 @@ module Gitlab
def fetch_blob(sha, path)
return unless sha
- # Load only patch_hard_limit_bytes number of bytes for the blob
- # Because otherwise, it is too large to be displayed
- Blob.lazy(
- repository.project, sha, path,
- blob_size_limit: Gitlab::Git::Diff.patch_hard_limit_bytes)
+ Blob.lazy(repository.project, sha, path)
end
def total_blob_lines(blob)
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 48da838366f..0b999197cd8 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -130,8 +130,7 @@ module Gitlab
# :skip is the number of commits to skip
# :order is the commits order and allowed value is :none (default), :date,
# :topo, or any combination of them (in an array). Commit ordering types
- # are documented here:
- # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant)
+ # are documented here: https://git-scm.com/docs/git-log#_commit_ordering
def find_all(repo, options = {})
wrapped_gitaly_errors do
Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 15318bc817a..b981b9cf1bc 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -324,6 +324,7 @@ module Gitlab
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
request.before = GitalyClient.timestamp(options[:before]) if options[:before]
request.revision = encode_binary(options[:ref]) if options[:ref]
+ request.order = options[:order].upcase if options[:order].present?
request.paths = encode_repeated(Array(options[:path])) if options[:path].present?
diff --git a/lib/gitlab/project_authorizations.rb b/lib/gitlab/project_authorizations.rb
index e2271b1492c..d65e8759ec9 100644
--- a/lib/gitlab/project_authorizations.rb
+++ b/lib/gitlab/project_authorizations.rb
@@ -68,12 +68,10 @@ module Gitlab
.select([namespaces[:id], members[:access_level]])
.except(:order)
- if Feature.enabled?(:share_group_with_group, default_enabled: true)
- # Namespaces shared with any of the group
- cte << Group.select([namespaces[:id], 'group_group_links.group_access AS access_level'])
- .joins(join_group_group_links)
- .joins(join_members_on_group_group_links)
- end
+ # Namespaces shared with any of the group
+ cte << Group.select([namespaces[:id], 'group_group_links.group_access AS access_level'])
+ .joins(join_group_group_links)
+ .joins(join_members_on_group_group_links)
# Sub groups of any groups the user is a member of.
cte << Group.select([
@@ -114,6 +112,8 @@ module Gitlab
members = Member.arel_table
cond = group_group_links[:shared_with_group_id].eq(members[:source_id])
+ .and(members[:source_type].eq('Namespace'))
+ .and(members[:requested_at].eq(nil))
.and(members[:user_id].eq(user.id))
Arel::Nodes::InnerJoin.new(members, Arel::Nodes::On.new(cond))
end
diff --git a/package.json b/package.json
index b6c2d56ca0a..3a5f4edb4ef 100644
--- a/package.json
+++ b/package.json
@@ -201,7 +201,6 @@
"yarn-deduplicate": "^1.1.1"
},
"resolutions": {
- "at.js": "https://gitlab.com/gitlab-org/frontend/At.js.git#121ce9a557b51c33f5693ac8df52d2bda1e53cbe",
"vue-jest/ts-jest": "24.0.0",
"monaco-editor": "0.18.1"
},
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
index 277e7364ada..b0b511eb23a 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', :smoke do
+ context 'Create', :smoke, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/205511', type: :bug } do
describe 'Snippet creation' do
it 'User creates a snippet' do
Flow::Login.sign_in
diff --git a/spec/factories/project_authorizations.rb b/spec/factories/project_authorizations.rb
new file mode 100644
index 00000000000..ffdf5576f84
--- /dev/null
+++ b/spec/factories/project_authorizations.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_authorization do
+ user
+ project
+ access_level { Gitlab::Access::REPORTER }
+ end
+end
diff --git a/spec/frontend/ide/components/error_message_spec.js b/spec/frontend/ide/components/error_message_spec.js
index 1de496ba3f8..3a4dcc5873d 100644
--- a/spec/frontend/ide/components/error_message_spec.js
+++ b/spec/frontend/ide/components/error_message_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import ErrorMessage from '~/ide/components/error_message.vue';
@@ -15,7 +15,7 @@ describe('IDE error message component', () => {
actions: { setErrorMessage: setErrorMessageMock },
});
- wrapper = shallowMount(ErrorMessage, {
+ wrapper = mount(ErrorMessage, {
propsData: {
message: {
text: 'some text',
@@ -38,15 +38,18 @@ describe('IDE error message component', () => {
wrapper = null;
});
+ const findDismissButton = () => wrapper.find('button[aria-label=Dismiss]');
+ const findActionButton = () => wrapper.find('button.gl-alert-action');
+
it('renders error message', () => {
const text = 'error message';
createComponent({ text });
expect(wrapper.text()).toContain(text);
});
- it('clears error message on click', () => {
+ it('clears error message on dismiss click', () => {
createComponent();
- wrapper.trigger('click');
+ findDismissButton().trigger('click');
expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null, undefined);
});
@@ -68,29 +71,27 @@ describe('IDE error message component', () => {
});
it('renders action button', () => {
- const button = wrapper.find('button');
+ const button = findActionButton();
expect(button.exists()).toBe(true);
expect(button.text()).toContain(message.actionText);
});
- it('does not clear error message on click', () => {
- wrapper.trigger('click');
-
- expect(setErrorMessageMock).not.toHaveBeenCalled();
+ it('does not show dismiss button', () => {
+ expect(findDismissButton().exists()).toBe(false);
});
it('dispatches action', () => {
- wrapper.find('button').trigger('click');
+ findActionButton().trigger('click');
expect(actionMock).toHaveBeenCalledWith(message.actionPayload);
});
it('does not dispatch action when already loading', () => {
- wrapper.find('button').trigger('click');
+ findActionButton().trigger('click');
actionMock.mockReset();
return wrapper.vm.$nextTick(() => {
- wrapper.find('button').trigger('click');
+ findActionButton().trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(actionMock).not.toHaveBeenCalled();
@@ -106,7 +107,7 @@ describe('IDE error message component', () => {
resolveAction = resolve;
}),
);
- wrapper.find('button').trigger('click');
+ findActionButton().trigger('click');
return wrapper.vm.$nextTick(() => {
expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
@@ -115,7 +116,7 @@ describe('IDE error message component', () => {
});
it('hides loading icon when operation finishes', () => {
- wrapper.find('button').trigger('click');
+ findActionButton().trigger('click');
return actionMock()
.then(() => wrapper.vm.$nextTick())
.then(() => {
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
index 048db4a7533..4241b994cba 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -61,14 +61,14 @@ describe('ide component, non-empty repo', () => {
});
it('shows error message when set', done => {
- expect(vm.$el.querySelector('.flash-container')).toBe(null);
+ expect(vm.$el.querySelector('.gl-alert')).toBe(null);
vm.$store.state.errorMessage = {
text: 'error',
};
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.flash-container')).not.toBe(null);
+ expect(vm.$el.querySelector('.gl-alert')).not.toBe(null);
done();
});
diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
index a8d39b7b5fe..5f432f2a1b5 100644
--- a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
+++ b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
@@ -1,149 +1,160 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
-const TEST_TITLE = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
-const STYLE_TRUNCATED = 'display: inline-block; max-width: 20px;';
-const STYLE_NORMAL = 'display: inline-block; max-width: 1000px;';
+const TEXT_SHORT = 'lorem';
+const TEXT_LONG = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
-const localVue = createLocalVue();
+const TEXT_TRUNCATE = 'white-space: nowrap; overflow:hidden;';
+const STYLE_NORMAL = `${TEXT_TRUNCATE} display: inline-block; max-width: 1000px;`; // does not overflows
+const STYLE_OVERFLOWED = `${TEXT_TRUNCATE} display: inline-block; max-width: 50px;`; // overflowed when text is long
const createElementWithStyle = (style, content) => `<a href="#" style="${style}">${content}</a>`;
describe('TooltipOnTruncate component', () => {
let wrapper;
+ let parent;
const createComponent = ({ propsData, ...options } = {}) => {
- wrapper = shallowMount(localVue.extend(TooltipOnTruncate), {
- localVue,
+ wrapper = shallowMount(TooltipOnTruncate, {
attachToDocument: true,
propsData: {
- title: TEST_TITLE,
...propsData,
},
+ attrs: {
+ style: STYLE_OVERFLOWED,
+ },
...options,
});
};
+ const createWrappedComponent = ({ propsData, ...options }) => {
+ // set a parent around the tested component
+ parent = mount(
+ {
+ props: {
+ title: { default: '' },
+ },
+ template: `
+ <TooltipOnTruncate :title="title" truncate-target="child" style="${STYLE_OVERFLOWED}">
+ <div>{{title}}</div>
+ </TooltipOnTruncate>
+ `,
+ components: {
+ TooltipOnTruncate,
+ },
+ },
+ {
+ propsData: { ...propsData },
+ attachToDocument: true,
+ ...options,
+ },
+ );
+
+ wrapper = parent.find(TooltipOnTruncate);
+ };
+
+ const hasTooltip = () => wrapper.classes('js-show-tooltip');
+
afterEach(() => {
wrapper.destroy();
});
- const hasTooltip = () => wrapper.classes('js-show-tooltip');
-
describe('with default target', () => {
- it('renders tooltip if truncated', done => {
+ it('renders tooltip if truncated', () => {
createComponent({
- attrs: {
- style: STYLE_TRUNCATED,
+ propsData: {
+ title: TEXT_LONG,
},
slots: {
- default: [TEST_TITLE],
+ default: [TEXT_LONG],
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-original-title')).toEqual(TEST_TITLE);
- expect(wrapper.attributes('data-placement')).toEqual('top');
- })
- .then(done)
- .catch(done.fail);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
+ });
});
- it('does not render tooltip if normal', done => {
+ it('does not render tooltip if normal', () => {
createComponent({
- attrs: {
- style: STYLE_NORMAL,
+ propsData: {
+ title: TEXT_SHORT,
},
slots: {
- default: [TEST_TITLE],
+ default: [TEXT_SHORT],
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(hasTooltip()).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(false);
+ });
});
});
describe('with child target', () => {
- it('renders tooltip if truncated', done => {
+ it('renders tooltip if truncated', () => {
createComponent({
attrs: {
style: STYLE_NORMAL,
},
propsData: {
+ title: TEXT_LONG,
truncateTarget: 'child',
},
slots: {
- default: createElementWithStyle(STYLE_TRUNCATED, TEST_TITLE),
+ default: createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(hasTooltip()).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ });
});
- it('does not render tooltip if normal', done => {
+ it('does not render tooltip if normal', () => {
createComponent({
propsData: {
truncateTarget: 'child',
},
slots: {
- default: createElementWithStyle(STYLE_NORMAL, TEST_TITLE),
+ default: createElementWithStyle(STYLE_NORMAL, TEXT_LONG),
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(hasTooltip()).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(false);
+ });
});
});
describe('with fn target', () => {
- it('renders tooltip if truncated', done => {
+ it('renders tooltip if truncated', () => {
createComponent({
attrs: {
style: STYLE_NORMAL,
},
propsData: {
+ title: TEXT_LONG,
truncateTarget: el => el.childNodes[1],
},
slots: {
default: [
- createElementWithStyle('', TEST_TITLE),
- createElementWithStyle(STYLE_TRUNCATED, TEST_TITLE),
+ createElementWithStyle('', TEXT_LONG),
+ createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG),
],
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(hasTooltip()).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ });
});
});
describe('placement', () => {
- it('sets data-placement when tooltip is rendered', done => {
+ it('sets data-placement when tooltip is rendered', () => {
const placement = 'bottom';
createComponent({
@@ -151,21 +162,75 @@ describe('TooltipOnTruncate component', () => {
placement,
},
attrs: {
- style: STYLE_TRUNCATED,
+ style: STYLE_OVERFLOWED,
},
slots: {
- default: TEST_TITLE,
+ default: TEXT_LONG,
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(hasTooltip()).toBe(true);
- expect(wrapper.attributes('data-placement')).toEqual(placement);
- })
- .then(done)
- .catch(done.fail);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-placement')).toEqual(placement);
+ });
+ });
+ });
+
+ describe('updates when title and slot content changes', () => {
+ describe('is initialized with a long text', () => {
+ beforeEach(() => {
+ createWrappedComponent({
+ propsData: { title: TEXT_LONG },
+ });
+ return parent.vm.$nextTick();
+ });
+
+ it('renders tooltip', () => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
+ });
+
+ it('does not render tooltip after updated to a short text', () => {
+ parent.setProps({
+ title: TEXT_SHORT,
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
+ .then(() => {
+ expect(hasTooltip()).toBe(false);
+ });
+ });
+ });
+
+ describe('is initialized with a short text', () => {
+ beforeEach(() => {
+ createWrappedComponent({
+ propsData: { title: TEXT_SHORT },
+ });
+ return wrapper.vm.$nextTick();
+ });
+
+ it('does not render tooltip', () => {
+ expect(hasTooltip()).toBe(false);
+ });
+
+ it('renders tooltip after updated to a long text', () => {
+ parent.setProps({
+ title: TEXT_LONG,
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot
+ .then(() => {
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
+ });
+ });
});
});
});
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
new file mode 100644
index 00000000000..1ef2c451aa2
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
@@ -0,0 +1,243 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, :migration, schema: 20200204113223 do
+ let(:users_table) { table(:users) }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:project_authorizations_table) { table(:project_authorizations) }
+ let(:members_table) { table(:members) }
+ let(:group_group_links) { table(:group_group_links) }
+ let(:project_group_links) { table(:project_group_links) }
+
+ let(:user) { users_table.create!(id: 1, email: 'user@example.com', projects_limit: 10) }
+ let(:group) { namespaces_table.create!(type: 'Group', name: 'group', path: 'group') }
+
+ subject { described_class.new.perform([user.id]) }
+
+ context 'missing authorization' do
+ context 'personal project' do
+ before do
+ user_namespace = namespaces_table.create!(owner_id: user.id, name: 'User', path: 'user')
+ projects_table.create!(id: 1,
+ name: 'personal-project',
+ path: 'personal-project',
+ visibility_level: 0,
+ namespace_id: user_namespace.id)
+ end
+
+ it 'creates correct authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 40)]))
+ end
+ end
+
+ context 'group membership' do
+ before do
+ projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: group.id)
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 20, notification_level: 3)
+ end
+
+ it 'creates correct authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
+ end
+ end
+
+ context 'inherited group membership' do
+ before do
+ sub_group = namespaces_table.create!(type: 'Group', name: 'subgroup',
+ path: 'subgroup', parent_id: group.id)
+ projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: sub_group.id)
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 20, notification_level: 3)
+ end
+
+ it 'creates correct authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
+ end
+ end
+
+ context 'project membership' do
+ before do
+ project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: group.id)
+ members_table.create!(user_id: user.id, source_id: project.id, source_type: 'Project',
+ type: 'ProjectMember', access_level: 20, notification_level: 3)
+ end
+
+ it 'creates correct authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
+ end
+ end
+
+ context 'shared group' do
+ before do
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 30, notification_level: 3)
+
+ shared_group = namespaces_table.create!(type: 'Group', name: 'shared group',
+ path: 'shared-group')
+ projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0,
+ namespace_id: shared_group.id)
+
+ group_group_links.create(shared_group_id: shared_group.id, shared_with_group_id: group.id,
+ group_access: 20)
+ end
+
+ it 'creates correct authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
+ end
+ end
+
+ context 'shared project' do
+ before do
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 30, notification_level: 3)
+
+ another_group = namespaces_table.create!(type: 'Group', name: 'another group', path: 'another-group')
+ shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project',
+ visibility_level: 0, namespace_id: another_group.id)
+
+ project_group_links.create(project_id: shared_project.id, group_id: group.id, group_access: 20)
+ end
+
+ it 'creates correct authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
+ end
+ end
+ end
+
+ context 'unapproved access requests' do
+ context 'group membership' do
+ before do
+ projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: group.id)
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 20, requested_at: Time.now, notification_level: 3)
+ end
+
+ it 'does not create authorization' do
+ expect { subject }.not_to change { project_authorizations_table.count }.from(0)
+ end
+ end
+
+ context 'inherited group membership' do
+ before do
+ sub_group = namespaces_table.create!(type: 'Group', name: 'subgroup', path: 'subgroup',
+ parent_id: group.id)
+ projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: sub_group.id)
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 20, requested_at: Time.now, notification_level: 3)
+ end
+
+ it 'does not create authorization' do
+ expect { subject }.not_to change { project_authorizations_table.count }.from(0)
+ end
+ end
+
+ context 'project membership' do
+ before do
+ project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: group.id)
+ members_table.create!(user_id: user.id, source_id: project.id, source_type: 'Project',
+ type: 'ProjectMember', access_level: 20, requested_at: Time.now, notification_level: 3)
+ end
+
+ it 'does not create authorization' do
+ expect { subject }.not_to change { project_authorizations_table.count }.from(0)
+ end
+ end
+
+ context 'shared group' do
+ before do
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 30, requested_at: Time.now, notification_level: 3)
+
+ shared_group = namespaces_table.create!(type: 'Group', name: 'shared group',
+ path: 'shared-group')
+ projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0,
+ namespace_id: shared_group.id)
+
+ group_group_links.create(shared_group_id: shared_group.id, shared_with_group_id: group.id,
+ group_access: 20)
+ end
+
+ it 'does not create authorization' do
+ expect { subject }.not_to change { project_authorizations_table.count }.from(0)
+ end
+ end
+
+ context 'shared project' do
+ before do
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 30, requested_at: Time.now, notification_level: 3)
+
+ another_group = namespaces_table.create!(type: 'Group', name: 'another group', path: 'another-group')
+ shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project',
+ visibility_level: 0, namespace_id: another_group.id)
+
+ project_group_links.create(project_id: shared_project.id, group_id: group.id, group_access: 20)
+ end
+
+ it 'does not create authorization' do
+ expect { subject }.not_to change { project_authorizations_table.count }.from(0)
+ end
+ end
+ end
+
+ context 'incorrect authorization' do
+ before do
+ project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: group.id)
+ members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
+ type: 'GroupMember', access_level: 30, notification_level: 3)
+
+ project_authorizations_table.create!(user_id: user.id, project_id: project.id,
+ access_level: 10)
+ end
+
+ it 'fixes authorization' do
+ expect { subject }.not_to change { project_authorizations_table.count }.from(1)
+ expect(project_authorizations_table.all).to(
+ match_array([have_attributes(user_id: 1, project_id: 1, access_level: 30)]))
+ end
+ end
+
+ context 'unwanted authorization' do
+ before do
+ project = projects_table.create!(name: 'group-project', path: 'group-project',
+ visibility_level: 0, namespace_id: group.id)
+
+ project_authorizations_table.create!(user_id: user.id, project_id: project.id,
+ access_level: 10)
+ end
+
+ it 'deletes authorization' do
+ expect { subject }.to change { project_authorizations_table.count }.from(1).to(0)
+ end
+ end
+
+ context 'deleted user' do
+ let(:nonexistent_user_id) { User.maximum(:id).to_i + 999 }
+
+ it 'does not fail' do
+ expect { described_class.new.perform([nonexistent_user_id]) }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 4733607ccc0..61d7400b95e 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -175,7 +175,7 @@ describe Gitlab::Diff::File do
[diff_file.new_content_sha, diff_file.new_path], [diff_file.old_content_sha, diff_file.old_path]
]
- expect(project.repository).to receive(:blobs_at).with(items, blob_size_limit: 100 * 1024).and_call_original
+ expect(project.repository).to receive(:blobs_at).with(items, blob_size_limit: 10.megabytes).and_call_original
old_data = diff_file.old_blob.data
data = diff_file.new_blob.data
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 820578dfc6e..5e1d6199c3c 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -279,4 +279,19 @@ describe Gitlab::GitalyClient::CommitService do
expect(subject.deletions).to eq(15)
end
end
+
+ describe '#find_commits' do
+ it 'sends an RPC request' do
+ request = Gitaly::FindCommitsRequest.new(
+ repository: repository_message,
+ disable_walk: true,
+ order: 'TOPO'
+ )
+
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
+ .with(request, kind_of(Hash)).and_return([])
+
+ client.find_commits(order: 'topo')
+ end
+ end
end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 6e5c36172e2..1c579128223 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -97,87 +97,68 @@ describe Gitlab::ProjectAuthorizations do
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
- context 'when feature flag share_group_with_group is enabled' do
- before do
- stub_feature_flags(share_group_with_group: true)
- end
-
- context 'group user' do
- let(:user) { group_user }
+ context 'group user' do
+ let(:user) { group_user }
- it 'creates proper authorizations' do
- mapping = map_access_levels(authorizations)
+ it 'creates proper authorizations' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[project_parent.id]).to be_nil
- expect(mapping[project.id]).to eq(Gitlab::Access::DEVELOPER)
- expect(mapping[project_child.id]).to eq(Gitlab::Access::DEVELOPER)
- end
+ expect(mapping[project_parent.id]).to be_nil
+ expect(mapping[project.id]).to eq(Gitlab::Access::DEVELOPER)
+ expect(mapping[project_child.id]).to eq(Gitlab::Access::DEVELOPER)
end
+ end
- context 'parent group user' do
- let(:user) { parent_group_user }
+ context 'parent group user' do
+ let(:user) { parent_group_user }
- it 'creates proper authorizations' do
- mapping = map_access_levels(authorizations)
+ it 'creates proper authorizations' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[project_parent.id]).to be_nil
- expect(mapping[project.id]).to be_nil
- expect(mapping[project_child.id]).to be_nil
- end
+ expect(mapping[project_parent.id]).to be_nil
+ expect(mapping[project.id]).to be_nil
+ expect(mapping[project_child.id]).to be_nil
end
+ end
- context 'child group user' do
- let(:user) { child_group_user }
+ context 'child group user' do
+ let(:user) { child_group_user }
- it 'creates proper authorizations' do
- mapping = map_access_levels(authorizations)
+ it 'creates proper authorizations' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[project_parent.id]).to be_nil
- expect(mapping[project.id]).to be_nil
- expect(mapping[project_child.id]).to be_nil
- end
+ expect(mapping[project_parent.id]).to be_nil
+ expect(mapping[project.id]).to be_nil
+ expect(mapping[project_child.id]).to be_nil
end
end
- context 'when feature flag share_group_with_group is disabled' do
- before do
- stub_feature_flags(share_group_with_group: false)
- end
-
- context 'group user' do
- let(:user) { group_user }
-
- it 'creates proper authorizations' do
- mapping = map_access_levels(authorizations)
+ context 'user without accepted access request' do
+ let!(:user) { create(:user) }
- expect(mapping[project_parent.id]).to be_nil
- expect(mapping[project.id]).to be_nil
- expect(mapping[project_child.id]).to be_nil
- end
- end
+ it 'does not have access to group and its projects' do
+ create(:group_member, :developer, :access_request, user: user, group: group)
- context 'parent group user' do
- let(:user) { parent_group_user }
+ mapping = map_access_levels(authorizations)
- it 'creates proper authorizations' do
- mapping = map_access_levels(authorizations)
-
- expect(mapping[project_parent.id]).to be_nil
- expect(mapping[project.id]).to be_nil
- expect(mapping[project_child.id]).to be_nil
- end
+ expect(mapping[project_parent.id]).to be_nil
+ expect(mapping[project.id]).to be_nil
+ expect(mapping[project_child.id]).to be_nil
end
+ end
- context 'child group user' do
- let(:user) { child_group_user }
+ context 'unrelated project owner' do
+ let(:common_id) { [Project.maximum(:id).to_i, Namespace.maximum(:id).to_i].max + 999 }
+ let!(:group) { create(:group, id: common_id) }
+ let!(:unrelated_project) { create(:project, id: common_id) }
+ let(:user) { unrelated_project.owner }
- it 'creates proper authorizations' do
- mapping = map_access_levels(authorizations)
+ it 'does not have access to group and its projects' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[project_parent.id]).to be_nil
- expect(mapping[project.id]).to be_nil
- expect(mapping[project_child.id]).to be_nil
- end
+ expect(mapping[project_parent.id]).to be_nil
+ expect(mapping[project.id]).to be_nil
+ expect(mapping[project_child.id]).to be_nil
end
end
end
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
new file mode 100644
index 00000000000..77ad2b2dc8e
--- /dev/null
+++ b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200204113223_schedule_recalculate_project_authorizations.rb')
+
+describe ScheduleRecalculateProjectAuthorizations, :migration do
+ let(:users_table) { table(:users) }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:project_authorizations_table) { table(:project_authorizations) }
+
+ let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 1) }
+ let(:user2) { users_table.create!(name: 'user2', email: 'user2@example.com', projects_limit: 1) }
+ let(:group) { namespaces_table.create!(id: 1, type: 'Group', name: 'group', path: 'group') }
+ let(:project) do
+ projects_table.create!(id: 1, name: 'project', path: 'project',
+ visibility_level: 0, namespace_id: group.id)
+ end
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+
+ project_authorizations_table.create!(user_id: user1.id, project_id: project.id, access_level: 30)
+ project_authorizations_table.create!(user_id: user2.id, project_id: project.id, access_level: 30)
+ end
+
+ it 'schedules background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ expect(described_class::MIGRATION).to be_scheduled_migration([user1.id])
+ expect(described_class::MIGRATION).to be_scheduled_migration([user2.id])
+ end
+ end
+ end
+
+ it 'ignores projects with higher id than maximum group id' do
+ another_user = users_table.create!(name: 'another user', email: 'another-user@example.com',
+ projects_limit: 1)
+ ignored_project = projects_table.create!(id: 2, name: 'ignored-project', path: 'ignored-project',
+ visibility_level: 0, namespace_id: group.id)
+ project_authorizations_table.create!(user_id: another_user.id, project_id: ignored_project.id,
+ access_level: 30)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ expect(described_class::MIGRATION).to be_scheduled_migration([user1.id])
+ expect(described_class::MIGRATION).to be_scheduled_migration([user2.id])
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5c56d1aa757..79d9e42666c 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1980,6 +1980,23 @@ describe Project do
expect(project.reload.import_url).to eq('http://test.com')
end
+
+ it 'saves the url credentials percent decoded' do
+ url = 'http://user:pass%21%3F%40@github.com/t.git'
+ project = build(:project, import_url: url)
+
+ # When the credentials are not decoded this expectation fails
+ expect(project.import_url).to eq(url)
+ expect(project.import_data.credentials).to eq(user: 'user', password: 'pass!?@')
+ end
+
+ it 'saves url with no credentials' do
+ url = 'http://github.com/t.git'
+ project = build(:project, import_url: url)
+
+ expect(project.import_url).to eq(url)
+ expect(project.import_data.credentials).to eq(user: nil, password: nil)
+ end
end
describe '#container_registry_url' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 0adf3fc8e85..00ffc3cae54 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -325,6 +325,14 @@ describe Repository do
expect(repository.commits(nil, all: true, limit: 60).size).to eq(60)
end
end
+
+ context "when 'order' flag is set" do
+ it 'passes order option to perform the query' do
+ expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(order: 'topo')).and_call_original
+
+ repository.commits('master', limit: 1, order: 'topo')
+ end
+ end
end
describe '#new_commits' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index e390f3945a9..170b9ccccf8 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -12,7 +12,6 @@ describe API::Commits do
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
-
let(:project_id) { project.id }
let(:current_user) { nil }
@@ -241,6 +240,40 @@ describe API::Commits do
end
end
end
+
+ context 'with order parameter' do
+ let(:route) { "/projects/#{project_id}/repository/commits?ref_name=0031876&per_page=6&order=#{order}" }
+
+ context 'set to topo' do
+ let(:order) { 'topo' }
+
+ # git log --graph -n 6 --pretty=format:"%h" --topo-order 0031876
+ # * 0031876
+ # |\
+ # | * 48ca272
+ # | * 335bc94
+ # * | bf6e164
+ # * | 9d526f8
+ # |/
+ # * 1039376
+ it 'returns project commits ordered by topo order' do
+ commits = project.repository.commits("0031876", limit: 6, order: 'topo')
+
+ get api(route, current_user)
+
+ expect(json_response.size).to eq(6)
+ expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id))
+ end
+ end
+
+ context 'set to blank' do
+ let(:order) { '' }
+
+ it_behaves_like '400 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 09e005398a9..19422d4ca39 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -795,13 +795,13 @@ describe API::Issues do
it 'returns issues from non archived projects only by default' do
get api("/groups/#{group1.id}/issues", user), params: { scope: 'all' }
- expect_response_contain_exactly(issue2, issue1)
+ expect_paginated_array_response([issue2.id, issue1.id])
end
it 'returns issues from archived and non archived projects when non_archived is false' do
get api("/groups/#{group1.id}/issues", user), params: { non_archived: false, scope: 'all' }
- expect_response_contain_exactly(issue1, issue2, issue3)
+ expect_paginated_array_response([issue3.id, issue2.id, issue1.id])
end
end
end
@@ -888,9 +888,4 @@ describe API::Issues do
include_examples 'time tracking endpoints', 'issue'
end
-
- def expect_response_contain_exactly(*items)
- expect(json_response.length).to eq(items.size)
- expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id))
- end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 427a361295c..00af0937dd7 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -41,8 +41,7 @@ describe API::MergeRequests do
it 'returns merge requests for public projects' do
get api(endpoint_path)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
end
end
@@ -87,10 +86,11 @@ describe API::MergeRequests do
it 'returns an array of all merge_requests' do
get api(endpoint_path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
+
expect(json_response.last['title']).to eq(merge_request.title)
expect(json_response.last).to have_key('web_url')
expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
@@ -111,7 +111,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
+ expect_successful_response_with_paginated_array
expect(json_response.last['labels'].pluck('name')).to eq([label2.title, label.title])
expect(json_response.last['labels'].first).to match_schema('/public_api/v4/label_basic')
end
@@ -139,11 +139,11 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
expect(json_response.last['iid']).to eq(merge_request.iid)
expect(json_response.last['title']).to eq(merge_request.title)
expect(json_response.last).to have_key('web_url')
@@ -157,10 +157,10 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
expect(json_response.last['title']).to eq(merge_request.title)
end
@@ -169,10 +169,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response([merge_request.id])
expect(json_response.last['title']).to eq(merge_request.title)
end
@@ -181,10 +178,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response([merge_request_closed.id])
expect(json_response.first['title']).to eq(merge_request_closed.title)
end
@@ -193,10 +187,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response([merge_request_merged.id])
expect(json_response.first['title']).to eq(merge_request_merged.title)
end
@@ -210,17 +201,13 @@ describe API::MergeRequests do
it 'returns an empty array if no issue matches milestone' do
get api(endpoint_path, user), params: { milestone: '1.0.0' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it 'returns an empty array if milestone does not exist' do
get api(endpoint_path, user), params: { milestone: 'foo' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it 'returns an array of merge requests in given milestone' do
@@ -234,9 +221,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests matching state in milestone' do
get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect_paginated_array_response([merge_request_closed.id])
expect(json_response.first['id']).to eq(merge_request_closed.id)
end
@@ -248,8 +233,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label2.title, label.title])
end
@@ -259,9 +243,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it 'returns an empty array if no merge request matches labels' do
@@ -269,9 +251,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it 'returns an array of labeled merge requests where all labels match' do
@@ -279,8 +259,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label2.title, label.title])
end
@@ -288,8 +267,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests with any label when filtering by any label' do
get api(endpoint_path, user), params: { labels: [" #{label.title} ", " #{label2.title} "] }
- expect_paginated_array_response
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label2.title, label.title])
expect(json_response.first['id']).to eq(merge_request.id)
@@ -298,8 +276,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests with any label when filtering by any label' do
get api(endpoint_path, user), params: { labels: ["#{label.title} , #{label2.title}"] }
- expect_paginated_array_response
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label2.title, label.title])
expect(json_response.first['id']).to eq(merge_request.id)
@@ -308,7 +285,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests with any label when filtering by any label' do
get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request.id)
end
@@ -316,10 +293,9 @@ describe API::MergeRequests do
it 'returns an array of merge requests without a label when filtering by no label' do
get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE }
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect_paginated_array_response
- expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id, merge_request_closed.id
+ ])
end
end
@@ -339,10 +315,7 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['id']).to eq(mr2.id)
+ expect_paginated_array_response([mr2.id])
end
context 'with ordering' do
@@ -356,10 +329,10 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect_paginated_array_response([
+ merge_request_closed.id, merge_request_locked.id,
+ merge_request_merged.id, merge_request.id
+ ])
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
@@ -369,10 +342,10 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect_paginated_array_response([
+ merge_request.id, merge_request_merged.id,
+ merge_request_locked.id, merge_request_closed.id
+ ])
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -414,10 +387,10 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect_paginated_array_response([
+ merge_request.id, merge_request_locked.id,
+ merge_request_merged.id, merge_request_closed.id
+ ])
response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -427,10 +400,10 @@ describe API::MergeRequests do
get api(path, user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect_paginated_array_response([
+ merge_request_closed.id, merge_request_locked.id,
+ merge_request_merged.id, merge_request.id
+ ])
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
@@ -440,7 +413,9 @@ describe API::MergeRequests do
it 'returns merge requests with the given source branch' do
get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id, merge_request_closed.id
+ ])
end
end
@@ -448,7 +423,9 @@ describe API::MergeRequests do
it 'returns merge requests with the given target branch' do
get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id, merge_request_closed.id
+ ])
end
end
end
@@ -471,7 +448,10 @@ describe API::MergeRequests do
it 'returns an array of all merge requests' do
get api('/merge_requests', user), params: { scope: 'all' }
- expect_paginated_array_response
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
end
it "returns authentication error without any scope" do
@@ -507,30 +487,23 @@ describe API::MergeRequests do
it 'returns an array of all merge requests except unauthorized ones' do
get api('/merge_requests', user), params: { scope: :all }
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.map { |mr| mr['id'] })
- .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request_locked.id, merge_request2.id)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request2.id, merge_request_locked.id, merge_request_closed.id, merge_request.id
+ ])
end
it "returns an array of no merge_requests when wip=yes" do
get api("/merge_requests", user), params: { wip: 'yes' }
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it "returns an array of no merge_requests when wip=no" do
get api("/merge_requests", user), params: { wip: 'no' }
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.map { |mr| mr['id'] })
- .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request_locked.id, merge_request2.id)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request2.id, merge_request_locked.id, merge_request_closed.id, merge_request.id
+ ])
end
it 'does not return unauthorized merge requests' do
@@ -539,7 +512,9 @@ describe API::MergeRequests do
get api('/merge_requests', user), params: { scope: :all }
- expect_response_contain_exactly(merge_request2, merge_request_merged, merge_request_closed, merge_request, merge_request_locked)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request2.id, merge_request_locked.id, merge_request_closed.id, merge_request.id
+ ])
expect(json_response.map { |mr| mr['id'] }).not_to include(merge_request3.id)
end
@@ -548,7 +523,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2)
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests authored by the given user' do
@@ -556,7 +531,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), params: { author_id: user2.id, scope: :all }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests assigned to the given user' do
@@ -564,7 +539,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), params: { assignee_id: user2.id, scope: :all }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests with no assignee' do
@@ -572,7 +547,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), params: { assignee_id: 'None', scope: :all }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests with any assignee' do
@@ -581,7 +556,10 @@ describe API::MergeRequests do
get api('/merge_requests', user), params: { assignee_id: 'Any', scope: :all }
- expect_response_contain_exactly(merge_request, merge_request2, merge_request_closed, merge_request_merged, merge_request_locked)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request2.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
end
it 'returns an array of merge requests assigned to me' do
@@ -589,7 +567,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), params: { scope: 'assigned_to_me' }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests assigned to me (kebab-case)' do
@@ -597,7 +575,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), params: { scope: 'assigned-to-me' }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests created by me' do
@@ -605,7 +583,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), params: { scope: 'created_by_me' }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns an array of merge requests created by me (kebab-case)' do
@@ -613,7 +591,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), params: { scope: 'created-by-me' }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
it 'returns merge requests reacted by the authenticated user by the given emoji' do
@@ -622,14 +600,16 @@ describe API::MergeRequests do
get api('/merge_requests', user2), params: { my_reaction_emoji: award_emoji.name, scope: 'all' }
- expect_response_ordered_exactly(merge_request3)
+ expect_paginated_array_response([merge_request3.id])
end
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
get api('/merge_requests', user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id, merge_request_closed.id
+ ])
end
end
@@ -637,7 +617,9 @@ describe API::MergeRequests do
it 'returns merge requests with the given target branch' do
get api('/merge_requests', user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id, merge_request_closed.id
+ ])
end
end
@@ -646,7 +628,7 @@ describe API::MergeRequests do
get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user)
- expect_response_ordered_exactly(merge_request2)
+ expect_paginated_array_response([merge_request2.id])
end
it 'returns merge requests created after a specific date' do
@@ -654,7 +636,7 @@ describe API::MergeRequests do
get api("/merge_requests?created_after=#{merge_request2.created_at}", user)
- expect_response_ordered_exactly(merge_request2)
+ expect_paginated_array_response([merge_request2.id])
end
it 'returns merge requests updated before a specific date' do
@@ -662,7 +644,7 @@ describe API::MergeRequests do
get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user)
- expect_response_ordered_exactly(merge_request2)
+ expect_paginated_array_response([merge_request2.id])
end
it 'returns merge requests updated after a specific date' do
@@ -670,7 +652,7 @@ describe API::MergeRequests do
get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user)
- expect_response_ordered_exactly(merge_request2)
+ expect_paginated_array_response([merge_request2.id])
end
context 'search params' do
@@ -681,25 +663,25 @@ describe API::MergeRequests do
it 'returns merge requests matching given search string for title' do
get api("/merge_requests", user), params: { search: merge_request.title }
- expect_response_ordered_exactly(merge_request)
+ expect_paginated_array_response([merge_request.id])
end
it 'returns merge requests matching given search string for title and scoped in title' do
get api("/merge_requests", user), params: { search: merge_request.title, in: 'title' }
- expect_response_ordered_exactly(merge_request)
+ expect_paginated_array_response([merge_request.id])
end
- it 'returns an empty array if no merge reques matches given search string for description and scoped in title' do
+ it 'returns an empty array if no merge request matches given search string for description and scoped in title' do
get api("/merge_requests", user), params: { search: merge_request.description, in: 'title' }
- expect_response_contain_exactly
+ expect_empty_array_response
end
it 'returns merge requests for project matching given search string for description' do
get api("/merge_requests", user), params: { project_id: project.id, search: merge_request.description }
- expect_response_ordered_exactly(merge_request)
+ expect_paginated_array_response([merge_request.id])
end
end
@@ -707,7 +689,7 @@ describe API::MergeRequests do
it 'returns merge requests with the given state' do
get api('/merge_requests', user), params: { state: 'locked' }
- expect_response_contain_exactly(merge_request_locked)
+ expect_paginated_array_response([merge_request_locked.id])
end
end
end
@@ -729,18 +711,13 @@ describe API::MergeRequests do
it "returns an array of no merge_requests when wip=yes" do
get api("/projects/#{project.id}/merge_requests", user), params: { wip: 'yes' }
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it 'returns merge_request by "iids" array' do
get api(endpoint_path, user), params: { iids: [merge_request.iid, merge_request_closed.iid] }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect_paginated_array_response([merge_request_closed.id, merge_request.id])
expect(json_response.first['title']).to eq merge_request_closed.title
expect(json_response.first['id']).to eq merge_request_closed.id
end
@@ -815,12 +792,10 @@ describe API::MergeRequests do
it 'returns an array excluding merge_requests from archived projects' do
get api(endpoint_path, user)
- expect_response_contain_exactly(
- merge_request_merged,
- merge_request_locked,
- merge_request_closed,
- merge_request
- )
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
end
context 'with non_archived param set as false' do
@@ -829,13 +804,10 @@ describe API::MergeRequests do
get api(path, user)
- expect_response_contain_exactly(
- merge_request_merged,
- merge_request_locked,
- merge_request_closed,
- merge_request,
- merge_request_archived
- )
+ expect_paginated_array_response([
+ merge_request_merged.id, merge_request_archived.id, merge_request_locked.id,
+ merge_request_closed.id, merge_request.id
+ ])
end
end
end
@@ -1079,9 +1051,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
commit = merge_request.commits.first
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.size).to eq(merge_request.commits.size)
expect(json_response.first['id']).to eq(commit.id)
expect(json_response.first['title']).to eq(commit.title)
@@ -1105,9 +1075,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.size).to eq(merge_request.context_commits.size)
expect(json_response.first['id']).to eq(context_commit.id)
expect(json_response.first['title']).to eq(context_commit.title)
@@ -1147,9 +1115,7 @@ describe API::MergeRequests do
it 'returns a paginated array of corresponding pipelines' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines")
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.count).to eq(1)
expect(json_response.first['id']).to eq(pipeline.id)
end
@@ -1395,7 +1361,7 @@ describe API::MergeRequests do
expect(json_response['labels']).to eq([])
end
- xit 'empty label param as array, does not add any labels' do
+ it 'empty label param as array, does not add any labels' do
params[:labels] = []
post api("/projects/#{project.id}/merge_requests", user), params: params
@@ -2232,7 +2198,7 @@ describe API::MergeRequests do
expect(json_response['labels']).to eq []
end
- xit 'empty label as array, removes labels' do
+ it 'empty label as array, removes labels' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
params: {
title: 'new issue',
@@ -2240,7 +2206,6 @@ describe API::MergeRequests do
}
expect(response.status).to eq(200)
- # fails, as grape ommits for some reason empty array as optional param value, so nothing it passed along
expect(json_response['labels']).to eq []
end
@@ -2306,9 +2271,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
end
@@ -2316,10 +2279,7 @@ describe API::MergeRequests do
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
+ expect_empty_array_response
end
it 'handles external issues' do
@@ -2332,9 +2292,7 @@ describe API::MergeRequests do
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect_successful_response_with_paginated_array
expect(json_response.length).to eq(2)
expect(json_response.second['title']).to eq(ext_issue.title)
expect(json_response.second['id']).to eq(ext_issue.id)
@@ -2546,22 +2504,4 @@ describe API::MergeRequests do
merge_request_closed.save
merge_request_closed
end
-
- def expect_response_contain_exactly(*items)
- expect_paginated_array_response
- expect(json_response.length).to eq(items.size)
- expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id))
- end
-
- def expect_response_ordered_exactly(*items)
- expect_paginated_array_response
- expect(json_response.length).to eq(items.size)
- expect(json_response.map { |element| element['id'] }).to eq(items.map(&:id))
- end
-
- def expect_paginated_array_response
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- end
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index f5a914bb482..d7ba7f5f69e 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -22,6 +22,42 @@ describe Users::RefreshAuthorizedProjectsService do
service.execute
end
+
+ context 'callbacks' do
+ let(:callback) { double('callback') }
+
+ context 'incorrect_auth_found_callback callback' do
+ let(:user) { create(:user) }
+ let(:service) do
+ described_class.new(user,
+ incorrect_auth_found_callback: callback)
+ end
+
+ it 'is called' do
+ access_level = Gitlab::Access::DEVELOPER
+ create(:project_authorization, user: user, project: project, access_level: access_level)
+
+ expect(callback).to receive(:call).with(project.id, access_level).once
+
+ service.execute
+ end
+ end
+
+ context 'missing_auth_found_callback callback' do
+ let(:service) do
+ described_class.new(user,
+ missing_auth_found_callback: callback)
+ end
+
+ it 'is called' do
+ ProjectAuthorization.delete_all
+
+ expect(callback).to receive(:call).with(project.id, Gitlab::Access::MAINTAINER).once
+
+ service.execute
+ end
+ end
+ end
end
describe '#execute_without_lease' do
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index 4bf6a17c03e..44c38df71b0 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -40,6 +40,17 @@ module ApiHelpers
end
end
+ def expect_empty_array_response
+ expect_successful_response_with_paginated_array
+ expect(json_response.length).to eq(0)
+ end
+
+ def expect_successful_response_with_paginated_array
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ end
+
def expect_paginated_array_response(items)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
diff --git a/yarn.lock b/yarn.lock
index 3989da9f800..ce6a0e94fc1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1774,9 +1774,10 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-at.js@^1.5.4, "at.js@https://gitlab.com/gitlab-org/frontend/At.js.git#121ce9a557b51c33f5693ac8df52d2bda1e53cbe":
+at.js@^1.5.4:
version "1.5.4"
- resolved "https://gitlab.com/gitlab-org/frontend/At.js.git#121ce9a557b51c33f5693ac8df52d2bda1e53cbe"
+ resolved "https://registry.yarnpkg.com/at.js/-/at.js-1.5.4.tgz#8fc60cc80eadbe4874449b166a818e7ae1d784c1"
+ integrity sha512-G8mgUb/PqShPoH8AyjuxsTGvIr1o716BtQUKDM44C8qN2W615y7KGJ68MlTGamd0J0D/m28emUkzagaHTdrGZw==
atob@^2.1.1:
version "2.1.2"