summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue12
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js7
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue95
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue44
-rw-r--r--app/controllers/dashboard/milestones_controller.rb7
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/projects/git_http_controller.rb5
-rw-r--r--app/controllers/projects/issues_controller.rb19
-rw-r--r--app/controllers/projects/merge_requests_controller.rb26
-rw-r--r--app/models/clusters/cluster.rb28
-rw-r--r--app/models/clusters/group.rb10
-rw-r--r--app/models/commit_status.rb3
-rw-r--r--app/models/concerns/deployment_platform.rb1
-rw-r--r--app/models/deployment.rb4
-rw-r--r--app/models/environment_status.rb41
-rw-r--r--app/models/global_milestone.rb44
-rw-r--r--app/models/group.rb3
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/milestone.rb16
-rw-r--r--app/presenters/commit_status_presenter.rb3
-rw-r--r--app/serializers/environment_status_entity.rb1
-rw-r--r--app/serializers/merge_request_widget_entity.rb1
-rw-r--r--app/services/clusters/create_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb5
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml33
-rw-r--r--app/views/projects/pipelines/show.html.haml10
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml7
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/workers/post_receive.rb13
-rw-r--r--changelogs/unreleased/34758-create-group-clusters.yml5
-rw-r--r--changelogs/unreleased/42790-improve-feedback-for-internal-git-access-checks-timeouts.yml5
-rw-r--r--changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml5
-rw-r--r--changelogs/unreleased/51335-fail-early-when-user-cannot-be-identified.yml5
-rw-r--r--changelogs/unreleased/52122-fix-broken-whitespace-button.yml5
-rw-r--r--changelogs/unreleased/52383-ui-filter-assignee-none-any.yml5
-rw-r--r--changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml5
-rw-r--r--changelogs/unreleased/53155-structured-logs-params-array.yml5
-rw-r--r--changelogs/unreleased/ac-post-merge-pipeline.yml5
-rw-r--r--changelogs/unreleased/add-failure-reason-for-execution-timeout.yml5
-rw-r--r--changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml5
-rw-r--r--changelogs/unreleased/pl-uprade-prometheus-alertmanager.yml5
-rw-r--r--changelogs/unreleased/rz_fix_milestone_count.yml5
-rw-r--r--config/routes.rb1
-rw-r--r--db/migrate/20181014203236_create_cluster_groups.rb17
-rw-r--r--db/migrate/20181017001059_add_cluster_type_to_clusters.rb18
-rw-r--r--db/schema.rb13
-rw-r--r--doc/README.md1
-rw-r--r--doc/administration/high_availability/nfs.md19
-rw-r--r--doc/administration/high_availability/redis.md8
-rw-r--r--doc/administration/high_availability/redis_source.md4
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/api/tags.md2
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md6
-rw-r--r--doc/development/testing_guide/review_apps.md15
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/index.md13
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/user/project/clusters/runbooks/index.md49
-rw-r--r--doc/user/project/issue_board.md4
-rw-r--r--doc/user/project/pages/getting_started_part_three.md2
-rw-r--r--doc/user/search/img/issues_filter_none_any.pngbin0 -> 27717 bytes
-rw-r--r--doc/user/search/index.md10
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/gitlab/cache/ci/project_pipeline_status.rb8
-rw-r--r--lib/gitlab/checks/change_access.rb110
-rw-r--r--lib/gitlab/checks/lfs_integrity.rb5
-rw-r--r--lib/gitlab/checks/timed_logger.rb83
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-rw-r--r--lib/gitlab/git/lfs_changes.rb4
-rw-r--r--lib/gitlab/git_access.rb22
-rw-r--r--lib/gitlab/git_post_receive.rb4
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb17
-rw-r--r--lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb10
-rw-r--r--lib/gitlab/identifier.rb21
-rw-r--r--locale/gitlab.pot30
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb9
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb51
-rw-r--r--spec/factories/clusters/clusters.rb15
-rw-r--r--spec/features/boards/modal_filter_spec.rb2
-rw-r--r--spec/features/commits_spec.rb60
-rw-r--r--spec/features/groups/milestone_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb12
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb19
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb11
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json1
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js126
-rw-r--r--spec/javascripts/diffs/mock_data/merge_request_diffs.js42
-rw-r--r--spec/javascripts/job_spec.js265
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js142
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js188
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js22
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb15
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb18
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb3
-rw-r--r--spec/lib/gitlab/checks/timed_logger_spec.rb63
-rw-r--r--spec/lib/gitlab/git/lfs_changes_spec.rb4
-rw-r--r--spec/lib/gitlab/git_access_spec.rb10
-rw-r--r--spec/lib/gitlab/identifier_spec.rb49
-rw-r--r--spec/models/clusters/cluster_spec.rb67
-rw-r--r--spec/models/clusters/group_spec.rb8
-rw-r--r--spec/models/environment_status_spec.rb32
-rw-r--r--spec/models/global_milestone_spec.rb35
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb20
-rw-r--r--spec/models/milestone_spec.rb37
-rw-r--r--spec/requests/api/internal_spec.rb18
-rw-r--r--spec/requests/api/runner_spec.rb9
-rw-r--r--spec/serializers/environment_status_entity_spec.rb3
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb34
-rw-r--r--spec/workers/post_receive_spec.rb133
-rw-r--r--vendor/prometheus/values.yaml4
121 files changed, 1811 insertions, 801 deletions
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 9da0a092a0d..6da4de57dc6 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.4.0 \ No newline at end of file
+8.4.1
diff --git a/Gemfile.lock b/Gemfile.lock
index e533b564d15..72fd7d679d1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -547,7 +547,7 @@ GEM
orm_adapter (0.5.0)
os (1.0.0)
parallel (1.12.1)
- parser (2.5.1.2)
+ parser (2.5.3.0)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.0.1)
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 9bbf62c0eb6..29b5aff0fb1 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -40,17 +40,14 @@ export default {
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
- isWhitespaceVisible() {
- return !getParameterValues('w')[0];
- },
toggleWhitespaceText() {
- if (this.isWhitespaceVisible) {
+ if (this.isWhitespaceVisible()) {
return __('Hide whitespace changes');
}
return __('Show whitespace changes');
},
toggleWhitespacePath() {
- if (this.isWhitespaceVisible) {
+ if (this.isWhitespaceVisible()) {
return mergeUrlParams({ w: 1 }, window.location.href);
}
@@ -67,6 +64,9 @@ export default {
'expandAllFiles',
'toggleShowTreeList',
]),
+ isWhitespaceVisible() {
+ return getParameterValues('w')[0] !== '1';
+ },
},
};
</script>
@@ -121,7 +121,7 @@ export default {
</a>
<a
:href="toggleWhitespacePath"
- class="btn btn-default"
+ class="btn btn-default qa-toggle-whitespace"
>
{{ toggleWhitespaceText }}
</a>
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index b70125c80ca..e22f542b7bf 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -58,11 +58,16 @@ export const alternativeTokenKeys = [
export const conditions = [
{
- url: 'assignee_id=0',
+ url: 'assignee_id=None',
tokenKey: 'assignee',
value: 'none',
},
{
+ url: 'assignee_id=Any',
+ tokenKey: 'assignee',
+ value: 'any',
+ },
+ {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 0a2681b7a1e..b670b0355b7 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -25,7 +25,7 @@ export default {
...mapState('pipelines', ['isLoadingPipeline', 'latestPipeline', 'stages', 'isLoadingJobs']),
ciLintText() {
return sprintf(
- __('You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}'),
+ __('You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}.'),
{
linkStart: `<a href="${_.escape(this.currentProject.web_url)}/-/ci/lint">`,
linkEnd: '</a>',
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 6c87287a4c4..5e7a35e9396 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -2,6 +2,7 @@
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
+import { __ } from '~/locale';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import tooltip from '../../vue_shared/directives/tooltip';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
@@ -31,6 +32,11 @@ export default {
required: true,
},
},
+ deployedTextMap: {
+ running: __('Deploying to'),
+ success: __('Deployed to'),
+ failed: __('Failed to deploy to'),
+ },
data() {
const features = window.gon.features || {};
return {
@@ -54,10 +60,13 @@ export default {
hasMetrics() {
return !!this.deployment.metrics_url;
},
+ deployedText() {
+ return this.$options.deployedTextMap[this.deployment.status];
+ },
},
methods: {
stopEnvironment() {
- const msg = 'Are you sure you want to stop this environment?';
+ const msg = __('Are you sure you want to stop this environment?');
const isConfirmed = confirm(msg); // eslint-disable-line
if (isConfirmed) {
@@ -87,10 +96,10 @@ export default {
<div class="ci-widget media">
<div class="media-body">
<div class="deploy-body">
- <div class="deployment-info">
+ <div class="js-deployment-info deployment-info">
<template v-if="hasDeploymentMeta">
<span>
- Deployed to
+ {{ deployedText }}
</span>
<tooltip-on-truncate
:title="deployment.name"
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 5d9f7cebcf2..a5c69d2bc7a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,4 +1,6 @@
<script>
+import _ from 'underscore';
+import { __ } from '~/locale';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import createFlash from '../flash';
@@ -80,6 +82,7 @@ export default {
const service = this.createService(store);
return {
mr: store,
+ state: store.state,
service,
};
},
@@ -103,6 +106,17 @@ export default {
(!this.mr.isNothingToMergeState && !this.mr.isMergedState)
);
},
+ shouldRenderMergedPipeline() {
+ return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline);
+ },
+ },
+ watch: {
+ state(newVal, oldVal) {
+ if (newVal !== oldVal && this.shouldRenderMergedPipeline) {
+ // init polling
+ this.initPostMergeDeploymentsPolling();
+ }
+ }
},
created() {
this.initPolling();
@@ -112,11 +126,19 @@ export default {
mounted() {
this.setFaviconHelper();
this.initDeploymentsPolling();
+
+ if (this.shouldRenderMergedPipeline) {
+ this.initPostMergeDeploymentsPolling();
+ }
},
beforeDestroy() {
eventHub.$off('mr.discussion.updated', this.checkStatus);
this.pollingInterval.destroy();
this.deploymentsInterval.destroy();
+
+ if (this.postMergeDeploymentsInterval) {
+ this.postMergeDeploymentsInterval.destroy();
+ }
},
methods: {
createService(store) {
@@ -146,7 +168,13 @@ export default {
cb.call(null, data);
}
})
- .catch(() => createFlash('Something went wrong. Please try again.'));
+ .catch(() => createFlash(__('Something went wrong. Please try again.')));
+ },
+ setFaviconHelper() {
+ if (this.mr.ciStatusFaviconPath) {
+ return setFaviconOverlay(this.mr.ciStatusFaviconPath);
+ }
+ return Promise.resolve();
},
initPolling() {
this.pollingInterval = new SmartInterval({
@@ -158,8 +186,14 @@ export default {
});
},
initDeploymentsPolling() {
- this.deploymentsInterval = new SmartInterval({
- callback: this.fetchDeployments,
+ this.deploymentsInterval = this.deploymentsPoll(this.fetchPreMergeDeployments);
+ },
+ initPostMergeDeploymentsPolling() {
+ this.postMergeDeploymentsInterval = this.deploymentsPoll(this.fetchPostMergeDeployments);
+ },
+ deploymentsPoll(callback) {
+ return new SmartInterval({
+ callback,
startingInterval: 30000,
maxInterval: 120000,
hiddenInterval: 240000,
@@ -167,26 +201,29 @@ export default {
immediateExecution: true,
});
},
- setFaviconHelper() {
- if (this.mr.ciStatusFaviconPath) {
- return setFaviconOverlay(this.mr.ciStatusFaviconPath);
- }
- return Promise.resolve();
+ fetchDeployments(target) {
+ return this.service.fetchDeployments(target);
},
- fetchDeployments() {
- return this.service
- .fetchDeployments()
- .then(res => res.data)
- .then(data => {
+ fetchPreMergeDeployments() {
+ return this.fetchDeployments()
+ .then(({ data }) => {
if (data.length) {
this.mr.deployments = data;
}
})
- .catch(() => {
- createFlash(
- 'Something went wrong while fetching the environments for this merge request. Please try again.',
- );
- });
+ .catch(() => this.throwDeploymentsError());
+ },
+ fetchPostMergeDeployments(){
+ return this.fetchDeployments('merge_commit')
+ .then(({ data }) => {
+ if (data.length) {
+ this.mr.postMergeDeployments = data;
+ }
+ })
+ .catch(() => this.throwDeploymentsError());
+ },
+ throwDeploymentsError() {
+ createFlash(__('Something went wrong while fetching the environments for this merge request. Please try again.'));
},
fetchActionsContent() {
this.service
@@ -199,7 +236,7 @@ export default {
Project.initRefSwitcher();
}
})
- .catch(() => createFlash('Something went wrong. Please try again.'));
+ .catch(() => createFlash(__('Something went wrong. Please try again.')));
},
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
@@ -267,7 +304,8 @@ export default {
/>
<deployment
v-for="deployment in mr.deployments"
- :key="deployment.id"
+ :key="`pre-merge-deploy-${deployment.id}`"
+ class="js-pre-merge-deploy"
:deployment="deployment"
/>
<div class="mr-section-container">
@@ -308,5 +346,22 @@ export default {
<mr-widget-merge-help />
</div>
</div>
+
+ <template v-if="shouldRenderMergedPipeline">
+ <mr-widget-pipeline
+ class="js-post-merge-pipeline prepend-top-default"
+ :pipeline="mr.mergePipeline"
+ :ci-status="mr.ciStatus"
+ :has-ci="mr.hasCI"
+ :source-branch="mr.targetBranch"
+ :source-branch-link="mr.targetBranch"
+ />
+ <deployment
+ v-for="postMergeDeployment in mr.postMergeDeployments"
+ :key="`post-merge-deploy-${postMergeDeployment.id}`"
+ :deployment="postMergeDeployment"
+ class="js-post-deployment"
+ />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index fecbfec2214..bf5b85b2ae6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -21,8 +21,12 @@ export default class MRWidgetService {
return axios.delete(this.endpoints.sourceBranchPath);
}
- fetchDeployments() {
- return axios.get(this.endpoints.ciEnvironmentsStatusPath);
+ fetchDeployments(targetParam) {
+ return axios.get(this.endpoints.ciEnvironmentsStatusPath, {
+ params: {
+ environment_target: targetParam
+ }
+ });
}
poll() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index e6655914700..a0c008e7314 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -32,7 +32,9 @@ export default class MergeRequestStore {
this.commitsCount = data.commits_count;
this.divergedCommitsCount = data.diverged_commits_count;
this.pipeline = data.pipeline || {};
+ this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
+ this.postMergeDeployments = this.postMergeDeployments || [];
this.initRebase(data);
if (data.issues_links) {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
index 7f1eb6bcec4..11fac3bb12c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
@@ -1,6 +1,11 @@
<script>
+ import tooltip from '~/vue_shared/directives/tooltip';
+
export default {
name: 'CollapsedCalendarIcon',
+ directives: {
+ tooltip,
+ },
props: {
containerClass: {
type: String,
@@ -17,6 +22,11 @@
required: false,
default: true,
},
+ tooltipText: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
methods: {
click() {
@@ -28,7 +38,13 @@
<template>
<div
+ v-tooltip
:class="containerClass"
+ :title="tooltipText"
+ data-container="body"
+ data-placement="left"
+ data-html="true"
+ data-boundary="viewport"
@click="click"
>
<i
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
index dac438a702d..6e7194ccc9e 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
@@ -1,25 +1,23 @@
<script>
- import { dateInWords } from '../../../lib/utils/datetime_utility';
- import toggleSidebar from './toggle_sidebar.vue';
+ import { __ } from '~/locale';
+ import timeagoMixin from '~/vue_shared/mixins/timeago';
+ import { dateInWords, timeFor } from '~/lib/utils/datetime_utility';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
export default {
name: 'SidebarCollapsedGroupedDatePicker',
components: {
- toggleSidebar,
collapsedCalendarIcon,
},
+ mixins: [
+ timeagoMixin,
+ ],
props: {
collapsed: {
type: Boolean,
required: false,
default: true,
},
- showToggleSidebar: {
- type: Boolean,
- required: false,
- default: false,
- },
minDate: {
type: Date,
required: false,
@@ -51,7 +49,7 @@
},
iconClass() {
const disabledClass = this.disableClickableIcons ? 'disabled' : '';
- return `block sidebar-collapsed-icon calendar-icon ${disabledClass}`;
+ return `sidebar-collapsed-icon calendar-icon ${disabledClass}`;
},
},
methods: {
@@ -63,7 +61,21 @@
const dateWords = dateInWords(date, true);
const parsedDateWords = dateWords ? dateWords.replace(',', '') : dateWords;
- return date ? parsedDateWords : 'None';
+ return date ? parsedDateWords : __('None');
+ },
+ tooltipText(dateType = 'min') {
+ const defaultText = dateType === 'min' ? __('Start date') : __('Due date');
+ const date = this[`${dateType}Date`];
+ const timeAgo = dateType === 'min' ? this.timeFormated(date) : timeFor(date);
+ const dateText = date ? [
+ this.dateText(dateType),
+ `(${timeAgo})`,
+ ].join(' ') : '';
+
+ if (date) {
+ return [defaultText, dateText].join('<br />');
+ }
+ return __('Start and due date');
},
},
};
@@ -71,18 +83,10 @@
<template>
<div class="block sidebar-grouped-item">
- <div
- v-if="showToggleSidebar"
- class="issuable-sidebar-header"
- >
- <toggle-sidebar
- :collapsed="collapsed"
- @toggle="toggleSidebar"
- />
- </div>
<collapsed-calendar-icon
v-if="showMinDateBlock"
:container-class="iconClass"
+ :tooltip-text="tooltipText('min')"
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
@@ -99,7 +103,7 @@
<collapsed-calendar-icon
v-if="maxDate"
:container-class="iconClass"
- :show-icon="!minDate"
+ :tooltip-text="tooltipText('max')"
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 6e17bc212e4..3802aa5f40f 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -4,12 +4,13 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
include MilestoneActions
before_action :projects
+ before_action :groups, only: :index
before_action :milestone, only: [:show, :merge_requests, :participants, :labels]
def index
respond_to do |format|
format.html do
- @milestone_states = GlobalMilestone.states_count(@projects)
+ @milestone_states = Milestone.states_count(@projects.select(:id), @groups.select(:id))
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
@@ -42,4 +43,8 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
@milestone = DashboardMilestone.build(@projects, params[:title])
render_404 unless @milestone
end
+
+ def groups
+ @groups ||= GroupsFinder.new(current_user, state_all: true).execute
+ end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index a7cee426cf1..b42116b0f36 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -10,7 +10,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def index
respond_to do |format|
format.html do
- @milestone_states = GlobalMilestone.states_count(group_projects, group)
+ @milestone_states = Milestone.states_count(group_projects, [group])
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index be708835e30..c0aa39d87c6 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -8,6 +8,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404
rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422
+ rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
@@ -62,6 +63,10 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render plain: exception.message, status: :unprocessable_entity
end
+ def render_503(exception)
+ render plain: exception.message, status: :service_unavailable
+ end
+
def access
@access ||= access_klass.new(access_actor, project,
'http', authentication_abilities: authentication_abilities,
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b06a6f3bb0d..308f666394c 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -9,12 +9,25 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuesCalendar
include SpammableActions
- prepend_before_action :authenticate_user!, only: [:new]
+ def self.authenticate_user_only_actions
+ %i[new]
+ end
+
+ def self.issue_except_actions
+ %i[index calendar new create bulk_update]
+ end
+
+ def self.set_issuables_index_only_actions
+ %i[index calendar]
+ end
+
+ prepend_before_action :authenticate_user!, only: authenticate_user_only_actions
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
- before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update]
- before_action :set_issuables_index, only: [:index, :calendar]
+ before_action :issue, except: issue_except_actions
+
+ before_action :set_issuables_index, only: set_issuables_index_only_actions
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 757b03d0b0e..27b83da4f54 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -168,7 +168,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge
- return access_denied! unless @merge_request.can_be_merged_by?(current_user)
+ access_check_result = merge_access_check
+
+ return access_check_result if access_check_result
status = merge!
@@ -201,9 +203,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def ci_environments_status
- environments = @merge_request.environments_for(current_user).map do |environment|
- EnvironmentStatus.new(environment, @merge_request)
- end
+ environments = if ci_environments_status_on_merge_result?
+ EnvironmentStatus.after_merge_request(@merge_request, current_user)
+ else
+ EnvironmentStatus.for_merge_request(@merge_request, current_user)
+ end
render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments)
end
@@ -241,6 +245,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
private
+ def ci_environments_status_on_merge_result?
+ params[:environment_target] == 'merge_commit'
+ end
+
def target_branch_missing?
@merge_request.has_no_commits? && !@merge_request.target_branch_exists?
end
@@ -256,6 +264,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :failed
end
+ merge_service = ::MergeRequests::MergeService.new(@project, current_user, merge_params)
+
+ unless merge_service.hooks_validation_pass?(@merge_request)
+ return :hook_validation_error
+ end
+
return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha
@merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
@@ -318,6 +332,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
access_denied! unless access_check
end
+ def merge_access_check
+ access_denied! unless @merge_request.can_be_merged_by?(current_user)
+ end
+
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42441
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42438')
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 95efecfc41d..222e4217e67 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -20,6 +20,12 @@ module Clusters
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
+ has_many :cluster_groups, class_name: 'Clusters::Group'
+ has_many :groups, through: :cluster_groups, class_name: '::Group'
+
+ has_one :cluster_group, -> { order(id: :desc) }, class_name: 'Clusters::Group'
+ has_one :group, through: :cluster_group, class_name: '::Group'
+
# we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
@@ -38,8 +44,12 @@ module Clusters
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
+ validates :cluster_type, presence: true
validate :restrict_modification, on: :update
+ validate :no_groups, unless: :group_type?
+ validate :no_projects, unless: :project_type?
+
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
@@ -50,6 +60,12 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
+ enum cluster_type: {
+ instance_type: 1,
+ group_type: 2,
+ project_type: 3
+ }
+
enum platform_type: {
kubernetes: 1
}
@@ -122,5 +138,17 @@ module Clusters
true
end
+
+ def no_groups
+ if groups.any?
+ errors.add(:cluster, 'cannot have groups assigned')
+ end
+ end
+
+ def no_projects
+ if projects.any?
+ errors.add(:cluster, 'cannot have projects assigned')
+ end
+ end
end
end
diff --git a/app/models/clusters/group.rb b/app/models/clusters/group.rb
new file mode 100644
index 00000000000..2b08a9e47f0
--- /dev/null
+++ b/app/models/clusters/group.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Clusters
+ class Group < ActiveRecord::Base
+ self.table_name = 'cluster_groups'
+
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :group, class_name: '::Group'
+ end
+end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 344f091c872..7d36f9982ab 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -50,7 +50,8 @@ class CommitStatus < ActiveRecord::Base
runner_system_failure: 4,
missing_dependency_failure: 5,
runner_unsupported: 6,
- stale_schedule: 7
+ stale_schedule: 7,
+ job_execution_timeout: 8
}
##
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 91052013592..e57a3383544 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -42,6 +42,7 @@ module DeploymentPlatform
{
name: 'kubernetes-template',
projects: [self],
+ cluster_type: :project_type,
provider_type: :user,
platform_type: :kubernetes,
platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 62dc0f2cbeb..ee5b96e7454 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -127,6 +127,10 @@ class Deployment < ActiveRecord::Base
metrics&.merge(deployment_time: created_at.to_i) || {}
end
+ def status
+ 'success'
+ end
+
private
def prometheus_adapter
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index 5ff3acc0e58..a84871f7253 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -3,21 +3,33 @@
class EnvironmentStatus
include Gitlab::Utils::StrongMemoize
- attr_reader :environment, :merge_request
+ attr_reader :environment, :merge_request, :sha
delegate :id, to: :environment
delegate :name, to: :environment
delegate :project, to: :environment
delegate :deployed_at, to: :deployment, allow_nil: true
+ delegate :status, to: :deployment
- def initialize(environment, merge_request)
+ def self.for_merge_request(mr, user)
+ build_environments_status(mr, user, mr.head_pipeline)
+ end
+
+ def self.after_merge_request(mr, user)
+ return [] unless mr.merged?
+
+ build_environments_status(mr, user, mr.merge_pipeline)
+ end
+
+ def initialize(environment, merge_request, sha)
@environment = environment
@merge_request = merge_request
+ @sha = sha
end
def deployment
strong_memoize(:deployment) do
- environment.first_deployment_for(merge_request.diff_head_sha)
+ environment.first_deployment_for(sha)
end
end
@@ -26,10 +38,9 @@ class EnvironmentStatus
end
def changes
- sha = merge_request.diff_head_sha
return [] if project.route_map_for(sha).nil?
- changed_files.map { |file| build_change(file, sha) }.compact
+ changed_files.map { |file| build_change(file) }.compact
end
def changed_files
@@ -41,7 +52,7 @@ class EnvironmentStatus
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
- def build_change(file, sha)
+ def build_change(file)
public_path = project.public_path_for_source_path(file.new_path, sha)
return if public_path.nil?
@@ -53,4 +64,22 @@ class EnvironmentStatus
external_url: environment.external_url_for(file.new_path, sha)
}
end
+
+ def self.build_environments_status(mr, user, pipeline)
+ return [] unless pipeline.present?
+
+ find_environments(user, pipeline).map do |environment|
+ EnvironmentStatus.new(environment, mr, pipeline.sha)
+ end
+ end
+ private_class_method :build_environments_status
+
+ def self.find_environments(user, pipeline)
+ env_ids = Deployment.where(deployable: pipeline.builds).select(:environment_id)
+
+ Environment.available.where(id: env_ids).select do |environment|
+ Ability.allowed?(user, :read_environment, environment)
+ end
+ end
+ private_class_method :find_environments
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index a6cebabe089..085ffd16c6a 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -34,50 +34,6 @@ class GlobalMilestone
new(title, child_milestones)
end
- def self.states_count(projects, group = nil)
- legacy_group_milestones_count = legacy_group_milestone_states_count(projects)
- group_milestones_count = group_milestones_states_count(group)
-
- legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count|
- legacy_group_milestones_count + group_milestones_count
- end
- end
-
- def self.group_milestones_states_count(group)
- return STATE_COUNT_HASH unless group
-
- params = { group_ids: [group.id], state: 'all' }
-
- relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
- grouped_by_state = relation.reorder(nil).group(:state).count
-
- {
- opened: grouped_by_state['active'] || 0,
- closed: grouped_by_state['closed'] || 0,
- all: grouped_by_state.values.sum
- }
- end
-
- # Counts the legacy group milestones which must be grouped by title
- def self.legacy_group_milestone_states_count(projects)
- return STATE_COUNT_HASH unless projects
-
- params = { project_ids: projects.map(&:id), state: 'all' }
-
- relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
- project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
-
- opened = count_by_state(project_milestones_by_state_and_title, 'active')
- closed = count_by_state(project_milestones_by_state_and_title, 'closed')
- all = project_milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
-
- {
- opened: opened,
- closed: closed,
- all: all
- }
- end
-
def self.count_by_state(milestones_by_state_and_title, state)
milestones_by_state_and_title.count do |(milestone_state, _), _|
milestone_state == state
diff --git a/app/models/group.rb b/app/models/group.rb
index 612c546ca57..c67479110c9 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -41,6 +41,9 @@ class Group < Namespace
has_many :boards
has_many :badges, class_name: 'GroupBadge'
+ has_many :cluster_groups, class_name: 'Clusters::Group'
+ has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster'
+
has_many :todos
accepts_nested_attributes_for :variables, allow_destroy: true
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6559f94a696..7eef08aa6a3 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -204,6 +204,12 @@ class MergeRequest < ActiveRecord::Base
head_pipeline&.sha == diff_head_sha ? head_pipeline : nil
end
+ def merge_pipeline
+ return unless merged?
+
+ target_project.pipeline_for(target_branch, merge_commit_sha)
+ end
+
# Pattern used to extract `!123` merge request references from text
#
# This pattern supports cross-project references.
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9efdee7b795..3cc8e2c44bb 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -170,6 +170,22 @@ class Milestone < ActiveRecord::Base
sorted.with_order_id_desc
end
+ def self.states_count(projects, groups = nil)
+ return STATE_COUNT_HASH unless projects || groups
+
+ counts = Milestone
+ .for_projects_and_groups(projects&.map(&:id), groups&.map(&:id))
+ .reorder(nil)
+ .group(:state)
+ .count
+
+ {
+ opened: counts['active'] || 0,
+ closed: counts['closed'] || 0,
+ all: counts.values.sum
+ }
+ end
+
##
# Returns the String necessary to reference this Milestone in Markdown. Group
# milestones only support name references, and do not support cross-project
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 29eaad759bb..a866e76df5a 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -9,7 +9,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
runner_system_failure: 'There has been a runner system failure, please try again',
missing_dependency_failure: 'There has been a missing dependency failure',
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
- stale_schedule: 'Delayed job could not be executed by some reason, please try again'
+ stale_schedule: 'Delayed job could not be executed by some reason, please try again',
+ job_execution_timeout: 'The script exceeded the maximum execution time set for the job'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb
index 3dfa4f204c9..f87cc894d2f 100644
--- a/app/serializers/environment_status_entity.rb
+++ b/app/serializers/environment_status_entity.rb
@@ -5,6 +5,7 @@ class EnvironmentStatusEntity < Grape::Entity
expose :id
expose :name
+ expose :status
expose :url do |es|
project_environment_path(es.project, es.environment)
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 9ec24f799ef..f33a1654d5e 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -55,6 +55,7 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_commit_message
expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline
+ expose :merge_pipeline, with: PipelineDetailsEntity, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)}
# Booleans
expose :merge_ongoing?, as: :merge_ongoing
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index c6e955800af..cd843b8ffa8 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -9,9 +9,9 @@ module Clusters
end
def execute(project:, access_token: nil)
- raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?(project)
+ raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?(project)
- cluster_params = params.merge(user: current_user, projects: [project])
+ cluster_params = params.merge(user: current_user, cluster_type: :project_type, projects: [project])
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index fb44f809c41..70a67baa01c 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -49,6 +49,11 @@ module MergeRequests
end
end
+ # Overridden in EE.
+ def hooks_validation_pass?(_merge_request)
+ true
+ end
+
private
def error_check!
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index c63ff070f70..95bba47802c 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -19,30 +19,23 @@
#js-pipeline-graph-vue
#js-tab-builds.tab-pane
- - if pipeline.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - pipeline.yaml_errors.split(",").each do |error|
- %li= error
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", project_ci_lint_path(@project)}
+ - if pipeline.legacy_stages.present?
+ .table-holder.pipeline-holder
+ %table.table.ci-table.pipeline
+ %thead
+ %tr
+ %th Status
+ %th Job ID
+ %th Name
+ %th
+ %th Coverage
+ %th
+ = render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
- - if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
+ - elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
- .table-holder.pipeline-holder
- %table.table.ci-table.pipeline
- %thead
- %tr
- %th Status
- %th Job ID
- %th Name
- %th
- %th Coverage
- %th
- = render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
-
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index ff0ed3ed30d..193d437dad1 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -9,6 +9,14 @@
- if @pipeline.commit.present?
= render "projects/pipelines/info", commit: @pipeline.commit
- = render "projects/pipelines/with_tabs", pipeline: @pipeline
+ - if @pipeline.builds.empty? && @pipeline.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - @pipeline.yaml_errors.split(",").each do |error|
+ %li= error
+ You can test your .gitlab-ci.yml in #{link_to "CI Lint", project_ci_lint_path(@project)}.
+ - else
+ = render "projects/pipelines/with_tabs", pipeline: @pipeline
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index cb45928d9a5..1d876cc4a5d 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -61,7 +61,10 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- = _('No Assignee')
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
@@ -81,7 +84,7 @@
%li.filter-dropdown-item{ data: { value: 'upcoming' } }
%button.btn.btn-link{ type: 'button' }
= _('Upcoming')
- %li.filter-dropdown-item{ 'data-value' => 'started' }
+ %li.filter-dropdown-item{ data: { value: 'started' } }
%button.btn.btn-link{ type: 'button' }
= _('Started')
%li.divider.droplab-item-ignore
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 10ffe8dd37f..5295e656ab0 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -24,7 +24,7 @@
.block.milestone
.sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
= icon('clock-o', 'aria-hidden': 'true')
- %span.milestone-title
+ %span.milestone-title.collapse-truncated-title
- if issuable.milestone
= issuable.milestone.title
- else
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 09a594cdb4e..72a1733a2a8 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -29,15 +29,14 @@ class PostReceive
def process_project_changes(post_received)
changes = []
refs = Set.new
+ @user = post_received.identify
- post_received.changes_refs do |oldrev, newrev, ref|
- @user ||= post_received.identify(newrev)
-
- unless @user
- log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
- return false # rubocop:disable Cop/AvoidReturnFromBlocks
- end
+ unless @user
+ log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
+ return false
+ end
+ post_received.changes_refs do |oldrev, newrev, ref|
if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
elsif Gitlab::Git.branch_ref?(ref)
diff --git a/changelogs/unreleased/34758-create-group-clusters.yml b/changelogs/unreleased/34758-create-group-clusters.yml
new file mode 100644
index 00000000000..50efde3cac3
--- /dev/null
+++ b/changelogs/unreleased/34758-create-group-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: Adds model and migrations to enable group level clusters
+merge_request: 22307
+author:
+type: other
diff --git a/changelogs/unreleased/42790-improve-feedback-for-internal-git-access-checks-timeouts.yml b/changelogs/unreleased/42790-improve-feedback-for-internal-git-access-checks-timeouts.yml
new file mode 100644
index 00000000000..d58d8da3a0e
--- /dev/null
+++ b/changelogs/unreleased/42790-improve-feedback-for-internal-git-access-checks-timeouts.yml
@@ -0,0 +1,5 @@
+---
+title: Adds trace of each access check when git push times out
+merge_request: 22265
+author:
+type: added
diff --git a/changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml b/changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml
new file mode 100644
index 00000000000..97052d01b24
--- /dev/null
+++ b/changelogs/unreleased/45669-table-in-jobs-on-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Hide all tables on Pipeline when no Jobs for the Pipeline
+merge_request: 18540
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/51335-fail-early-when-user-cannot-be-identified.yml b/changelogs/unreleased/51335-fail-early-when-user-cannot-be-identified.yml
new file mode 100644
index 00000000000..a042debc28f
--- /dev/null
+++ b/changelogs/unreleased/51335-fail-early-when-user-cannot-be-identified.yml
@@ -0,0 +1,5 @@
+---
+title: If user was not found, service hooks won't run on post receive background job
+merge_request: 22519
+author:
+type: fixed
diff --git a/changelogs/unreleased/52122-fix-broken-whitespace-button.yml b/changelogs/unreleased/52122-fix-broken-whitespace-button.yml
new file mode 100644
index 00000000000..3f261eb2ac5
--- /dev/null
+++ b/changelogs/unreleased/52122-fix-broken-whitespace-button.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken "Show whitespace changes" button on MRs.
+merge_request: 22539
+author:
+type: fixed
diff --git a/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml b/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml
new file mode 100644
index 00000000000..adf153f33ce
--- /dev/null
+++ b/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml
@@ -0,0 +1,5 @@
+---
+title: Add None/Any option for assignee_id in search bar
+merge_request: 22599
+author: Heinrich Lee Yu
+type: added
diff --git a/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml b/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml
new file mode 100644
index 00000000000..7586d7995b7
--- /dev/null
+++ b/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Cache pipeline status per SHA.
+merge_request: 22589
+author:
+type: fixed
diff --git a/changelogs/unreleased/53155-structured-logs-params-array.yml b/changelogs/unreleased/53155-structured-logs-params-array.yml
new file mode 100644
index 00000000000..4d4f68a5c84
--- /dev/null
+++ b/changelogs/unreleased/53155-structured-logs-params-array.yml
@@ -0,0 +1,5 @@
+---
+title: Use key-value pair arrays for API query parameter logging instead of hashes
+merge_request: 22623
+author:
+type: other
diff --git a/changelogs/unreleased/ac-post-merge-pipeline.yml b/changelogs/unreleased/ac-post-merge-pipeline.yml
new file mode 100644
index 00000000000..08322c9cb8a
--- /dev/null
+++ b/changelogs/unreleased/ac-post-merge-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Show post-merge pipeline in merge request page
+merge_request: 22292
+author:
+type: added
diff --git a/changelogs/unreleased/add-failure-reason-for-execution-timeout.yml b/changelogs/unreleased/add-failure-reason-for-execution-timeout.yml
new file mode 100644
index 00000000000..c8488cbf200
--- /dev/null
+++ b/changelogs/unreleased/add-failure-reason-for-execution-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Add failure reason for execution timeout
+merge_request: 22224
+author:
+type: changed
diff --git a/changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml b/changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml
new file mode 100644
index 00000000000..ca3b99e73ab
--- /dev/null
+++ b/changelogs/unreleased/gt-truncate-milestone-title-on-collapsed-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Truncate milestone title on collapsed sidebar
+merge_request: 22624
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/pl-uprade-prometheus-alertmanager.yml b/changelogs/unreleased/pl-uprade-prometheus-alertmanager.yml
new file mode 100644
index 00000000000..d0c8ed8001d
--- /dev/null
+++ b/changelogs/unreleased/pl-uprade-prometheus-alertmanager.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Prometheus to 2.4.3 and Alertmanager to 0.15.2
+merge_request: 22600
+author:
+type: other
diff --git a/changelogs/unreleased/rz_fix_milestone_count.yml b/changelogs/unreleased/rz_fix_milestone_count.yml
new file mode 100644
index 00000000000..1013b88e0bc
--- /dev/null
+++ b/changelogs/unreleased/rz_fix_milestone_count.yml
@@ -0,0 +1,5 @@
+---
+title: Fixing count on Milestones
+merge_request: 21446
+author:
+type: fixed
diff --git a/config/routes.rb b/config/routes.rb
index 8723a928cc3..37c7f98ec98 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -80,6 +80,7 @@ Rails.application.routes.draw do
get 'ide' => 'ide#index'
get 'ide/*vueroute' => 'ide#index', format: false
+ draw :operations
draw :instance_statistics
end
diff --git a/db/migrate/20181014203236_create_cluster_groups.rb b/db/migrate/20181014203236_create_cluster_groups.rb
new file mode 100644
index 00000000000..69382d5c851
--- /dev/null
+++ b/db/migrate/20181014203236_create_cluster_groups.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateClusterGroups < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :cluster_groups do |t|
+ t.references :cluster, null: false, foreign_key: { on_delete: :cascade }
+ t.references :group, null: false, index: true
+
+ t.index [:cluster_id, :group_id], unique: true
+ t.foreign_key :namespaces, column: :group_id, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20181017001059_add_cluster_type_to_clusters.rb b/db/migrate/20181017001059_add_cluster_type_to_clusters.rb
new file mode 100644
index 00000000000..191e7eb4fb3
--- /dev/null
+++ b/db/migrate/20181017001059_add_cluster_type_to_clusters.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddClusterTypeToClusters < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ PROJECT_CLUSTER_TYPE = 3
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:clusters, :cluster_type, :smallint, default: PROJECT_CLUSTER_TYPE)
+ end
+
+ def down
+ remove_column(:clusters, :cluster_type)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7a75aafd7b0..a36399cb37e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20181016152238) do
+ActiveRecord::Schema.define(version: 20181017001059) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -599,6 +599,14 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
+ create_table "cluster_groups", force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.integer "group_id", null: false
+ end
+
+ add_index "cluster_groups", ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true, using: :btree
+ add_index "cluster_groups", ["group_id"], name: "index_cluster_groups_on_group_id", using: :btree
+
create_table "cluster_platforms_kubernetes", force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
@@ -654,6 +662,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do
t.boolean "enabled", default: true
t.string "name", null: false
t.string "environment_scope", default: "*", null: false
+ t.integer "cluster_type", limit: 2, default: 3, null: false
end
add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree
@@ -2372,6 +2381,8 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
+ add_foreign_key "cluster_groups", "clusters", on_delete: :cascade
+ add_foreign_key "cluster_groups", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "cluster_platforms_kubernetes", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "projects", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index 03371226041..20fcd2e1724 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -165,6 +165,7 @@ configuration. Then customize everything from buildpacks to CI/CD.
- [Deployment of Helm, Ingress, and Prometheus on Kubernetes](user/project/clusters/index.md#installing-applications)
- [Protected variables](ci/variables/README.md#protected-variables)
- [Easy creation of Kubernetes clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
+- [Executable Runbooks](user/project/clusters/runbooks/index.md)
### Monitor
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 96803746637..481eb692674 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -61,7 +61,7 @@ on an Linux NFS server, do the following:
2. Restart the NFS server process. For example, on CentOS run `service nfs restart`.
-## AWS Elastic File System
+## Avoid using AWS's Elastic File System (EFS)
GitLab strongly recommends against using AWS Elastic File System (EFS).
Our support team will not be able to assist on performance issues related to
@@ -78,6 +78,23 @@ stored on a local volume.
For more details on another person's experience with EFS, see
[Amazon's Elastic File System: Burst Credits](https://rawkode.com/2017/04/16/amazons-elastic-file-system-burst-credits/)
+## Avoid using PostgreSQL with NFS
+
+GitLab strongly recommends against running your PostgreSQL database
+across NFS. The GitLab support team will not be able to assist on performance issues related to
+this configuration.
+
+Additionally, this configuration is specifically warned against in the
+[Postgres Documentation](https://www.postgresql.org/docs/current/static/creating-cluster.html#CREATING-CLUSTER-NFS):
+
+>PostgreSQL does nothing special for NFS file systems, meaning it assumes NFS behaves exactly like
+>locally-connected drives. If the client or server NFS implementation does not provide standard file
+>system semantics, this can cause reliability problems. Specifically, delayed (asynchronous) writes
+>to the NFS server can cause data corruption problems.
+
+For supported database architecture, please see our documentation on
+[Configuring a Database for GitLab HA](https://docs.gitlab.com/ee/administration/high_availability/database.html).
+
## NFS Client mount options
Below is an example of an NFS mount point defined in `/etc/fstab` we use on
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index b5d1ff698c6..dcee57def74 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -15,7 +15,7 @@ Omnibus GitLab packages.
> **Notes:**
> - Redis requires authentication for High Availability. See
-> [Redis Security](http://redis.io/topics/security) documentation for more
+> [Redis Security](https://redis.io/topics/security) documentation for more
> information. We recommend using a combination of a Redis password and tight
> firewall rules to secure your Redis service.
> - You are highly encouraged to read the [Redis Sentinel][sentinel] documentation
@@ -82,7 +82,7 @@ When a **Master** fails to respond, it's the application's responsibility
for a new **Master**).
To get a better understanding on how to correctly set up Sentinel, please read
-the [Redis Sentinel documentation](http://redis.io/topics/sentinel) first, as
+the [Redis Sentinel documentation](https://redis.io/topics/sentinel) first, as
failing to configure it correctly can lead to data loss or can bring your
whole cluster down, invalidating the failover effort.
@@ -885,8 +885,8 @@ Read more on High Availability:
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[gh-531]: https://github.com/redis/redis-rb/issues/531
[gh-534]: https://github.com/redis/redis-rb/issues/534
-[redis]: http://redis.io/
-[sentinel]: http://redis.io/topics/sentinel
+[redis]: https://redis.io/
+[sentinel]: https://redis.io/topics/sentinel
[omnifile]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
[source]: ../../install/installation.md
[ce]: https://about.gitlab.com/downloads
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 5823c575251..2101d36d2b6 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -107,7 +107,7 @@ starting with `sentinel` prefix.
Assuming that the Redis Sentinel is installed on the same instance as Redis
master with IP `10.0.0.1` (some settings might overlap with the master):
-1. [Install Redis Sentinel](http://redis.io/topics/sentinel)
+1. [Install Redis Sentinel](https://redis.io/topics/sentinel)
1. Edit `/etc/redis/sentinel.conf`:
```conf
@@ -363,7 +363,7 @@ production:
port: 26379 # point to sentinel, not to redis port
```
-When in doubt, please read [Redis Sentinel documentation](http://redis.io/topics/sentinel).
+When in doubt, please read [Redis Sentinel documentation](https://redis.io/topics/sentinel).
[gh-531]: https://github.com/redis/redis-rb/issues/531
[downloads]: https://about.gitlab.com/downloads
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 0e41a38b7e2..038e043281c 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -84,7 +84,7 @@ Introduced in GitLab 10.0, this file lives in
It helps you see requests made directly to the API. For example:
```json
-{"time":"2017-10-10T12:30:11.579Z","severity":"INFO","duration":16.84,"db":1.57,"view":15.27,"status":200,"method":"POST","path":"/api/v4/internal/allowed","params":{"action":"git-upload-pack","changes":"_any","gl_repository":null,"project":"root/foobar.git","protocol":"ssh","env":"{}","key_id":"[FILTERED]","secret_token":"[FILTERED]"},"host":"127.0.0.1","ip":"127.0.0.1","ua":"Ruby"}
+{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30}
```
This entry above shows an access to an internal endpoint to check whether an
diff --git a/doc/api/tags.md b/doc/api/tags.md
index f2a3f9f28d2..826900ca518 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -134,7 +134,7 @@ Parameters:
"description": "Amazing release. Wow"
},
"name": "v1.0.0",
- "target: "2695effb5807a22ff3d138d593fd856244e155e7",
+ "target": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": null
}
```
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index b2c73caae2e..c0346d78141 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -398,10 +398,10 @@ other reasons][ci-reasons] to keep using GitLab CI/CD. The benefits to our teams
- [Using Docker images documentation][using-docker]
- [Example project: Hello GitLab CI/CD on GitLab][hello-gitlab]
-[phoenix-site]: http://phoenixframework.org/ "Phoenix Framework"
+[phoenix-site]: https://phoenixframework.org/ "Phoenix Framework"
[phoenix-learning-guide]: https://hexdocs.pm/phoenix/learning.html "Phoenix Learning Guide"
-[phoenix-install]: http://www.phoenixframework.org/docs/installation "Phoenix Installation"
-[phoenix-mysql]: http://www.phoenixframework.org/docs/using-mysql "Phoenix with MySQL"
+[phoenix-install]: https://hexdocs.pm/phoenix/installation.html "Phoenix Installation"
+[phoenix-mysql]: https://hexdocs.pm/phoenix/ecto.html#using-mysql "Phoenix with MySQL"
[elixir-site]: http://elixir-lang.org/ "Elixir"
[elixir-mix]: http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html "Introduction to mix"
[elixir-docs]: http://elixir-lang.org/getting-started/introduction.html "Elixir Documentation"
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 25c6371f3d7..4292a17bfa5 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -1,15 +1,13 @@
# Review apps
-We currently have review apps available as a manual job in EE pipelines. Here is
-[the first implementation](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6259).
-
-That said, [the Quality team is working](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6665)
-on making Review Apps automatically deployed by each pipeline, both in CE and EE.
+Review Apps are automatically deployed by each pipeline, both in
+[CE](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22010) and
+[EE](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6665).
## How does it work?
-1. On every EE [pipeline][gitlab-pipeline] during the `test` stage, you can
- start the [`review` job][review-job]
+1. On every [pipeline][gitlab-pipeline] during the `test` stage, the
+ [`review` job][review-job] is automatically started.
1. The `review` job [triggers a pipeline][cng-pipeline] in the
[`CNG-mirror`][cng-mirror] [^1] project
1. The `CNG-mirror` pipeline creates the Docker images of each component (e.g. `gitlab-rails-ee`,
@@ -39,6 +37,9 @@ on making Review Apps automatically deployed by each pipeline, both in CE and EE
review app manually, and is also started by GitLab once a branch is deleted
- [TBD] Review apps are cleaned up regularly using a pipeline schedule that runs
the [`scripts/review_apps/automated_cleanup.rb`][automated_cleanup.rb] script
+- If you're unable to log in using the `root` username and password the
+ deployment may have failed. Stop the review app via the `stop_review`
+ manual job and then retry the `review` job to redeploy the review app.
[^1]: We use the `CNG-mirror` project so that the `CNG`, (**C**loud **N**ative **G**itLab), project's registry is
not overloaded with a lot of transient Docker images.
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 9546f43eea8..73301394e9f 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -43,7 +43,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
## Third-party resources
- [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth)
-- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitLab+OAuth+Plugin)
+- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin)
- [Set up Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/)
- [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/)
- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab)
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 4d4832184e2..96e788666a1 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -225,10 +225,11 @@ Auto DevOps at the project level.
### Enabling/disabling Auto DevOps at the project-level
-1. Check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
+If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
+
1. Go to your project's **Settings > CI/CD > Auto DevOps**.
-1. Check the **Default to Auto DevOps pipeline** checkbox.
-1. Optionally, but recommended, add in the [base domain](#auto-devops-base-domain)
+1. Toggle the **Default to Auto DevOps pipeline** checkbox (checked to enable, unchecked to disable)
+1. When enabling, it's optional but recommended to add in the [base domain](#auto-devops-base-domain)
that will be used by Auto DevOps to [deploy your application](#auto-deploy)
and choose the [deployment strategy](#deployment-strategy).
1. Click **Save changes** for the changes to take effect.
@@ -246,12 +247,6 @@ There is also a feature flag to enable Auto DevOps to a percentage of projects
which can be enabled from the console with
`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`.
-### Disable Auto DevOps at the project level
-
-1. Go to your project's **Settings > CI/CD > Auto DevOps**.
-1. Uncheck the **Default to Auto DevOps pipeline** checkbox.
-1. Click **Save changes** for the changes to take effect.
-
### Deployment strategy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38542) in GitLab 11.0.
diff --git a/doc/university/README.md b/doc/university/README.md
index 5edf92b3b09..f19b1ffd3d9 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -205,7 +205,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
### 4. External Articles
-1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
+1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/)
1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/)
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 6ff27e495fb..6e0f71017c6 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -303,7 +303,7 @@ A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) use
### Jenkins
-An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins-ci.org/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html).
+An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins.io/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html).
### Jira
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
new file mode 100644
index 00000000000..3b81e439119
--- /dev/null
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -0,0 +1,49 @@
+# Runbooks
+
+Runbooks are a collection of documented procedures that explain how to
+carry out a particular process, be it starting, stopping, debugging,
+or troubleshooting a particular system.
+
+## Overview
+
+Historically, runbooks took the form of a decision tree or a detailed
+step-by-step guide depending on the condition or system.
+
+Modern implementations have introduced the concept of an "executable
+runbooks", where along with a well define process, operators can execute
+code blocks or database queries against a given environment.
+
+## Nurtch Executable Runbooks
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45912) in GitLab 11.4.
+
+The JupyterHub app offered via GitLab’s Kubernetes integration now ships
+with Nurtch’s Rubix library, providing a simple way to create DevOps
+runbooks. A sample runbook is provided, showcasing common operations.
+
+**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE)
+for an overview of how this is acomplished in GitLab!**
+
+## Requirements
+
+To create an executable runbook, you will need:
+
+1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications.
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab).
+1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install
+ all the other applications. It is installed in its own pod inside the cluster which
+ can run the helm CLI in a safe environment.
+1. **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
+ virtual hosting. It acts as a web proxy for your applications.
+1. **JupyterHub** - JupyterHub is a multi-user service for managing notebooks across
+ a team. Jupyter Notebooks provide a web-based interactive programming environment
+ used for data analysis, visualization, and machine learning.
+
+## Nurtch
+
+Nurtch is the company behind the [Rubix library](https://github.com/Nurtch/rubix). Rubix is
+an open-source python library that makes it easy to perform common DevOps tasks inside Jupyter Notebooks.
+Tasks such as plotting Cloudwatch metrics and rolling your ECS/Kubernetes app are simplified
+down to a couple of lines of code. Check the [Nurtch Documentation](http://docs.nurtch.com/en/latest)
+for more information.
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index f5ea350a58f..9e2434c02ec 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -176,8 +176,8 @@ Clicking on the current board name in the upper left corner will reveal a
menu from where you can create another Issue Board and rename or delete the
existing one.
-Clicking on the main issue board link will take you to the last board
-you visited.
+When you're revisiting an issue board in a project or group with multiple boards,
+GitLab will automatically load the last board you visited.
NOTE: **Note:**
The Multiple Issue Boards feature is available for
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index e1eede8bbed..89b9621b8b9 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -212,7 +212,7 @@ security measure, necessary just for big companies, like banks and shoppings sit
with financial transactions.
Now we have a different picture. [According to Josh Aas](https://letsencrypt.org/2015/10/29/phishing-and-malware.html), Executive Director at [ISRG](https://en.wikipedia.org/wiki/Internet_Security_Research_Group):
-> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](http://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._
+> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._
Therefore, the reason why certificates are so important is that they encrypt
the connection between the **client** (you, me, your visitors)
diff --git a/doc/user/search/img/issues_filter_none_any.png b/doc/user/search/img/issues_filter_none_any.png
new file mode 100644
index 00000000000..9682fc55315
--- /dev/null
+++ b/doc/user/search/img/issues_filter_none_any.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 4f1b96b775c..3f9d07dacaa 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -40,6 +40,16 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label.
+### Filtering by **None** / **Any**
+
+Some filter fields like milestone and assignee, allow you to filter by **None** or **Any**.
+
+![filter by none any](img/issues_filter_none_any.png)
+
+Selecting **None** returns results that have an empty value for that field. E.g.: no milestone, no assignee.
+
+Selecting **Any** does the opposite. It returns results that have a non-empty value for that field.
+
### Searching for specific terms
You can filter issues and merge requests by specific terms included in titles or descriptions.
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 4dd6b19e353..ae40b5f7557 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -65,6 +65,8 @@ module API
result
rescue Gitlab::GitAccess::UnauthorizedError => e
break response_with_status(code: 401, success: false, message: e.message)
+ rescue Gitlab::GitAccess::TimeoutError => e
+ break response_with_status(code: 503, success: false, message: e.message)
rescue Gitlab::GitAccess::NotFoundError => e
break response_with_status(code: 404, success: false, message: e.message)
end
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index b369b9e7600..dfbb83f7bb9 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -42,7 +42,7 @@ module Gitlab
end
def self.cache_key_for_project(project)
- "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status"
+ "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status/#{project.commit&.sha}"
end
def self.update_for_pipeline(pipeline)
@@ -84,9 +84,7 @@ module Gitlab
def load_from_project
return unless commit
- self.sha = commit.sha
- self.status = commit.status
- self.ref = project.default_branch
+ self.sha, self.status, self.ref = commit.sha, commit.status, project.default_branch
end
# We only cache the status for the HEAD commit of a project
@@ -104,6 +102,8 @@ module Gitlab
def load_from_cache
Gitlab::Redis::Cache.with do |redis|
self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
+
+ self.status = nil if self.status.empty?
end
end
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 49e7f7e1fd7..074afe9c412 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -18,11 +18,24 @@ module Gitlab
lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'
}.freeze
- attr_reader :user_access, :project, :skip_authorization, :skip_lfs_integrity_check, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name
+ LOG_MESSAGES = {
+ push_checks: "Checking if you are allowed to push...",
+ delete_default_branch_check: "Checking if default branch is being deleted...",
+ protected_branch_checks: "Checking if you are force pushing to a protected branch...",
+ protected_branch_push_checks: "Checking if you are allowed to push to the protected branch...",
+ protected_branch_deletion_checks: "Checking if you are allowed to delete the protected branch...",
+ tag_checks: "Checking if you are allowed to change existing tags...",
+ protected_tag_checks: "Checking if you are creating, updating or deleting a protected tag...",
+ lfs_objects_exist_check: "Scanning repository for blobs stored in LFS and verifying their files have been uploaded to GitLab...",
+ commits_check_file_paths_validation: "Validating commits' file paths...",
+ commits_check: "Validating commit contents..."
+ }.freeze
+
+ attr_reader :user_access, :project, :skip_authorization, :skip_lfs_integrity_check, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name, :logger
def initialize(
change, user_access:, project:, skip_authorization: false,
- skip_lfs_integrity_check: false, protocol:
+ skip_lfs_integrity_check: false, protocol:, logger:
)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@branch_name = Gitlab::Git.branch_name(@ref)
@@ -32,6 +45,9 @@ module Gitlab
@skip_authorization = skip_authorization
@skip_lfs_integrity_check = skip_lfs_integrity_check
@protocol = protocol
+
+ @logger = logger
+ @logger.append_message("Running checks for ref: #{@branch_name || @tag_name}")
end
def exec(skip_commits_check: false)
@@ -49,26 +65,32 @@ module Gitlab
protected
def push_checks
- unless can_push?
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ unless can_push?
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
+ end
end
end
def branch_checks
return unless branch_name
- if deletion? && branch_name == project.default_branch
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch]
+ logger.log_timed(LOG_MESSAGES[:delete_default_branch_check]) do
+ if deletion? && branch_name == project.default_branch
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch]
+ end
end
protected_branch_checks
end
def protected_branch_checks
- return unless ProtectedBranch.protected?(project, branch_name)
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ return unless ProtectedBranch.protected?(project, branch_name) # rubocop:disable Cop/AvoidReturnFromBlocks
- if forced_push?
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch]
+ if forced_push?
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch]
+ end
end
if deletion?
@@ -79,23 +101,27 @@ module Gitlab
end
def protected_branch_deletion_checks
- unless user_access.can_delete_branch?(branch_name)
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch]
- end
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ unless user_access.can_delete_branch?(branch_name)
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch]
+ end
- unless updated_from_web?
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch]
+ unless updated_from_web?
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch]
+ end
end
end
def protected_branch_push_checks
- if matching_merge_request?
- unless user_access.can_merge_to_branch?(branch_name) || user_access.can_push_to_branch?(branch_name)
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch]
- end
- else
- unless user_access.can_push_to_branch?(branch_name)
- raise GitAccess::UnauthorizedError, push_to_protected_branch_rejected_message
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ if matching_merge_request?
+ unless user_access.can_merge_to_branch?(branch_name) || user_access.can_push_to_branch?(branch_name)
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch]
+ end
+ else
+ unless user_access.can_push_to_branch?(branch_name)
+ raise GitAccess::UnauthorizedError, push_to_protected_branch_rejected_message
+ end
end
end
end
@@ -103,21 +129,25 @@ module Gitlab
def tag_checks
return unless tag_name
- if tag_exists? && user_access.cannot_do_action?(:admin_project)
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ if tag_exists? && user_access.cannot_do_action?(:admin_project)
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
+ end
end
protected_tag_checks
end
def protected_tag_checks
- return unless ProtectedTag.protected?(project, tag_name)
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ return unless ProtectedTag.protected?(project, tag_name) # rubocop:disable Cop/AvoidReturnFromBlocks
- raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update?
- raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion?
+ raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update?
+ raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion?
- unless user_access.can_create_tag?(tag_name)
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag]
+ unless user_access.can_create_tag?(tag_name)
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag]
+ end
end
end
@@ -125,14 +155,20 @@ module Gitlab
return if deletion? || newrev.nil?
return unless should_run_commit_validations?
- # n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593
- ::Gitlab::GitalyClient.allow_n_plus_1_calls do
- commits.each do |commit|
- commit_check.validate(commit, validations_for_commit(commit))
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593
+ ::Gitlab::GitalyClient.allow_n_plus_1_calls do
+ commits.each do |commit|
+ logger.check_timeout_reached
+
+ commit_check.validate(commit, validations_for_commit(commit))
+ end
end
end
- commit_check.validate_file_paths
+ logger.log_timed(LOG_MESSAGES[:commits_check_file_paths_validation]) do
+ commit_check.validate_file_paths
+ end
end
# Method overwritten in EE to inject custom validations
@@ -194,10 +230,12 @@ module Gitlab
end
def lfs_objects_exist_check
- lfs_check = Checks::LfsIntegrity.new(project, newrev)
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ lfs_check = Checks::LfsIntegrity.new(project, newrev, logger.time_left)
- if lfs_check.objects_missing?
- raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing]
+ if lfs_check.objects_missing?
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing]
+ end
end
end
diff --git a/lib/gitlab/checks/lfs_integrity.rb b/lib/gitlab/checks/lfs_integrity.rb
index fa3dc1808df..1652d5a30a4 100644
--- a/lib/gitlab/checks/lfs_integrity.rb
+++ b/lib/gitlab/checks/lfs_integrity.rb
@@ -3,9 +3,10 @@
module Gitlab
module Checks
class LfsIntegrity
- def initialize(project, newrev)
+ def initialize(project, newrev, time_left)
@project = project
@newrev = newrev
+ @time_left = time_left
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -13,7 +14,7 @@ module Gitlab
return false unless @newrev && @project.lfs_enabled?
new_lfs_pointers = Gitlab::Git::LfsChanges.new(@project.repository, @newrev)
- .new_pointers(object_limit: ::Gitlab::Git::Repository::REV_LIST_COMMIT_LIMIT)
+ .new_pointers(object_limit: ::Gitlab::Git::Repository::REV_LIST_COMMIT_LIMIT, dynamic_timeout: @time_left)
return false unless new_lfs_pointers.present?
diff --git a/lib/gitlab/checks/timed_logger.rb b/lib/gitlab/checks/timed_logger.rb
new file mode 100644
index 00000000000..f365e0a43f6
--- /dev/null
+++ b/lib/gitlab/checks/timed_logger.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Checks
+ class TimedLogger
+ TimeoutError = Class.new(StandardError)
+
+ attr_reader :start_time, :header, :log, :timeout
+
+ def initialize(start_time: Time.now, log: [], header: "", timeout:)
+ @start_time = start_time
+ @timeout = timeout
+ @header = header
+ @log = log
+ end
+
+ # Adds trace of method being tracked with
+ # the correspondent time it took to run it.
+ # We make use of the start default argument
+ # on unit tests related to this method
+ #
+ def log_timed(log_message, start = Time.now)
+ check_timeout_reached
+
+ timed = true
+
+ yield
+
+ append_message(log_message + time_suffix_message(start: start))
+ rescue GRPC::DeadlineExceeded, TimeoutError
+ args = { cancelled: true }
+ args[:start] = start if timed
+
+ append_message(log_message + time_suffix_message(args))
+
+ raise TimeoutError
+ end
+
+ def check_timeout_reached
+ return unless time_expired?
+
+ raise TimeoutError
+ end
+
+ def time_left
+ (start_time + timeout.seconds) - Time.now
+ end
+
+ def full_message
+ header + log.join("\n")
+ end
+
+ # We always want to append in-place on the log
+ def append_message(message)
+ log << message
+ end
+
+ private
+
+ def time_expired?
+ time_left <= 0
+ end
+
+ def time_suffix_message(cancelled: false, start: nil)
+ return " (#{elapsed_time(start)}ms)" unless cancelled
+
+ if start
+ " (cancelled after #{elapsed_time(start)}ms)"
+ else
+ " (cancelled)"
+ end
+ end
+
+ def elapsed_time(start)
+ to_ms(Time.now - start)
+ end
+
+ def to_ms(elapsed)
+ (elapsed.to_f * 1000).round(2)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 50b0d044265..4babc23a495 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -11,7 +11,8 @@ module Gitlab
runner_system_failure: 'runner system failure',
missing_dependency_failure: 'missing dependency failure',
runner_unsupported: 'unsupported runner',
- stale_schedule: 'stale schedule'
+ stale_schedule: 'stale schedule',
+ job_execution_timeout: 'job execution timeout'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 6fa59e41d20..db48b187e5e 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -210,7 +210,7 @@ container_scanning:
refs:
- branches
variables:
- - $GITLAB_FEATURES =~ /\bsast_container\b/
+ - $GITLAB_FEATURES =~ /\bcontainer_scanning\b/
except:
variables:
- $CONTAINER_SCANNING_DISABLED
diff --git a/lib/gitlab/git/lfs_changes.rb b/lib/gitlab/git/lfs_changes.rb
index f0fab1e76a3..d7148165408 100644
--- a/lib/gitlab/git/lfs_changes.rb
+++ b/lib/gitlab/git/lfs_changes.rb
@@ -6,8 +6,8 @@ module Gitlab
@newrev = newrev
end
- def new_pointers(object_limit: nil, not_in: nil)
- @repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in)
+ def new_pointers(object_limit: nil, not_in: nil, dynamic_timeout: nil)
+ @repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in, dynamic_timeout)
end
def all_pointers
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 827c04ae035..802fa65dd63 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -9,6 +9,7 @@ module Gitlab
UnauthorizedError = Class.new(StandardError)
NotFoundError = Class.new(StandardError)
ProjectCreationError = Class.new(StandardError)
+ TimeoutError = Class.new(StandardError)
ProjectMovedError = Class.new(NotFoundError)
ERROR_MESSAGES = {
@@ -26,11 +27,18 @@ module Gitlab
cannot_push_to_read_only: "You can't push code to a read-only GitLab instance."
}.freeze
+ INTERNAL_TIMEOUT = 50.seconds.freeze
+ LOG_HEADER = <<~MESSAGE
+ Push operation timed out
+
+ Timing information for debugging purposes:
+ MESSAGE
+
DOWNLOAD_COMMANDS = %w{git-upload-pack git-upload-archive}.freeze
PUSH_COMMANDS = %w{git-receive-pack}.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
- attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type, :changes
+ attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type, :changes, :logger
def initialize(actor, project, protocol, authentication_abilities:, namespace_path: nil, project_path: nil, redirected_path: nil, auth_result_type: nil)
@actor = actor
@@ -44,6 +52,7 @@ module Gitlab
end
def check(cmd, changes)
+ @logger = Checks::TimedLogger.new(timeout: INTERNAL_TIMEOUT, header: LOG_HEADER)
@changes = changes
check_protocol!
@@ -269,14 +278,19 @@ module Gitlab
end
def check_single_change_access(change, skip_lfs_integrity_check: false)
- Checks::ChangeAccess.new(
+ change_access = Checks::ChangeAccess.new(
change,
user_access: user_access,
project: project,
skip_authorization: deploy_key?,
skip_lfs_integrity_check: skip_lfs_integrity_check,
- protocol: protocol
- ).exec
+ protocol: protocol,
+ logger: logger
+ )
+
+ change_access.exec
+ rescue Checks::TimedLogger::TimeoutError
+ raise TimeoutError, logger.full_message
end
def deploy_key
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index e731e654f3c..cf2329e489d 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -11,8 +11,8 @@ module Gitlab
@changes = deserialize_changes(changes)
end
- def identify(revision)
- super(identifier, project, revision)
+ def identify
+ super(identifier)
end
def changes_refs
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index 1840bf45154..086ce31e678 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -72,7 +72,7 @@ module Gitlab
GitalyClient::BlobsStitcher.new(response)
end
- def get_new_lfs_pointers(revision, limit, not_in)
+ def get_new_lfs_pointers(revision, limit, not_in, dynamic_timeout = nil)
request = Gitaly::GetNewLFSPointersRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision),
@@ -85,7 +85,20 @@ module Gitlab
request.not_in_refs += not_in
end
- response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
+ timeout =
+ if dynamic_timeout
+ [dynamic_timeout, GitalyClient.medium_timeout].min
+ else
+ GitalyClient.medium_timeout
+ end
+
+ response = GitalyClient.call(
+ @gitaly_repo.storage_name,
+ :blob_service,
+ :get_new_lfs_pointers,
+ request,
+ timeout: timeout
+ )
map_lfs_pointers(response)
end
diff --git a/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb b/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb
index 0014ce2689b..41004408dec 100644
--- a/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb
+++ b/lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb
@@ -6,7 +6,7 @@ module Gitlab
def call(severity, datetime, _, data)
time = data.delete :time
- data[:params] = utf8_encode_values(data[:params]) if data.has_key?(:params)
+ data[:params] = process_params(data)
attributes = {
time: datetime.utc.iso8601(3),
@@ -20,6 +20,14 @@ module Gitlab
private
+ def process_params(data)
+ return [] unless data.has_key?(:params)
+
+ data[:params]
+ .each_pair
+ .map { |k, v| { key: k, value: utf8_encode_values(v) } }
+ end
+
def utf8_encode_values(data)
case data
when Hash
diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb
index 28a9fe29465..d5f94ad04f1 100644
--- a/lib/gitlab/identifier.rb
+++ b/lib/gitlab/identifier.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
# Detect user based on identifier like
-# key-13 or user-36 or last commit
+# key-13 or user-36
module Gitlab
module Identifier
- def identify(identifier, project = nil, newrev = nil)
- if identifier.blank?
- identify_using_commit(project, newrev)
- elsif identifier =~ /\Auser-\d+\Z/
+ def identify(identifier)
+ if identifier =~ /\Auser-\d+\Z/
# git push over http
identify_using_user(identifier)
elsif identifier =~ /\Akey-\d+\Z/
@@ -16,19 +14,6 @@ module Gitlab
end
end
- # Tries to identify a user based on a commit SHA.
- def identify_using_commit(project, ref)
- return if project.nil? && ref.nil?
-
- commit = project.commit(ref)
-
- return if !commit || !commit.author_email
-
- identify_with_cache(:email, commit.author_email) do
- commit.author
- end
- end
-
# Tries to identify a user based on a user identifier (e.g. "user-123").
# rubocop: disable CodeReuse/ActiveRecord
def identify_using_user(identifier)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 26270595c6a..23afc6b245b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -648,6 +648,9 @@ msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
msgid "Are you sure?"
msgstr ""
@@ -2324,6 +2327,12 @@ msgstr ""
msgid "DeployTokens|Your new project deploy token has been created."
msgstr ""
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
msgid "Deprioritize label"
msgstr ""
@@ -2750,6 +2759,9 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to deploy to"
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
@@ -4030,9 +4042,6 @@ msgstr ""
msgid "No"
msgstr ""
-msgid "No Assignee"
-msgstr ""
-
msgid "No Label"
msgstr ""
@@ -5607,6 +5616,9 @@ msgstr ""
msgid "Something went wrong while fetching comments. Please try again."
msgstr ""
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
msgid "Something went wrong while fetching the projects."
msgstr ""
@@ -5784,6 +5796,12 @@ msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
msgid "Start the Runner!"
msgstr ""
@@ -6996,9 +7014,6 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
-msgid "You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}"
-msgstr ""
-
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -7020,6 +7035,9 @@ msgstr ""
msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
msgstr ""
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 56047c0c8d2..278b980b6d8 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -45,6 +45,8 @@ describe Dashboard::MilestonesController do
end
describe "#index" do
+ render_views
+
it 'returns group and project milestones to which the user belongs' do
get :index, format: :json
@@ -53,5 +55,12 @@ describe Dashboard::MilestonesController do
expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id])
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end
+
+ it 'should contain group and project milestones to which the user belongs to' do
+ get :index
+
+ expect(response.body).to include("Open\n<span class=\"badge badge-pill\">3</span>")
+ expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>")
+ end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index dcfd6c05200..7b0459e0325 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -749,13 +749,15 @@ describe Projects::MergeRequestsController do
describe 'GET ci_environments_status' do
context 'the environment is from a forked project' do
- let!(:forked) { fork_project(project, user, repository: true) }
- let!(:environment) { create(:environment, project: forked) }
- let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
- let(:admin) { create(:admin) }
+ let(:forked) { fork_project(project, user, repository: true) }
+ let(:sha) { forked.commit.sha }
+ let(:environment) { create(:environment, project: forked) }
+ let(:pipeline) { create(:ci_pipeline, sha: sha, project: forked) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: 'master', deployable: build) }
let(:merge_request) do
- create(:merge_request, source_project: forked, target_project: project)
+ create(:merge_request, source_project: forked, target_project: project, target_branch: 'master', head_pipeline: pipeline)
end
it 'links to the environment on that project' do
@@ -764,6 +766,35 @@ describe Projects::MergeRequestsController do
expect(json_response.first['url']).to match /#{forked.full_path}/
end
+ context "when environment_target is 'merge_commit'" do
+ it 'returns nothing' do
+ get_ci_environments_status(environment_target: 'merge_commit')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+
+ context 'when is merged' do
+ let(:source_environment) { create(:environment, project: project) }
+ let(:merge_commit_sha) { project.repository.merge(user, forked.commit.id, merge_request, "merged in test") }
+ let(:post_merge_pipeline) { create(:ci_pipeline, sha: merge_commit_sha, project: project) }
+ let(:post_merge_build) { create(:ci_build, pipeline: post_merge_pipeline) }
+ let!(:source_deployment) { create(:deployment, environment: source_environment, sha: merge_commit_sha, ref: 'master', deployable: post_merge_build) }
+
+ before do
+ merge_request.update!(merge_commit_sha: merge_commit_sha)
+ merge_request.mark_as_merged!
+ end
+
+ it 'returns the enviroment on the source project' do
+ get_ci_environments_status(environment_target: 'merge_commit')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['url']).to match /#{project.full_path}/
+ end
+ end
+ end
+
# we're trying to reduce the overall number of queries for this method.
# set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287
it 'keeps queries in check' do
@@ -772,11 +803,15 @@ describe Projects::MergeRequestsController do
expect(control_count).to be <= 137
end
- def get_ci_environments_status
- get :ci_environments_status,
+ def get_ci_environments_status(extra_params = {})
+ params = {
namespace_id: merge_request.project.namespace.to_param,
project_id: merge_request.project,
- id: merge_request.iid, format: 'json'
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :ci_environments_status, params.merge(extra_params)
end
end
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index bbeba8ce8b9..c9f5d0a813e 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -2,13 +2,28 @@ FactoryBot.define do
factory :cluster, class: Clusters::Cluster do
user
name 'test-cluster'
+ cluster_type :project_type
+
+ trait :instance do
+ cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
+ end
trait :project do
+ cluster_type { Clusters::Cluster.cluster_types[:project_type] }
+
before(:create) do |cluster, evaluator|
cluster.projects << create(:project, :repository)
end
end
+ trait :group do
+ cluster_type { Clusters::Cluster.cluster_types[:group_type] }
+
+ before(:create) do |cluster, evalutor|
+ cluster.groups << create(:group)
+ end
+ end
+
trait :provided_by_user do
provider_type :user
platform_type :kubernetes
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 615223a2a88..2cdd3f55b50 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -106,7 +106,7 @@ describe 'Issue Boards add issue modal filtering', :js do
it 'filters by unassigned' do
set_filter('assignee')
- click_filter_link('No Assignee')
+ click_filter_link('None')
submit_filter
page.within('.add-issues-modal') do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 8989b2051bb..5c6c1c4fd15 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -114,33 +114,6 @@ describe 'Commits' do
expect(page).to have_content 'canceled'
end
end
-
- describe '.gitlab-ci.yml not found warning' do
- context 'ci builds enabled' do
- it "does not show warning" do
- visit pipeline_path(pipeline)
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
-
- it 'shows warning' do
- stub_ci_pipeline_yaml_file(nil)
- visit pipeline_path(pipeline)
- expect(page).to have_content '.gitlab-ci.yml not found in this commit'
- end
- end
-
- context 'ci builds disabled' do
- before do
- stub_ci_builds_disabled
- stub_ci_pipeline_yaml_file(nil)
- visit pipeline_path(pipeline)
- end
-
- it 'does not show warning' do
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
- end
- end
end
context "when logged as reporter" do
@@ -182,6 +155,39 @@ describe 'Commits' do
end
end
end
+
+ describe '.gitlab-ci.yml not found warning' do
+ before do
+ project.add_reporter(user)
+ end
+
+ context 'ci builds enabled' do
+ it 'does not show warning' do
+ visit pipeline_path(pipeline)
+
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+
+ it 'shows warning' do
+ stub_ci_pipeline_yaml_file(nil)
+
+ visit pipeline_path(pipeline)
+
+ expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+
+ context 'ci builds disabled' do
+ it 'does not show warning' do
+ stub_ci_builds_disabled
+ stub_ci_pipeline_yaml_file(nil)
+
+ visit pipeline_path(pipeline)
+
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+ end
end
context 'viewing commits for a branch' do
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index e8ca6a6714f..174840794ed 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -95,9 +95,9 @@ describe 'Group milestones' do
end
it 'counts milestones correctly' do
- expect(find('.top-area .active .badge').text).to eq("2")
- expect(find('.top-area .closed .badge').text).to eq("2")
- expect(find('.top-area .all .badge').text).to eq("4")
+ expect(find('.top-area .active .badge').text).to eq("3")
+ expect(find('.top-area .closed .badge').text).to eq("3")
+ expect(find('.top-area .all .badge').text).to eq("6")
end
it 'lists legacy group milestones and group milestones' do
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index d011d2545bb..e910fb54d23 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -156,13 +156,21 @@ describe 'Dropdown assignee', :js do
expect_filtered_search_input_empty
end
- it 'selects `no assignee`' do
- find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
+ it 'selects `None`' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: 'None').click
expect(page).to have_css(js_dropdown_assignee, visible: false)
expect_tokens([assignee_token('none')])
expect_filtered_search_input_empty
end
+
+ it 'selects `Any`' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: 'Any').click
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect_tokens([assignee_token('any')])
+ expect_filtered_search_input_empty
+ end
end
describe 'selecting from dropdown without Ajax call' do
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 6ac7ccd00f7..1e1dd5691ab 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -118,7 +118,7 @@ describe 'Visual tokens', :js do
describe 'selecting static option from dropdown' do
before do
- find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'No Assignee').click
+ find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click
end
it 'changes value in visual token' do
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index f744d7941f5..a298ead43db 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -3,15 +3,19 @@ require 'rails_helper'
describe 'Merge request > User sees deployment widget', :js do
describe 'when deployed to an environment' do
let(:user) { create(:user) }
- let(:project) { merge_request.target_project }
- let(:merge_request) { create(:merge_request, :merged) }
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, :merged, source_project: project) }
let(:environment) { create(:environment, project: project) }
let(:role) { :developer }
- let(:sha) { project.commit('master').id }
- let!(:deployment) { create(:deployment, environment: environment, sha: sha) }
+ let(:ref) { merge_request.target_branch }
+ let(:sha) { project.commit(ref).id }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, sha: sha, project: project, ref: ref) }
+ let(:build) { create(:ci_build, :success, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: ref, deployable: build) }
let!(:manual) { }
before do
+ merge_request.update!(merge_commit_sha: sha)
project.add_user(user, role)
sign_in(user)
visit project_merge_request_path(project, merge_request)
@@ -26,15 +30,10 @@ describe 'Merge request > User sees deployment widget', :js do
end
context 'with stop action' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
let(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
- let(:deployment) do
- create(:deployment, environment: environment, ref: merge_request.target_branch,
- sha: sha, deployable: build, on_stop: 'close_app')
- end
before do
+ deployment.update!(on_stop: manual.name)
wait_for_requests
end
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index f15129759de..4e5699f9571 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -40,21 +40,26 @@ describe 'Merge request > User sees merge widget', :js do
context 'view merge request' do
let!(:environment) { create(:environment, project: project) }
+ let(:sha) { project.commit(merge_request.source_branch).sha }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, status: 'success', sha: sha, project: project, ref: merge_request.source_branch) }
+ let(:build) { create(:ci_build, :success, pipeline: pipeline) }
let!(:deployment) do
create(:deployment, environment: environment,
- ref: 'feature',
- sha: merge_request.diff_head_sha)
+ ref: merge_request.source_branch,
+ deployable: build,
+ sha: sha)
end
before do
+ merge_request.update!(head_pipeline: pipeline)
visit project_merge_request_path(project, merge_request)
end
it 'shows environments link' do
wait_for_requests
- page.within('.mr-widget-heading') do
+ page.within('.js-pre-merge-deploy') do
expect(page).to have_content("Deployed to #{environment.name}")
expect(find('.js-deploy-url')[:href]).to include(environment.formatted_external_url)
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index c40977bc4ee..35971d564d5 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -46,6 +46,7 @@
"diff_head_commit_short_id": { "type": ["string", "null"] },
"merge_commit_message": { "type": ["string", "null"] },
"pipeline": { "type": ["object", "null"] },
+ "merge_pipeline": { "type": ["object", "null"] },
"work_in_progress": { "type": "boolean" },
"source_branch_exists": { "type": "boolean" },
"mergeable_discussions_state": { "type": "boolean" },
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 7237274eb43..d9d7f61785f 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -1 +1,125 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import Vue from 'vue';
+import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+describe('CompareVersions', () => {
+ let vm;
+ const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+
+ beforeEach(() => {
+ vm = createComponentWithStore(Vue.extend(CompareVersionsComponent), store, {
+ mergeRequestDiffs: diffsMockData,
+ mergeRequestDiff: diffsMockData[0],
+ targetBranch,
+ }).$mount();
+ });
+
+ describe('template', () => {
+ it('should render Tree List toggle button with correct attribute values', () => {
+ const treeListBtn = vm.$el.querySelector('.js-toggle-tree-list');
+
+ expect(treeListBtn).not.toBeNull();
+ expect(treeListBtn.dataset.originalTitle).toBe('Toggle file browser');
+ expect(treeListBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(treeListBtn.querySelector('svg use').getAttribute('xlink:href')).toContain(
+ '#hamburger',
+ );
+ });
+
+ it('should render comparison dropdowns with correct values', () => {
+ const sourceDropdown = vm.$el.querySelector('.mr-version-dropdown');
+ const targetDropdown = vm.$el.querySelector('.mr-version-compare-dropdown');
+
+ expect(sourceDropdown).not.toBeNull();
+ expect(targetDropdown).not.toBeNull();
+ expect(sourceDropdown.querySelector('a span').innerHTML).toContain('latest version');
+ expect(targetDropdown.querySelector('a span').innerHTML).toContain(targetBranch.branchName);
+ });
+
+ it('should not render comparison dropdowns if no mergeRequestDiffs are specified', () => {
+ vm.mergeRequestDiffs = [];
+
+ vm.$nextTick(() => {
+ const sourceDropdown = vm.$el.querySelector('.mr-version-dropdown');
+ const targetDropdown = vm.$el.querySelector('.mr-version-compare-dropdown');
+
+ expect(sourceDropdown).toBeNull();
+ expect(targetDropdown).toBeNull();
+ });
+ });
+
+ it('should render whitespace toggle button with correct attributes', () => {
+ const whitespaceBtn = vm.$el.querySelector('.qa-toggle-whitespace');
+ const href = vm.toggleWhitespacePath;
+
+ expect(whitespaceBtn).not.toBeNull();
+ expect(whitespaceBtn.getAttribute('href')).toEqual(href);
+ expect(whitespaceBtn.innerHTML).toContain('Hide whitespace changes');
+ });
+
+ it('should render view types buttons with correct values', () => {
+ const inlineBtn = vm.$el.querySelector('#inline-diff-btn');
+ const parallelBtn = vm.$el.querySelector('#parallel-diff-btn');
+
+ expect(inlineBtn).not.toBeNull();
+ expect(parallelBtn).not.toBeNull();
+ expect(inlineBtn.dataset.viewType).toEqual('inline');
+ expect(parallelBtn.dataset.viewType).toEqual('parallel');
+ expect(inlineBtn.innerHTML).toContain('Inline');
+ expect(parallelBtn.innerHTML).toContain('Side-by-side');
+ });
+ });
+
+ describe('setInlineDiffViewType', () => {
+ it('should persist the view type in the url', () => {
+ const viewTypeBtn = vm.$el.querySelector('#inline-diff-btn');
+ viewTypeBtn.click();
+
+ expect(window.location.toString()).toContain('?view=inline');
+ });
+ });
+
+ describe('setParallelDiffViewType', () => {
+ it('should persist the view type in the url', () => {
+ const viewTypeBtn = vm.$el.querySelector('#parallel-diff-btn');
+ viewTypeBtn.click();
+
+ expect(window.location.toString()).toContain('?view=parallel');
+ });
+ });
+
+ describe('comparableDiffs', () => {
+ it('should not contain the first item in the mergeRequestDiffs property', () => {
+ const { comparableDiffs } = vm;
+ const comparableDiffsMock = diffsMockData.slice(1);
+
+ expect(comparableDiffs).toEqual(comparableDiffsMock);
+ });
+ });
+
+ describe('isWhitespaceVisible', () => {
+ const originalHref = window.location.href;
+
+ afterEach(() => {
+ window.history.replaceState({}, null, originalHref);
+ });
+
+ it('should return "true" when no "w" flag is present in the URL (default)', () => {
+ expect(vm.isWhitespaceVisible()).toBe(true);
+ });
+
+ it('should return "false" when the flag is set to "1" in the URL', () => {
+ window.history.replaceState({}, null, '?w=1');
+
+ expect(vm.isWhitespaceVisible()).toBe(false);
+ });
+
+ it('should return "true" when the flag is set to "0" in the URL', () => {
+ window.history.replaceState({}, null, '?w=0');
+
+ expect(vm.isWhitespaceVisible()).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/mock_data/merge_request_diffs.js b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
new file mode 100644
index 00000000000..d72ad7818dd
--- /dev/null
+++ b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
@@ -0,0 +1,42 @@
+export default [
+ {
+ versionIndex: 4,
+ createdAt: '2018-10-23T11:49:16.611Z',
+ commitsCount: 4,
+ latest: true,
+ shortCommitSha: 'de7a8f7f',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=de7a8f7f20c3ea2e0bef3ba01cfd41c21f6b4995',
+ },
+ {
+ versionIndex: 3,
+ createdAt: '2018-10-23T11:46:40.617Z',
+ commitsCount: 3,
+ latest: false,
+ shortCommitSha: 'e78fc18f',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=e78fc18fa37acb2185c59ca94d4a964464feb50e',
+ },
+ {
+ versionIndex: 2,
+ createdAt: '2018-10-04T09:57:39.648Z',
+ commitsCount: 2,
+ latest: false,
+ shortCommitSha: '48da7e7e',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=48da7e7e9a99d41c852578bd9cb541ca4d864b3e',
+ },
+ {
+ versionIndex: 1,
+ createdAt: '2018-09-25T20:30:39.493Z',
+ commitsCount: 1,
+ latest: false,
+ shortCommitSha: '47bac2ed',
+ versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ comparePath:
+ '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=47bac2ed972c5bee344c1cea159a22cd7f711dc0',
+ },
+];
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
deleted file mode 100644
index bc973407b25..00000000000
--- a/spec/javascripts/job_spec.js
+++ /dev/null
@@ -1,265 +0,0 @@
-// import $ from 'jquery';
-// import MockAdapter from 'axios-mock-adapter';
-// import axios from '~/lib/utils/axios_utils';
-// import { numberToHumanSize } from '~/lib/utils/number_utils';
-// import '~/lib/utils/datetime_utility';
-// import Job from '~/job';
-// import '~/breakpoints';
-// import waitForPromises from 'spec/helpers/wait_for_promises';
-
-// describe('Job', () => {
-// const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
-// let mock;
-// let response;
-// let job;
-
-// preloadFixtures('builds/build-with-artifacts.html.raw');
-
-// beforeEach(() => {
-// loadFixtures('builds/build-with-artifacts.html.raw');
-
-// spyOnDependency(Job, 'visitUrl');
-
-// response = {};
-
-// mock = new MockAdapter(axios);
-
-// mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => [200, response]);
-// });
-
-// afterEach(() => {
-// mock.restore();
-
-// clearTimeout(job.timeout);
-// });
-
-// describe('class constructor', () => {
-// beforeEach(() => {
-// jasmine.clock().install();
-// });
-
-// afterEach(() => {
-// jasmine.clock().uninstall();
-// });
-
-// describe('running build', () => {
-// it('updates the build trace on an interval', function (done) {
-// response = {
-// html: '<span>Update<span>',
-// status: 'running',
-// state: 'newstate',
-// append: true,
-// complete: false,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-// expect(job.state).toBe('newstate');
-
-// response = {
-// html: '<span>More</span>',
-// status: 'running',
-// state: 'finalstate',
-// append: true,
-// complete: true,
-// };
-// })
-// .then(() => jasmine.clock().tick(4001))
-// .then(waitForPromises)
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
-// expect(job.state).toBe('finalstate');
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('replaces the entire build trace', (done) => {
-// response = {
-// html: '<span>Update<span>',
-// status: 'running',
-// append: false,
-// complete: false,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-
-// response = {
-// html: '<span>Different</span>',
-// status: 'running',
-// append: false,
-// };
-// })
-// .then(() => jasmine.clock().tick(4001))
-// .then(waitForPromises)
-// .then(() => {
-// expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
-// expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-// });
-
-// describe('truncated information', () => {
-// describe('when size is less than total', () => {
-// it('shows information about truncated log', (done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('shows the size in KiB', (done) => {
-// const size = 50;
-
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(
-// document.querySelector('.js-truncated-info-size').textContent.trim(),
-// ).toEqual(`${numberToHumanSize(size)}`);
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('shows incremented size', (done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// complete: false,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(
-// document.querySelector('.js-truncated-info-size').textContent.trim(),
-// ).toEqual(`${numberToHumanSize(50)}`);
-
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: true,
-// size: 10,
-// total: 100,
-// complete: true,
-// };
-// })
-// .then(() => jasmine.clock().tick(4001))
-// .then(waitForPromises)
-// .then(() => {
-// expect(
-// document.querySelector('.js-truncated-info-size').textContent.trim(),
-// ).toEqual(`${numberToHumanSize(60)}`);
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('renders the raw link', () => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// };
-
-// job = new Job();
-
-// expect(
-// document.querySelector('.js-raw-link').textContent.trim(),
-// ).toContain('Complete Raw');
-// });
-// });
-
-// describe('when size is equal than total', () => {
-// it('does not show the trunctated information', (done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 100,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(() => {
-// expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
-// })
-// .then(done)
-// .catch(done.fail);
-// });
-// });
-// });
-
-// describe('output trace', () => {
-// beforeEach((done) => {
-// response = {
-// html: '<span>Update</span>',
-// status: 'success',
-// append: false,
-// size: 50,
-// total: 100,
-// };
-
-// job = new Job();
-
-// waitForPromises()
-// .then(done)
-// .catch(done.fail);
-// });
-
-// it('should render trace controls', () => {
-// const controllers = document.querySelector('.controllers');
-
-// expect(controllers.querySelector('.js-raw-link-controller')).not.toBeNull();
-// expect(controllers.querySelector('.js-scroll-up')).not.toBeNull();
-// expect(controllers.querySelector('.js-scroll-down')).not.toBeNull();
-// });
-
-// it('should render received output', () => {
-// expect(
-// document.querySelector('.js-build-output').innerHTML,
-// ).toEqual('<span>Update</span>');
-// });
-// });
-// });
-
-// });
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index ce850bc621e..d26ffa4b4a6 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -2,54 +2,48 @@ import Vue from 'vue';
import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import { getTimeago } from '~/lib/utils/datetime_utility';
+import mountComponent from '../../helpers/vue_mount_component_helper';
-const deploymentMockData = {
- id: 15,
- name: 'review/diplo',
- url: '/root/acets-review-apps/environments/15',
- stop_url: '/root/acets-review-apps/environments/15/stop',
- metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
- external_url: 'http://diplo.',
- external_url_formatted: 'diplo.',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- changes: [
- {
- path: 'index.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
- },
- {
- path: 'imgs/gallery.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
- },
- {
- path: 'about/',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
- },
- ],
-};
-const createComponent = () => {
+describe('Deployment component', () => {
const Component = Vue.extend(deploymentComponent);
+ const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/review-apps/environments/15',
+ stop_url: '/root/review-apps/environments/15/stop',
+ metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
+ external_url: 'http://gitlab.com.',
+ external_url_formatted: 'gitlab',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ };
- return new Component({
- el: document.createElement('div'),
- propsData: { deployment: { ...deploymentMockData } },
- });
-};
-
-describe('Deployment component', () => {
let vm;
- beforeEach(() => {
- vm = createComponent();
- });
-
afterEach(() => {
vm.$destroy();
});
- describe('computed', () => {
+ describe('', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
+ });
+
describe('deployTimeago', () => {
it('return formatted date', () => {
const readable = getTimeago().format(deploymentMockData.deployed_at);
@@ -111,9 +105,7 @@ describe('Deployment component', () => {
expect(vm.hasDeploymentMeta).toEqual(false);
});
});
- });
- describe('methods', () => {
describe('stopEnvironment', () => {
const url = '/foo/bar';
const returnPromise = () =>
@@ -152,42 +144,33 @@ describe('Deployment component', () => {
expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
});
});
- });
-
- describe('template', () => {
- let el;
-
- beforeEach(() => {
- vm = createComponent(deploymentMockData);
- el = vm.$el;
- });
it('renders deployment name', () => {
- expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
+ expect(vm.$el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
deploymentMockData.url,
);
- expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
+ expect(vm.$el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
});
it('renders external URL', () => {
- expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
+ expect(vm.$el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
deploymentMockData.external_url,
);
- expect(el.querySelector('.js-deploy-url').innerText).toContain('View app');
+ expect(vm.$el.querySelector('.js-deploy-url').innerText).toContain('View app');
});
it('renders stop button', () => {
- expect(el.querySelector('.btn')).not.toBeNull();
+ expect(vm.$el.querySelector('.btn')).not.toBeNull();
});
it('renders deployment time', () => {
- expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
+ expect(vm.$el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
});
it('renders metrics component', () => {
- expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-mr-memory-usage')).not.toBeNull();
});
});
@@ -196,8 +179,7 @@ describe('Deployment component', () => {
window.gon = window.gon || {};
window.gon.features = window.gon.features || {};
window.gon.features.ciEnvironmentsStatusChanges = true;
-
- vm = createComponent(deploymentMockData);
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
});
afterEach(() => {
@@ -216,7 +198,7 @@ describe('Deployment component', () => {
window.gon.features = window.gon.features || {};
window.gon.features.ciEnvironmentsStatusChanges = false;
- vm = createComponent(deploymentMockData);
+ vm = mountComponent(Component, { deployment: { ...deploymentMockData } });
});
afterEach(() => {
@@ -228,4 +210,44 @@ describe('Deployment component', () => {
expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
});
});
+
+ describe('deployment status', () => {
+ describe('running', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'running' }),
+ });
+ });
+
+ it('renders information about running deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to');
+ });
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'success' }),
+ });
+ });
+
+ it('renders information about finished deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deployed to');
+ });
+ });
+
+ describe('failed', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'failed' }),
+ });
+ });
+
+ it('renders information about finished deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
+ 'Failed to deploy to',
+ );
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index d1a064b9f4d..27b6c91e154 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -189,7 +189,7 @@ describe('mrWidgetOptions', () => {
it('should fetch deployments', done => {
spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
- vm.fetchDeployments();
+ vm.fetchPreMergeDeployments();
setTimeout(() => {
expect(vm.service.fetchDeployments).toHaveBeenCalled();
@@ -454,6 +454,7 @@ describe('mrWidgetOptions', () => {
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes,
+ status: 'success'
};
beforeEach(done => {
@@ -486,4 +487,189 @@ describe('mrWidgetOptions', () => {
).toEqual(changes.length);
});
});
+
+ describe('pipeline for target branch after merge', () => {
+ describe('with information for target branch pipeline', () => {
+ beforeEach(done => {
+ vm.mr.state = 'merged';
+ vm.mr.mergePipeline = {
+ id: 127,
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ active: true,
+ coverage: null,
+ source: 'push',
+ created_at: '2018-10-22T11:41:35.186Z',
+ updated_at: '2018-10-22T11:41:35.433Z',
+ path: '/root/ci-web-terminal/pipelines/127',
+ flags: {
+ latest: true,
+ stuck: true,
+ auto_devops: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: true,
+ failure_reason: false,
+ },
+ details: {
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/root/ci-web-terminal/pipelines/127',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ },
+ duration: null,
+ finished_at: null,
+ stages: [
+ {
+ name: 'test',
+ title: 'test: pending',
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/root/ci-web-terminal/pipelines/127#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ },
+ path: '/root/ci-web-terminal/pipelines/127#test',
+ dropdown_path: '/root/ci-web-terminal/pipelines/127/stage.json?stage=test',
+ },
+ ],
+ artifacts: [],
+ manual_actions: [],
+ scheduled_actions: [],
+ },
+ ref: {
+ name: 'master',
+ path: '/root/ci-web-terminal/commits/master',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'aa1939133d373c94879becb79d91828a892ee319',
+ short_id: 'aa193913',
+ title: "Merge branch 'master-test' into 'master'",
+ created_at: '2018-10-22T11:41:33.000Z',
+ parent_ids: [
+ '4622f4dd792468993003caf2e3be978798cbe096',
+ '76598df914cdfe87132d0c3c40f80db9fa9396a4',
+ ],
+ message:
+ "Merge branch 'master-test' into 'master'\n\nUpdate .gitlab-ci.yml\n\nSee merge request root/ci-web-terminal!1",
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2018-10-22T11:41:33.000Z',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2018-10-22T11:41:33.000Z',
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ author_gravatar_url: null,
+ commit_url:
+ 'http://localhost:3000/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
+ commit_path: '/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
+ },
+ cancel_path: '/root/ci-web-terminal/pipelines/127/cancel',
+ };
+ vm.$nextTick(done);
+ });
+
+ it('renders pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).not.toBeNull();
+ });
+
+ describe('with post merge deployments', () => {
+ beforeEach(done => {
+ vm.mr.postMergeDeployments = [{
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ status: 'success'
+ }];
+
+ vm.$nextTick(done);
+ });
+
+ it('renders post deployment information', () => {
+ expect(vm.$el.querySelector('.js-post-deployment')).not.toBeNull();
+ });
+ });
+ });
+
+ describe('without information for target branch pipeline', () => {
+ beforeEach(done => {
+ vm.mr.state = 'merged';
+
+ vm.$nextTick(done);
+ });
+
+ it('does not render pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ });
+ });
+
+ describe('when state is not merged', () => {
+ beforeEach(done => {
+ vm.mr.state = 'archived';
+
+ vm.$nextTick(done);
+ });
+
+ it('does not render pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ });
+
+ it('does not render post deployment information', () => {
+ expect(vm.$el.querySelector('.js-post-deployment')).toBeNull();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
index 026a0c7ea09..3483b7d387d 100644
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -11,16 +11,6 @@ describe('collapsedGroupedDatePicker', () => {
});
});
- it('should render toggle sidebar if showToggleSidebar', (done) => {
- expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeDefined();
-
- vm.showToggleSidebar = false;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeNull();
- done();
- });
- });
-
describe('toggleCollapse events', () => {
beforeEach((done) => {
spyOn(vm, 'toggleSidebar');
@@ -28,12 +18,6 @@ describe('collapsedGroupedDatePicker', () => {
Vue.nextTick(done);
});
- it('should emit when sidebar is toggled', () => {
- vm.$el.querySelector('.gutter-toggle').click();
-
- expect(vm.toggleSidebar).toHaveBeenCalled();
- });
-
it('should emit when collapsed-calendar-icon is clicked', () => {
vm.$el.querySelector('.sidebar-collapsed-icon').click();
@@ -92,5 +76,11 @@ describe('collapsedGroupedDatePicker', () => {
expect(icons.length).toEqual(1);
expect(icons[0].innerText.trim()).toEqual('None');
});
+
+ it('should have tooltip as `Start and due date`', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons[0].dataset.originalTitle).toBe('Start and due date');
+ });
});
});
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 4d5081b0a75..e5999a1c509 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -282,6 +282,21 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
expect(pipeline_status.status).to eq(status)
expect(pipeline_status.ref).to eq(ref)
end
+
+ context 'when status is empty string' do
+ before do
+ Gitlab::Redis::Cache.with do |redis|
+ redis.mapped_hmset(cache_key,
+ { sha: sha, status: '', ref: ref })
+ end
+ end
+
+ it 'reads the status as nil' do
+ pipeline_status.load_from_cache
+
+ expect(pipeline_status.status).to eq(nil)
+ end
+ end
end
describe '#has_cache?' do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 4df426c54ae..81804ba5c76 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -10,13 +10,16 @@ describe Gitlab::Checks::ChangeAccess do
let(:ref) { 'refs/heads/master' }
let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } }
let(:protocol) { 'ssh' }
+ let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT }
+ let(:logger) { Gitlab::Checks::TimedLogger.new(timeout: timeout) }
subject(:change_access) do
described_class.new(
changes,
project: project,
user_access: user_access,
- protocol: protocol
+ protocol: protocol,
+ logger: logger
)
end
@@ -30,6 +33,19 @@ describe Gitlab::Checks::ChangeAccess do
end
end
+ context 'when time limit was reached' do
+ it 'raises a TimeoutError' do
+ logger = Gitlab::Checks::TimedLogger.new(start_time: timeout.ago, timeout: timeout)
+ access = described_class.new(changes,
+ project: project,
+ user_access: user_access,
+ protocol: protocol,
+ logger: logger)
+
+ expect { access.exec }.to raise_error(Gitlab::Checks::TimedLogger::TimeoutError)
+ end
+ end
+
context 'when the user is not allowed to push to the repo' do
it 'raises an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index ec22e3a198e..0488720cec8 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe Gitlab::Checks::LfsIntegrity do
include ProjectForksHelper
+ let!(:time_left) { 50 }
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:newrev) do
@@ -15,7 +16,7 @@ describe Gitlab::Checks::LfsIntegrity do
operations.commit_tree('8856a329dd38ca86dfb9ce5aa58a16d88cc119bd', "New LFS objects")
end
- subject { described_class.new(project, newrev) }
+ subject { described_class.new(project, newrev, time_left) }
describe '#objects_missing?' do
let(:blob_object) { repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
diff --git a/spec/lib/gitlab/checks/timed_logger_spec.rb b/spec/lib/gitlab/checks/timed_logger_spec.rb
new file mode 100644
index 00000000000..0ed3940c038
--- /dev/null
+++ b/spec/lib/gitlab/checks/timed_logger_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Checks::TimedLogger do
+ let!(:timeout) { 50.seconds }
+ let!(:start) { Time.now }
+ let!(:ref) { "bar" }
+ let!(:logger) { described_class.new(start_time: start, timeout: timeout) }
+ let!(:log_messages) do
+ {
+ foo: "Foo message..."
+ }
+ end
+
+ before do
+ logger.append_message("Checking ref: #{ref}")
+ end
+
+ describe '#log_timed' do
+ it 'logs message' do
+ Timecop.freeze(start + 30.seconds) do
+ logger.log_timed(log_messages[:foo], start) { bar_check }
+ end
+
+ expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (30000.0ms)")
+ end
+
+ context 'when time limit was reached' do
+ it 'cancels action' do
+ Timecop.freeze(start + 50.seconds) do
+ expect do
+ logger.log_timed(log_messages[:foo], start) do
+ bar_check
+ end
+ end.to raise_error(described_class::TimeoutError)
+ end
+
+ expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (cancelled)")
+ end
+
+ it 'cancels action with time elapsed if work was performed' do
+ Timecop.freeze(start + 30.seconds) do
+ expect do
+ logger.log_timed(log_messages[:foo], start) do
+ grpc_check
+ end
+ end.to raise_error(described_class::TimeoutError)
+
+ expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (cancelled after 30000.0ms)")
+ end
+ end
+ end
+ end
+
+ def bar_check
+ 2 + 2
+ end
+
+ def grpc_check
+ raise GRPC::DeadlineExceeded
+ end
+end
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index c5e7ab959b2..d035df7e0c2 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -15,5 +15,9 @@ describe Gitlab::Git::LfsChanges do
it 'limits new_objects using object_limit' do
expect(subject.new_pointers(object_limit: 1)).to eq([])
end
+
+ it 'times out if given a small dynamic timeout' do
+ expect { subject.new_pointers(dynamic_timeout: 0.001) }.to raise_error(GRPC::DeadlineExceeded)
+ end
end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index e7da5565c26..a417ef77c9e 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -934,6 +934,16 @@ describe Gitlab::GitAccess do
# There is still an N+1 query with protected branches
expect { access.check('git-receive-pack', changes) }.not_to exceed_query_limit(control_count).with_threshold(1)
end
+
+ it 'raises TimeoutError when #check_single_change_access raises a timeout error' do
+ message = "Push operation timed out\n\nTiming information for debugging purposes:\nRunning checks for ref: wow"
+
+ expect_next_instance_of(Gitlab::Checks::ChangeAccess) do |check|
+ expect(check).to receive(:exec).and_raise(Gitlab::Checks::TimedLogger::TimeoutError)
+ end
+
+ expect { access.check('git-receive-pack', changes) }.to raise_error(described_class::TimeoutError, message)
+ end
end
end
diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb
index 0385dd762c2..1e583f4cee2 100644
--- a/spec/lib/gitlab/identifier_spec.rb
+++ b/spec/lib/gitlab/identifier_spec.rb
@@ -11,11 +11,8 @@ describe Gitlab::Identifier do
describe '#identify' do
context 'without an identifier' do
- it 'identifies the user using a commit' do
- expect(identifier).to receive(:identify_using_commit)
- .with(project, '123')
-
- identifier.identify('', project, '123')
+ it 'returns nil' do
+ expect(identifier.identify('')).to be nil
end
end
@@ -24,7 +21,7 @@ describe Gitlab::Identifier do
expect(identifier).to receive(:identify_using_user)
.with("user-#{user.id}")
- identifier.identify("user-#{user.id}", project, '123')
+ identifier.identify("user-#{user.id}")
end
end
@@ -33,49 +30,11 @@ describe Gitlab::Identifier do
expect(identifier).to receive(:identify_using_ssh_key)
.with("key-#{key.id}")
- identifier.identify("key-#{key.id}", project, '123')
+ identifier.identify("key-#{key.id}")
end
end
end
- describe '#identify_using_commit' do
- it "returns the User for an existing commit author's Email address" do
- commit = double(:commit, author: user, author_email: user.email)
-
- expect(project).to receive(:commit).with('123').and_return(commit)
-
- expect(identifier.identify_using_commit(project, '123')).to eq(user)
- end
-
- it 'returns nil when no user could be found' do
- allow(project).to receive(:commit).with('123').and_return(nil)
-
- expect(identifier.identify_using_commit(project, '123')).to be_nil
- end
-
- it 'returns nil when the commit does not have an author Email' do
- commit = double(:commit, author_email: nil)
-
- expect(project).to receive(:commit).with('123').and_return(commit)
-
- expect(identifier.identify_using_commit(project, '123')).to be_nil
- end
-
- it 'caches the found users per Email' do
- commit = double(:commit, author: user, author_email: user.email)
-
- expect(project).to receive(:commit).with('123').twice.and_return(commit)
-
- 2.times do
- expect(identifier.identify_using_commit(project, '123')).to eq(user)
- end
- end
-
- it 'returns nil if the project & ref are not present' do
- expect(identifier.identify_using_commit(nil, nil)).to be_nil
- end
- end
-
describe '#identify_using_user' do
it 'returns the User for an existing ID in the identifier' do
found = identifier.identify_using_user("user-#{user.id}")
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index f5c4b0b66ae..c245e8df815 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -4,7 +4,10 @@ require 'spec_helper'
describe Clusters::Cluster do
it { is_expected.to belong_to(:user) }
+ it { is_expected.to have_many(:cluster_projects) }
it { is_expected.to have_many(:projects) }
+ it { is_expected.to have_many(:cluster_groups) }
+ it { is_expected.to have_many(:groups) }
it { is_expected.to have_one(:provider_gcp) }
it { is_expected.to have_one(:platform_kubernetes) }
it { is_expected.to have_one(:application_helm) }
@@ -178,6 +181,53 @@ describe Clusters::Cluster do
it { expect(cluster.update(enabled: false)).to be_truthy }
end
end
+
+ describe 'cluster_type validations' do
+ let(:instance_cluster) { create(:cluster, :instance) }
+ let(:group_cluster) { create(:cluster, :group) }
+ let(:project_cluster) { create(:cluster, :project) }
+
+ it 'validates presence' do
+ cluster = build(:cluster, :project, cluster_type: nil)
+
+ expect(cluster).not_to be_valid
+ expect(cluster.errors.full_messages).to include("Cluster type can't be blank")
+ end
+
+ context 'project_type cluster' do
+ it 'does not allow setting group' do
+ project_cluster.groups << build(:group)
+
+ expect(project_cluster).not_to be_valid
+ expect(project_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
+ end
+ end
+
+ context 'group_type cluster' do
+ it 'does not allow setting project' do
+ group_cluster.projects << build(:project)
+
+ expect(group_cluster).not_to be_valid
+ expect(group_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
+ end
+ end
+
+ context 'instance_type cluster' do
+ it 'does not allow setting group' do
+ instance_cluster.groups << build(:group)
+
+ expect(instance_cluster).not_to be_valid
+ expect(instance_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
+ end
+
+ it 'does not allow setting project' do
+ instance_cluster.projects << build(:project)
+
+ expect(instance_cluster).not_to be_valid
+ expect(instance_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
+ end
+ end
+ end
end
describe '#provider' do
@@ -229,6 +279,23 @@ describe Clusters::Cluster do
end
end
+ describe '#group' do
+ subject { cluster.group }
+
+ context 'when cluster belongs to a group' do
+ let(:cluster) { create(:cluster, :group) }
+ let(:group) { cluster.groups.first }
+
+ it { is_expected.to eq(group) }
+ end
+
+ context 'when cluster does not belong to any group' do
+ let(:cluster) { create(:cluster) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe '#applications' do
set(:cluster) { create(:cluster) }
diff --git a/spec/models/clusters/group_spec.rb b/spec/models/clusters/group_spec.rb
new file mode 100644
index 00000000000..ba145342cb8
--- /dev/null
+++ b/spec/models/clusters/group_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Group do
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to belong_to(:group) }
+end
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index f2eb263c98c..e7805d52d75 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -5,13 +5,15 @@ describe EnvironmentStatus do
let(:environment) { deployment.environment}
let(:project) { deployment.project }
let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
+ let(:sha) { deployment.sha }
- subject(:environment_status) { described_class.new(environment, merge_request) }
+ subject(:environment_status) { described_class.new(environment, merge_request, sha) }
it { is_expected.to delegate_method(:id).to(:environment) }
it { is_expected.to delegate_method(:name).to(:environment) }
it { is_expected.to delegate_method(:project).to(:environment) }
it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) }
+ it { is_expected.to delegate_method(:status).to(:deployment) }
describe '#project' do
subject { environment_status.project }
@@ -58,4 +60,32 @@ describe EnvironmentStatus do
)
end
end
+
+ describe '.for_merge_request' do
+ let(:admin) { create(:admin) }
+ let(:pipeline) { create(:ci_pipeline, sha: sha) }
+
+ it 'is based on merge_request.head_pipeline' do
+ expect(merge_request).to receive(:head_pipeline).and_return(pipeline)
+ expect(merge_request).not_to receive(:merge_pipeline)
+
+ described_class.for_merge_request(merge_request, admin)
+ end
+ end
+
+ describe '.after_merge_request' do
+ let(:admin) { create(:admin) }
+ let(:pipeline) { create(:ci_pipeline, sha: sha) }
+
+ before do
+ merge_request.mark_as_merged!
+ end
+
+ it 'is based on merge_request.merge_pipeline' do
+ expect(merge_request).to receive(:merge_pipeline).and_return(pipeline)
+ expect(merge_request).not_to receive(:head_pipeline)
+
+ described_class.after_merge_request(merge_request, admin)
+ end
+ end
end
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index ab58f5c5021..b6355455c1d 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -92,41 +92,6 @@ describe GlobalMilestone do
end
end
- describe '.states_count' do
- context 'when the projects have milestones' do
- before do
- create(:closed_milestone, title: 'Active Group Milestone', project: project3)
- create(:active_milestone, title: 'Active Group Milestone', project: project1)
- create(:active_milestone, title: 'Active Group Milestone', project: project2)
- create(:closed_milestone, title: 'Closed Group Milestone', project: project1)
- create(:closed_milestone, title: 'Closed Group Milestone', project: project2)
- create(:closed_milestone, title: 'Closed Group Milestone', project: project3)
- end
-
- it 'returns the quantity of global milestones in each possible state' do
- expected_count = { opened: 1, closed: 2, all: 2 }
-
- count = described_class.states_count(Project.all)
-
- expect(count).to eq(expected_count)
- end
- end
-
- context 'when the projects do not have milestones' do
- before do
- project1
- end
-
- it 'returns 0 as the quantity of global milestones in each state' do
- expected_count = { opened: 0, closed: 0, all: 0 }
-
- count = described_class.states_count(Project.all)
-
- expect(count).to eq(expected_count)
- end
- end
- end
-
describe '#initialize' do
let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 1bf8f89e126..571b160d901 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -19,6 +19,8 @@ describe Group do
it { is_expected.to have_one(:chat_team) }
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
it { is_expected.to have_many(:badges).class_name('GroupBadge') }
+ it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
+ it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
describe '#members & #requesters' do
let(:requester) { create(:user) }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 666d7e69f89..c8943f2d86f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1058,6 +1058,26 @@ describe MergeRequest do
end
end
+ describe '#merge_pipeline' do
+ it 'returns nil when not merged' do
+ expect(subject.merge_pipeline).to be_nil
+ end
+
+ context 'when the MR is merged' do
+ let(:sha) { subject.target_project.commit.id }
+ let(:pipeline) { create(:ci_empty_pipeline, sha: sha, ref: subject.target_branch, project: subject.target_project) }
+
+ before do
+ subject.mark_as_merged!
+ subject.update_attribute(:merge_commit_sha, pipeline.sha)
+ end
+
+ it 'returns the post-merge pipeline' do
+ expect(subject.merge_pipeline).to eq(pipeline)
+ end
+ end
+ end
+
describe '#has_ci?' do
let(:merge_request) { build_stubbed(:merge_request) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 27d4e622710..d11eb46159e 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -348,4 +348,41 @@ describe Milestone do
end
end
end
+
+ describe '.states_count' do
+ context 'when the projects have milestones' do
+ before do
+ project_1 = create(:project)
+ project_2 = create(:project)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ create(:active_milestone, title: 'Active Group Milestone', project: project_1)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project_1)
+ create(:active_milestone, title: 'Active Group Milestone', project: project_2)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project_2)
+ create(:closed_milestone, title: 'Active Group Milestone', group: group_1)
+ create(:closed_milestone, title: 'Closed Group Milestone', group: group_1)
+ create(:closed_milestone, title: 'Active Group Milestone', group: group_2)
+ create(:closed_milestone, title: 'Closed Group Milestone', group: group_2)
+ end
+
+ it 'returns the quantity of milestones in each possible state' do
+ expected_count = { opened: 5, closed: 6, all: 11 }
+
+ count = described_class.states_count(Project.all, Group.all)
+ expect(count).to eq(expected_count)
+ end
+ end
+
+ context 'when the projects do not have milestones' do
+ it 'returns 0 as the quantity of global milestones in each state' do
+ expected_count = { opened: 0, closed: 0, all: 0 }
+
+ count = described_class.states_count([project])
+
+ expect(count).to eq(expected_count)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index e0b5b34f9c4..2ebcb787d06 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -494,6 +494,24 @@ describe API::Internal do
end
end
+ context 'request times out' do
+ context 'git push' do
+ it 'responds with a gateway timeout' do
+ personal_project = create(:project, namespace: user.namespace)
+
+ expect_next_instance_of(Gitlab::GitAccess) do |access|
+ expect(access).to receive(:check).and_raise(Gitlab::GitAccess::TimeoutError, "Foo")
+ end
+ push(key, personal_project)
+
+ expect(response).to have_gitlab_http_status(503)
+ expect(json_response['status']).to be_falsey
+ expect(json_response['message']).to eq("Foo")
+ expect(user.reload.last_activity_on).to be_nil
+ end
+ end
+ end
+
context "archived project" do
before do
project.add_developer(user)
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index c0d5a3ad74b..909703a8d47 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -806,6 +806,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it { expect(job).to be_unknown_failure }
end
+
+ context 'when failure_reason is job_execution_timeout' do
+ before do
+ update_job(state: 'failed', failure_reason: 'job_execution_timeout')
+ job.reload
+ end
+
+ it { expect(job).to be_job_execution_timeout }
+ end
end
context 'when trace is given' do
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
index 6894c65d639..1b4d8b70aa6 100644
--- a/spec/serializers/environment_status_entity_spec.rb
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -9,7 +9,7 @@ describe EnvironmentStatusEntity do
let(:project) { deployment.project }
let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
- let(:environment_status) { EnvironmentStatus.new(environment, merge_request) }
+ let(:environment_status) { EnvironmentStatus.new(environment, merge_request, merge_request.diff_head_sha) }
let(:entity) { described_class.new(environment_status, request: request) }
subject { entity.as_json }
@@ -26,6 +26,7 @@ describe EnvironmentStatusEntity do
it { is_expected.to include(:deployed_at) }
it { is_expected.to include(:deployed_at_formatted) }
it { is_expected.to include(:changes) }
+ it { is_expected.to include(:status) }
it { is_expected.not_to include(:stop_url) }
it { is_expected.not_to include(:metrics_url) }
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 5bf8aa7f23f..561421d5ac8 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -52,6 +52,40 @@ describe MergeRequestWidgetEntity do
end
end
+ describe 'merge_pipeline' do
+ it 'returns nil' do
+ expect(subject[:merge_pipeline]).to be_nil
+ end
+
+ context 'when is merged' do
+ let(:resource) { create(:merged_merge_request, source_project: project, merge_commit_sha: project.commit.id) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.target_branch, sha: resource.merge_commit_sha) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns merge_pipeline' do
+ pipeline.reload
+ pipeline_payload = PipelineDetailsEntity
+ .represent(pipeline, request: request)
+ .as_json
+
+ expect(subject[:merge_pipeline]).to eq(pipeline_payload)
+ end
+
+ context 'when user cannot read pipelines on target project' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns nil' do
+ expect(subject[:merge_pipeline]).to be_nil
+ end
+ end
+ end
+ end
+
describe 'metrics' do
context 'when metrics record exists with merged data' do
before do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index cd6661f09a1..9176eb12b12 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -6,7 +6,7 @@ describe PostReceive do
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
let(:gl_repository) { "project-#{project.id}" }
let(:key) { create(:key, user: project.owner) }
- let(:key_id) { key.shell_id }
+ let!(:key_id) { key.shell_id }
let(:project) do
create(:project, :repository, auto_cancel_pending_pipelines: 'disabled')
@@ -31,85 +31,108 @@ describe PostReceive do
end
describe "#process_project_changes" do
- before do
- allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ context 'empty changes' do
+ it "does not call any PushService but runs after project hooks" do
+ expect(GitPushService).not_to receive(:new)
+ expect(GitTagPushService).not_to receive(:new)
+ expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
+
+ described_class.new.perform(gl_repository, key_id, "")
+ end
end
- context "branches" do
- let(:changes) { "123456 789012 refs/heads/tést" }
+ context 'unidentified user' do
+ let!(:key_id) { "" }
- it "calls GitTagPushService" do
- expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
- expect_any_instance_of(GitTagPushService).not_to receive(:execute)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ it 'returns false' do
+ expect(GitPushService).not_to receive(:new)
+ expect(GitTagPushService).not_to receive(:new)
+
+ expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false
end
end
- context "tags" do
- let(:changes) { "123456 789012 refs/tags/tag" }
+ context 'with changes' do
+ before do
+ allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ end
+
+ context "branches" do
+ let(:changes) { "123456 789012 refs/heads/tést" }
- it "calls GitTagPushService" do
- expect_any_instance_of(GitPushService).not_to receive(:execute)
- expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ it "calls GitPushService" do
+ expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
+ expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
+ end
end
- end
- context "merge-requests" do
- let(:changes) { "123456 789012 refs/merge-requests/123" }
+ context "tags" do
+ let(:changes) { "123456 789012 refs/tags/tag" }
- it "does not call any of the services" do
- expect_any_instance_of(GitPushService).not_to receive(:execute)
- expect_any_instance_of(GitTagPushService).not_to receive(:execute)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ it "calls GitTagPushService" do
+ expect_any_instance_of(GitPushService).not_to receive(:execute)
+ expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
+ end
end
- end
- context "gitlab-ci.yml" do
- let(:changes) { "123456 789012 refs/heads/feature\n654321 210987 refs/tags/tag" }
+ context "merge-requests" do
+ let(:changes) { "123456 789012 refs/merge-requests/123" }
- subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
+ it "does not call any of the services" do
+ expect_any_instance_of(GitPushService).not_to receive(:execute)
+ expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
+ end
+ end
- context "creates a Ci::Pipeline for every change" do
- before do
- stub_ci_pipeline_to_return_yaml_file
+ context "gitlab-ci.yml" do
+ let(:changes) { "123456 789012 refs/heads/feature\n654321 210987 refs/tags/tag" }
- allow_any_instance_of(Project)
- .to receive(:commit)
- .and_return(project.commit)
+ subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
- allow_any_instance_of(Repository)
- .to receive(:branch_exists?)
- .and_return(true)
- end
+ context "creates a Ci::Pipeline for every change" do
+ before do
+ stub_ci_pipeline_to_return_yaml_file
- it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }
- end
+ allow_any_instance_of(Project)
+ .to receive(:commit)
+ .and_return(project.commit)
- context "does not create a Ci::Pipeline" do
- before do
- stub_ci_pipeline_yaml_file(nil)
+ allow_any_instance_of(Repository)
+ .to receive(:branch_exists?)
+ .and_return(true)
+ end
+
+ it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }
end
- it { expect { subject }.not_to change { Ci::Pipeline.count } }
+ context "does not create a Ci::Pipeline" do
+ before do
+ stub_ci_pipeline_yaml_file(nil)
+ end
+
+ it { expect { subject }.not_to change { Ci::Pipeline.count } }
+ end
end
- end
- context 'after project changes hooks' do
- let(:changes) { '123456 789012 refs/heads/tést' }
- let(:fake_hook_data) { Hash.new(event_name: 'repository_update') }
+ context 'after project changes hooks' do
+ let(:changes) { '123456 789012 refs/heads/tést' }
+ let(:fake_hook_data) { Hash.new(event_name: 'repository_update') }
- before do
- allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
- # silence hooks so we can isolate
- allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
- allow_any_instance_of(GitPushService).to receive(:execute).and_return(true)
- end
+ before do
+ allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
+ # silence hooks so we can isolate
+ allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
+ allow_any_instance_of(GitPushService).to receive(:execute).and_return(true)
+ end
- it 'calls SystemHooksService' do
- expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
+ it 'calls SystemHooksService' do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ described_class.new.perform(gl_repository, key_id, base64_changes)
+ end
end
end
end
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index c432be72163..02ec3e2d9fe 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -1,5 +1,7 @@
alertmanager:
enabled: false
+ image:
+ tag: v0.15.2
kubeStateMetrics:
enabled: true
@@ -16,7 +18,7 @@ rbac:
server:
fullnameOverride: "prometheus-prometheus-server"
image:
- tag: v2.1.0
+ tag: v2.4.3
serverFiles:
alerts: {}