summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-30 21:09:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-30 21:09:47 +0000
commit3aeda4e6146bea1920c3283e98b01ca4fcf796a8 (patch)
treeb44e6298a749bd8a02283bc5867ab4a3269b62c3
parentadafb996ef88da50b30c737cdb8caee8307ec6d6 (diff)
downloadgitlab-ce-3aeda4e6146bea1920c3283e98b01ca4fcf796a8.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--CHANGELOG-EE.md27
-rw-r--r--app/assets/javascripts/alert_management/components/alert_details.vue37
-rw-r--r--app/assets/javascripts/alert_management/details.js15
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js13
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel.vue8
-rw-r--r--app/assets/javascripts/pages/projects/alert_management/details/index.js5
-rw-r--r--app/assets/stylesheets/page_bundles/themes/_dark.scss26
-rw-r--r--app/controllers/clusters/clusters_controller.rb40
-rw-r--r--app/controllers/oauth/authorized_applications_controller.rb7
-rw-r--r--app/helpers/projects_helper.rb1
-rw-r--r--app/serializers/cluster_entity.rb9
-rw-r--r--app/serializers/cluster_serializer.rb14
-rw-r--r--app/serializers/remote_mirror_entity.rb2
-rw-r--r--app/views/projects/alert_management/details.html.haml2
-rw-r--r--changelogs/unreleased/set-multiple-custom-metrics-edit-path.yml5
-rw-r--r--config/application.rb1
-rw-r--r--doc/development/import_export.md79
-rw-r--r--doc/integration/img/jenkins_gitlab_service_settings.pngbin24094 -> 0 bytes
-rw-r--r--doc/integration/jenkins.md203
-rw-r--r--lib/api/api.rb9
-rw-r--r--lib/gitlab/middleware/multipart.rb18
-rw-r--r--lib/uploaded_file.rb9
-rw-r--r--locale/gitlab.pot15
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb2
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb25
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb25
-rw-r--r--spec/controllers/oauth/authorized_applications_controller_spec.rb21
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb27
-rw-r--r--spec/fixtures/api/schemas/cluster_list.json14
-rw-r--r--spec/frontend/alert_management/components/alert_management_detail_spec.js34
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js17
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js67
-rw-r--r--spec/helpers/application_helper_spec.rb23
-rw-r--r--spec/javascripts/pipelines/time_ago_spec.js64
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb109
-rw-r--r--spec/lib/uploaded_file_spec.rb167
-rw-r--r--spec/serializers/cluster_entity_spec.rb6
-rw-r--r--spec/serializers/remote_mirror_entity_spec.rb7
39 files changed, 927 insertions, 228 deletions
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md
index 5bc5fa8b9cc..4d98d9a56e6 100644
--- a/CHANGELOG-EE.md
+++ b/CHANGELOG-EE.md
@@ -1,5 +1,14 @@
Please view this file on the master branch, on stable branches it's out of date.
+## 12.10.2 (2020-04-30)
+
+### Security (3 changes)
+
+- Fix rendering failure of Audit Event generated by Releases API.
+- Ensure that NuGet package versions are SemVer compliant.
+- Ensure that NuGet package versions are validated before updating the stored file path.
+
+
## 12.10.1 (2020-04-24)
### Changed (1 change)
@@ -50,6 +59,15 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add health status counts to usage data. !28964
+## 12.9.5 (2020-04-30)
+
+### Security (3 changes)
+
+- Fix rendering failure of Audit Event generated by Releases API.
+- Ensure that NuGet package versions are SemVer compliant.
+- Ensure that NuGet package versions are validated before updating the stored file path.
+
+
## 12.9.4 (2020-04-16)
- No changes.
@@ -222,6 +240,15 @@ Please view this file on the master branch, on stable branches it's out of date.
- Allow users to be marked as service users. !202680
+## 12.8.10 (2020-04-30)
+
+### Security (3 changes)
+
+- Fix rendering failure of Audit Event generated by Releases API.
+- Ensure that NuGet package versions are SemVer compliant.
+- Ensure that NuGet package versions are validated before updating the stored file path.
+
+
## 12.8.9 (2020-04-14)
### Security (1 change)
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue
new file mode 100644
index 00000000000..3f86f7e78e8
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_details.vue
@@ -0,0 +1,37 @@
+<script>
+import { GlTabs, GlTab } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ i18n: {
+ fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'),
+ overviewTitle: s__('AlertManagement|Overview'),
+ },
+ components: {
+ GlTab,
+ GlTabs,
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="d-flex justify-content-between">
+ <gl-tabs>
+ <gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
+ <ul class="pl-3">
+ <li data-testid="startTimeItem" class="font-weight-bold mb-3 mt-2">
+ {{ s__('AlertManagement|Start time:') }}
+ </li>
+ <li class="font-weight-bold my-3">
+ {{ s__('AlertManagement|End time:') }}
+ </li>
+ <li class="font-weight-bold my-3">
+ {{ s__('AlertManagement|Events:') }}
+ </li>
+ </ul>
+ </gl-tab>
+ <gl-tab data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle" />
+ </gl-tabs>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/alert_management/details.js b/app/assets/javascripts/alert_management/details.js
new file mode 100644
index 00000000000..23c3b8a742a
--- /dev/null
+++ b/app/assets/javascripts/alert_management/details.js
@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import AlertDetails from './components/alert_details.vue';
+
+export default selector => {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: selector,
+ components: {
+ AlertDetails,
+ },
+ render(createElement) {
+ return createElement('alert-details', {});
+ },
+ });
+};
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index fe63ebd470d..e9f563d3781 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -24,13 +24,24 @@ let mermaidModule = {};
function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
+ let theme = 'neutral';
+
+ if (
+ window.gon?.user_color_scheme === 'dark' &&
+ window.gon?.features?.webideDarkTheme &&
+ // if on the Web IDE page
+ document.querySelector('.ide')
+ ) {
+ theme = 'dark';
+ }
+
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
- theme: 'neutral',
+ theme,
flowchart: {
useMaxWidth: true,
htmlLabels: false,
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index a77ee815b2c..1877f58ed96 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -552,6 +552,7 @@ export default {
<dashboard-panel
v-show="expandedPanel.panel"
ref="expandedPanel"
+ :settings-path="settingsPath"
:clipboard-text="generatePanelLink(expandedPanel.group, expandedPanel.panel)"
:graph-data="expandedPanel.panel"
:alerts-endpoint="alertsEndpoint"
@@ -610,6 +611,7 @@ export default {
</div>
<dashboard-panel
+ :settings-path="settingsPath"
:clipboard-text="generatePanelLink(groupData.group, graphData)"
:graph-data="graphData"
:alerts-endpoint="alertsEndpoint"
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
index a269ef83799..24e95c52820 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
@@ -82,6 +82,11 @@ export default {
required: false,
default: false,
},
+ settingsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -196,6 +201,9 @@ export default {
return Boolean(this.graphDataHasResult && !this.basicChartComponent);
},
editCustomMetricLink() {
+ if (this.graphData.metrics.length > 1) {
+ return this.settingsPath;
+ }
return this.graphData?.metrics[0].edit_path;
},
editCustomMetricLinkText() {
diff --git a/app/assets/javascripts/pages/projects/alert_management/details/index.js b/app/assets/javascripts/pages/projects/alert_management/details/index.js
new file mode 100644
index 00000000000..0124795e1af
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/alert_management/details/index.js
@@ -0,0 +1,5 @@
+import AlertDetails from '~/alert_management/details';
+
+document.addEventListener('DOMContentLoaded', () => {
+ AlertDetails('#js-alert_details');
+});
diff --git a/app/assets/stylesheets/page_bundles/themes/_dark.scss b/app/assets/stylesheets/page_bundles/themes/_dark.scss
index faadf31a87e..ef9a648a93e 100644
--- a/app/assets/stylesheets/page_bundles/themes/_dark.scss
+++ b/app/assets/stylesheets/page_bundles/themes/_dark.scss
@@ -27,6 +27,9 @@
$btn-disabled-border: rgba(223, 223, 223, 0.24);
$btn-disabled-color: rgba(145, 145, 145, 0.48);
+ $diff-insert: rgba(155, 185, 85, 0.2);
+ $diff-remove: rgba(255, 0, 0, 0.2);
+
a {
color: $link-color;
}
@@ -37,6 +40,8 @@
h4:not(.modal-title),
h5,
h6,
+ code,
+ .md table:not(.code),
.md,
.md p,
.ide-view,
@@ -86,6 +91,7 @@
background-color: transparent;
}
+ code,
.multi-file-commit-panel,
.multi-file-tabs,
.multi-file-tabs li,
@@ -102,6 +108,10 @@
background-color: $background;
}
+ pre code {
+ background-color: inherit;
+ }
+
.ide-sidebar-link:hover {
background-color: $background-hover;
}
@@ -111,6 +121,7 @@
}
&,
+ .md table:not(.code) tr th,
.multi-file-commit-panel-inner-content,
.multi-file-commit-form,
.multi-file-tabs li.active,
@@ -141,6 +152,12 @@
border-color: $border-color;
}
+ .md h1,
+ .md h2,
+ .md blockquote,
+ pre,
+ .md table:not(.code) tbody td,
+ .md table:not(.code) tr th,
.multi-file-commit-form > .commit-form-compact,
.ide-tree-header,
.multi-file-commit-panel-header,
@@ -267,6 +284,7 @@
}
.md-previewer,
+ .md table:not(.code) tbody,
.ide-empty-state {
background-color: $border-color;
}
@@ -289,6 +307,14 @@
}
}
}
+
+ .idiff.addition {
+ background-color: $diff-insert;
+ }
+
+ .idiff.deletion {
+ background-color: $diff-remove;
+ }
}
.navbar.theme-dark {
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 2c9ee69c8c4..aa39d430b24 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -18,20 +18,19 @@ class Clusters::ClustersController < Clusters::BaseController
STATUS_POLLING_INTERVAL = 10_000
def index
- finder = ClusterAncestorsFinder.new(clusterable.subject, current_user)
- clusters = finder.execute
+ @clusters = cluster_list
- # Note: We are paginating through an array here but this should OK as:
- #
- # In CE, we can have a maximum group nesting depth of 21, so including
- # project cluster, we can have max 22 clusters for a group hierarchy.
- # In EE (Premium) we can have any number, as multiple clusters are
- # supported, but the number of clusters are fairly low currently.
- #
- # See https://gitlab.com/gitlab-org/gitlab-foss/issues/55260 also.
- @clusters = Kaminari.paginate_array(clusters).page(params[:page]).per(20)
+ respond_to do |format|
+ format.html
+ format.json do
+ serializer = ClusterSerializer.new(current_user: current_user)
- @has_ancestor_clusters = finder.has_ancestor_clusters?
+ render json: {
+ clusters: serializer.with_pagination(request, response).represent_list(@clusters),
+ has_ancestor_clusters: @has_ancestor_clusters
+ }
+ end
+ end
end
def new
@@ -158,6 +157,23 @@ class Clusters::ClustersController < Clusters::BaseController
private
+ def cluster_list
+ finder = ClusterAncestorsFinder.new(clusterable.subject, current_user)
+ clusters = finder.execute
+
+ @has_ancestor_clusters = finder.has_ancestor_clusters?
+
+ # Note: We are paginating through an array here but this should OK as:
+ #
+ # In CE, we can have a maximum group nesting depth of 21, so including
+ # project cluster, we can have max 22 clusters for a group hierarchy.
+ # In EE (Premium) we can have any number, as multiple clusters are
+ # supported, but the number of clusters are fairly low currently.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-foss/issues/55260 also.
+ Kaminari.paginate_array(clusters).page(params[:page]).per(20)
+ end
+
def destroy_params
params.permit(:cleanup)
end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index 9cfa57c53a5..addec71f0bf 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -5,6 +5,13 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
layout 'profile'
+ def index
+ respond_to do |format|
+ format.html { render "errors/not_found", layout: "errors", status: :not_found }
+ format.json { render json: "", status: :not_found }
+ end
+ end
+
def destroy
if params[:token_id].present?
current_resource_owner.oauth_authorized_tokens.find(params[:token_id]).revoke
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a8cde2b723e..0d05d60d9fc 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -626,6 +626,7 @@ module ProjectsHelper
def find_file_path
return unless @project && !@project.empty_repo?
+ return unless can?(current_user, :download_code, @project)
ref = @ref || @project.repository.root_ref
diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb
index c59f68bbc49..a4d094adf7c 100644
--- a/app/serializers/cluster_entity.rb
+++ b/app/serializers/cluster_entity.rb
@@ -3,7 +3,16 @@
class ClusterEntity < Grape::Entity
include RequestAwareEntity
+ expose :cluster_type
+ expose :enabled
+ expose :environment_scope
+ expose :name
expose :status_name, as: :status
expose :status_reason
+
+ expose :path do |cluster|
+ Clusters::ClusterPresenter.new(cluster).show_path # rubocop: disable CodeReuse/Presenter
+ end
+
expose :applications, using: ClusterApplicationEntity
end
diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb
index 4bb4d4880d4..418308f3fa6 100644
--- a/app/serializers/cluster_serializer.rb
+++ b/app/serializers/cluster_serializer.rb
@@ -1,8 +1,22 @@
# frozen_string_literal: true
class ClusterSerializer < BaseSerializer
+ include WithPagination
entity ClusterEntity
+ def represent_list(resource)
+ represent(resource, {
+ only: [
+ :cluster_type,
+ :enabled,
+ :environment_scope,
+ :name,
+ :path,
+ :status
+ ]
+ })
+ end
+
def represent_status(resource)
represent(resource, { only: [:status, :status_reason, :applications] })
end
diff --git a/app/serializers/remote_mirror_entity.rb b/app/serializers/remote_mirror_entity.rb
index 8835c6d4647..440e4274668 100644
--- a/app/serializers/remote_mirror_entity.rb
+++ b/app/serializers/remote_mirror_entity.rb
@@ -2,7 +2,7 @@
class RemoteMirrorEntity < Grape::Entity
expose :id
- expose :url
+ expose :safe_url, as: :url
expose :enabled
expose :auth_method
diff --git a/app/views/projects/alert_management/details.html.haml b/app/views/projects/alert_management/details.html.haml
index bf2870328b5..1208c541a9d 100644
--- a/app/views/projects/alert_management/details.html.haml
+++ b/app/views/projects/alert_management/details.html.haml
@@ -1 +1,3 @@
- page_title _('Alert Details')
+
+#js-alert_details
diff --git a/changelogs/unreleased/set-multiple-custom-metrics-edit-path.yml b/changelogs/unreleased/set-multiple-custom-metrics-edit-path.yml
new file mode 100644
index 00000000000..ca911ef6999
--- /dev/null
+++ b/changelogs/unreleased/set-multiple-custom-metrics-edit-path.yml
@@ -0,0 +1,5 @@
+---
+title: Multiple metrics edit navigates to prom edit page
+merge_request: 30666
+author:
+type: added
diff --git a/config/application.rb b/config/application.rb
index a135bef342a..d8f02277527 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -131,6 +131,7 @@ module Gitlab
encrypted_key
hook
import_url
+ elasticsearch_url
otp_attempt
sentry_dsn
trace
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index 5a7d176d1d3..40a56679f2f 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -331,3 +331,82 @@ module Projects
wiki_repo_saver, lfs_saver].all?(&:save)
end
```
+
+## Test fixtures
+
+Fixtures used in Import/Export specs live in `spec/fixtures/lib/gitlab/import_export`. There are both Project and Group fixtures.
+
+There are two versions of each of these fixtures:
+
+- A human readable single JSON file with all objects, called either `project.json` or `group.json`.
+- A tree.tar.gz file containing a tree of files in `ndjson` format. **Please do not edit this file manually unless strictly necessary.**
+
+The tools to generate the NDJSON tree from the human-readable JSON files live in the [`gitlab-org/memory-team/team-tools`](https://gitlab.com/gitlab-org/memory-team/team-tools/-/blob/master/import-export/) project.
+
+### Project
+
+**Please use `legacy-project-json-to-ndjson.sh` to generate the NDJSON tree.**
+Once you're done generating the files, please package them using `tar -czf tree.tar.gz tree` from the same directory as the `tree` directory generated is located.
+
+The NDJSON tree will look like this:
+
+```shell
+tree
+├── project
+│   ├── auto_devops.ndjson
+│   ├── boards.ndjson
+│   ├── ci_cd_settings.ndjson
+│   ├── ci_pipelines.ndjson
+│   ├── container_expiration_policy.ndjson
+│   ├── custom_attributes.ndjson
+│   ├── error_tracking_setting.ndjson
+│   ├── external_pull_requests.ndjson
+│   ├── issues.ndjson
+│   ├── labels.ndjson
+│   ├── merge_requests.ndjson
+│   ├── milestones.ndjson
+│   ├── pipeline_schedules.ndjson
+│   ├── project_badges.ndjson
+│   ├── project_feature.ndjson
+│   ├── project_members.ndjson
+│   ├── protected_branches.ndjson
+│   ├── protected_tags.ndjson
+│   ├── releases.ndjson
+│   ├── services.ndjson
+│   ├── snippets.ndjson
+│   └── triggers.ndjson
+└── project.json
+```
+
+### Group
+
+**Please use `legacy-group-json-to-ndjson.rb` to generate the NDJSON tree.** This script can be found in [`gitlab-org/memory-team/team-tools!7`](https://gitlab.com/gitlab-org/memory-team/team-tools/-/merge_requests/7).
+Once this MR is merged, the script will be found in the directory mentioned earlier.
+
+Once you're done generating the files, please package them using `tar -czf tree.tar.gz tree` from the same directory as the `tree` directory generated is located.
+
+The NDJSON tree will look like this:
+
+```shell
+tree
+└── groups
+ ├── 4351
+ │   ├── badges.ndjson
+ │   ├── boards.ndjson
+ │   ├── epics.ndjson
+ │   ├── labels.ndjson
+ │   ├── members.ndjson
+ │   └── milestones.ndjson
+ ├── 4352
+ │   ├── badges.ndjson
+ │   ├── boards.ndjson
+ │   ├── epics.ndjson
+ │   ├── labels.ndjson
+ │   ├── members.ndjson
+ │   └── milestones.ndjson
+ ├── _all.ndjson
+ ├── 4351.json
+ └── 4352.json
+```
+
+CAUTION: **Caution:** When updating these fixtures, please ensure you update the `json` files and the `tar.gz` archives, as the tests apply to both.
diff --git a/doc/integration/img/jenkins_gitlab_service_settings.png b/doc/integration/img/jenkins_gitlab_service_settings.png
deleted file mode 100644
index 5a12e9cb39a..00000000000
--- a/doc/integration/img/jenkins_gitlab_service_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index 5514b756c00..22be1ae9b5c 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -1,119 +1,156 @@
# Jenkins CI service **(STARTER)**
->**Note:**
-In GitLab 8.3, Jenkins integration using the
-[GitLab Hook Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
-was deprecated in favor of the
-[GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
-The deprecated integration has been renamed to [Jenkins CI (Deprecated)](jenkins_deprecated.md) in the
-integration settings. We may remove this in a future release and recommend
-using the new 'Jenkins CI' integration instead which is described in this
-document.
-
-## Overview
-
-[Jenkins](https://jenkins.io/) is a great Continuous Integration tool, similar to our built-in
-[GitLab CI/CD](../ci/README.md).
-
-GitLab's Jenkins integration allows you to trigger a Jenkins build when you
-push code to a repository, or when a merge request is created. Additionally,
-it shows the pipeline status on merge requests widgets and on the project's home page.
-
-Videos are also available on [GitLab workflow with Jira issues and Jenkins pipelines](https://youtu.be/Jn-_fyra7xQ)
-and [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y).
-
-## Use cases
-
-- Suppose you are new to GitLab, and want to keep using Jenkins until you prepare
- your projects to build with [GitLab CI/CD](../ci/README.md). You set up the
- integration between GitLab and Jenkins, then you migrate to GitLab CI/CD later. While
- you organize yourself and your team to onboard GitLab, you keep your pipelines
- running with Jenkins, but view the results in your project's repository in GitLab.
-- Your team uses [Jenkins Plugins](https://plugins.jenkins.io/) for other proceedings,
- therefore, you opt for keep using Jenkins to build your apps. Show the results of your
- pipelines directly in GitLab.
+From GitLab, you can trigger a Jenkins build when you push code to a repository, or when a merge
+request is created. In return, Jenkins shows the pipeline status on merge requests widgets and
+on the GitLab project's home page.
+
+To better understand GitLab's Jenkins integration, watch the following videos:
+
+- [GitLab workflow with Jira issues and Jenkins pipelines](https://youtu.be/Jn-_fyra7xQ)
+- [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y)
+
+Use the Jenkins integration with GitLab when:
+
+- You plan to migrate your CI from Jenkins to [GitLab CI/CD](../ci/README.md) in the future, but
+need an interim solution.
+- You're invested in [Jenkins Plugins](https://plugins.jenkins.io/) and choose to keep using Jenkins
+to build your apps.
For a real use case, read the blog post [Continuous integration: From Jenkins to GitLab using Docker](https://about.gitlab.com/blog/2017/07/27/docker-my-precious/).
-NOTE: **Moving from a traditional CI plug-in to a single application for the entire software development lifecycle can decrease hours spent on maintaining toolchains by 10% or more.**
-Visit the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html) to learn how our built-in CI compares to Jenkins.
+Moving from a traditional CI plug-in to a single application for the entire software development
+life cycle can decrease hours spent on maintaining toolchains by 10% or more. For more details, see
+the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html).
-## Requirements
+## Configure GitLab integration with Jenkins
-- [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
-- [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin)
-- Git clone access for Jenkins from the GitLab repository
-- GitLab API access to report build status
+GitLab's Jenkins integration requires installation and configuration in both GitLab and Jenkins.
+In GitLab, you need to grant Jenkins access to the relevant projects. In Jenkins, you need to
+install and configure several plugins.
-## Configure GitLab users
+### GitLab requirements
-Create a user or choose an existing user that Jenkins will use to interact
-through the GitLab API. This user will need to be a global Admin or added
-as a member to each Group/Project. Developer permission is required for reporting
-build status. This is because a successful build status can trigger a merge
-when 'Merge when pipeline succeeds' feature is used. Some features of the GitLab
-Plugin may require additional privileges. For example, there is an option to
-accept a merge request if the build is successful. Using this feature would
-require developer, maintainer or owner-level permission.
+- [Grant Jenkins permission to GitLab project](#grant-jenkins-access-to-gitlab-project)
+- [Configure GitLab API access](#configure-gitlab-api-access)
+- [Configure the GitLab project](#configure-the-gitlab-project)
-Copy the private API token from **Profile Settings -> Account**. You will need this
-when configuring the Jenkins server later.
+### Jenkins requirements
-## Configure the Jenkins server
+- [Configure the Jenkins server](#configure-the-jenkins-server)
+- [Configure the Jenkins project](#configure-the-jenkins-project)
-Install [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
-and [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin).
+## Grant Jenkins access to GitLab project
-Go to Manage Jenkins -> Configure System and scroll down to the 'GitLab' section.
-Enter the GitLab server URL in the 'GitLab host URL' field and paste the API token
-copied earlier in the 'API Token' field.
+Grant a GitLab user access to the select GitLab projects.
-For more information, see GitLab Plugin documentation about
-[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication)
+1. Create a new GitLab user, or choose an existing GitLab user.
-![Jenkins GitLab plugin configuration](img/jenkins_gitlab_plugin_config.png)
+ This account will be used by Jenkins to access the GitLab projects. We recommend creating a GitLab
+ user for only this purpose. If you use a person's account, and their account is deactivated or
+ deleted, the GitLab-Jenkins integration will stop working.
+
+1. Grant the user permission to the GitLab projects.
+
+ If you're integrating Jenkins with many GitLab projects, consider granting the user global
+ Admin permission. Otherwise, add the user to each project, and grant Developer permission.
-## Configure a Jenkins project
+## Configure GitLab API access
-Follow the GitLab Plugin documentation about [Jenkins Job Configuration](https://github.com/jenkinsci/gitlab-plugin#jenkins-job-configuration).
+Create a personal access token to authorize Jenkins' access to GitLab.
-NOTE: **Note:**
-Be sure to include the steps about [Build status configuration](https://github.com/jenkinsci/gitlab-plugin#build-status-configuration).
-The 'Publish build status to GitLab' post-build step is required to view
-Jenkins build status in GitLab Merge Requests.
+1. Log in to GitLab as the user to be used with Jenkins.
+1. Click your avatar, then **Settings.
+1. Click **Access Tokens** in the sidebar.
+1. Create a personal access token with the **API** scope checkbox checked. For more details, see
+ [Personal access tokens](../user/profile/personal_access_tokens.md).
+1. Record the personal access token's value, because it's required in [Configure the Jenkins server](#configure-the-jenkins-server).
-## Configure a GitLab project
+## Configure the Jenkins server
-Create a new GitLab project or choose an existing one. Then, go to **Integrations ->
-Jenkins CI**.
+Install and configure the Jenkins plugins. Both plugins must be installed and configured to
+authorize the connection to GitLab.
-Check the 'Active' box. Select whether you want GitLab to trigger a build
-on push, Merge Request creation, tag push, or any combination of these. We
-recommend unchecking 'Merge Request events' unless you have a specific use-case
-that requires re-building a commit when a merge request is created. With 'Push
-events' selected, GitLab will build the latest commit on each push and the build
-status will be displayed in the merge request.
+1. On the Jenkins server, go to **Manage Jenkins > Manage Plugins**.
+1. Install the [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
+1. Go to **Manage Jenkins > Configure System**.
+1. In the **GitLab** section, check the **Enable authentication for ‘/project’ end-point** checkbox.
+1. Click **Add**, then choose **Jenkins Credential Provider**.
+1. Choose **GitLab API token** as the token type.
+1. Enter the GitLab personal access token's value in the **API Token** field and click **Add**.
+1. Enter the GitLab server's URL in the **GitLab host URL** field.
+1. Click **Test Connection**, ensuring the connection is successful before proceeding.
-Enter the Jenkins URL and Project name. The project name should be URL-friendly
-where spaces are replaced with underscores. To be safe, copy the project name
-from the URL bar of your browser while viewing the Jenkins project.
+For more information, see GitLab Plugin documentation about
+[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication).
-Optionally, enter a username and password if your Jenkins server requires
-authentication.
+![Jenkins GitLab plugin configuration](img/jenkins_gitlab_plugin_config.png)
-![GitLab service settings](img/jenkins_gitlab_service_settings.png)
+## Configure the Jenkins project
+
+Set up the Jenkins project you’re going to run your build on.
+
+1. On your Jenkins instance, go to **New Item**.
+1. Enter the project's name.
+1. Choose between **Freestyle** or **Pipeline** and click **OK**.
+ We recommend a Freestyle project, because the Jenkins plugin will update the build status on
+ GitLab. In a Pipeline project, you must configure a script to update the status on GitLab.
+1. Choose your GitLab connection from the dropdown.
+1. Check the **Build when a change is pushed to GitLab** checkbox.
+1. Check the following checkboxes:
+ - **Accepted Merge Request Events**
+ - **Closed Merge Request Events**
+1. If you created a **Freestyle** project, choose Publish build status to GitLab in the Post-build Actions section.
+ If you created a **Pipeline** project, you must use a pipeline script to update the status on
+ GitLab. The following is an example pipeline script:
+
+ ```plaintext
+ pipeline {
+ agent any
+
+ stages {
+ stage('gitlab') {
+ steps {
+ echo 'Notify GitLab'
+ updateGitlabCommitStatus name: 'build', state: 'pending'
+ updateGitlabCommitStatus name: 'build', state: 'success'
+ }
+ }
+ }
+ }
+ ```
+
+## Configure the GitLab project
+
+Configure the GitLab integration with Jenkins.
+
+1. Create a new GitLab project or choose an existing one.
+1. Go to **Settings > Integrations**, then select **Jenkins CI**.
+1. Turn on the **Active** toggle.
+1. Select the events you want GitLab to trigger a Jenkins build for:
+ - Push
+ - Merge request
+ - Tag push
+1. Enter the **Jenkins URL**.
+1. Enter the **Project name**.
+
+ The project name should be URL-friendly, where spaces are replaced with underscores. To ensure
+ the project name is valid, copy it from your browser's address bar while viewing the Jenkins
+ project.
+1. Enter the **Username** and **Password** if your Jenkins server requires
+ authentication.
+1. Click **Test settings and save changes**. GitLab tests the connection to Jenkins.
## Plugin functional overview
GitLab does not contain a database table listing commits. Commits are always
-read from the repository directly. Therefore, it is not possible to retain the
+read from the repository directly. Therefore, it's not possible to retain the
build status of a commit in GitLab. This is overcome by requesting build
information from the integrated CI tool. The CI tool is responsible for creating
and storing build status for Commits and Merge Requests.
### Steps required to implement a similar integration
->**Note:**
+**Note:**
All steps are implemented using AJAX requests on the merge request page.
1. In order to display the build status in a merge request you must create a project service in GitLab.
@@ -133,7 +170,7 @@ receive a build status update via the API. Either Jenkins was not properly
configured or there was an error reporting the status via the API.
1. [Configure the Jenkins server](#configure-the-jenkins-server) for GitLab API access
-1. [Configure a Jenkins project](#configure-a-jenkins-project), including the
+1. [Configure the Jenkins project](#configure-the-jenkins-project), including the
'Publish build status to GitLab' post-build action.
### Merge Request event does not trigger a Jenkins Pipeline
diff --git a/lib/api/api.rb b/lib/api/api.rb
index de9a3120d90..5816d2db534 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -97,6 +97,15 @@ module API
handle_api_exception(exception)
end
+ # This is a specific exception raised by `rack-timeout` gem when Puma
+ # requests surpass its timeout. Given it inherits from Exception, we
+ # should rescue it separately. For more info, see:
+ # - https://github.com/sharpstone/rack-timeout/blob/master/doc/exceptions.md
+ # - https://github.com/ruby-grape/grape#exception-handling
+ rescue_from Rack::Timeout::RequestTimeoutException do |exception|
+ handle_api_exception(exception)
+ end
+
format :json
content_type :txt, "text/plain"
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index c82c05e7319..7d0de3aee1c 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -43,11 +43,13 @@ module Gitlab
raise "unexpected field: #{field.inspect}" unless parsed_field.count == 1
key, value = parsed_field.first
- if value.nil?
- value = open_file(@request.params, key)
+ if value.nil? # we have a top level param, eg. field = 'foo' and not 'foo[bar]'
+ raise "invalid field: #{field.inspect}" if field != key
+
+ value = open_file(@request.params, key, tmp_path.presence)
@open_files << value
else
- value = decorate_params_value(value, @request.params[key])
+ value = decorate_params_value(value, @request.params[key], tmp_path.presence)
end
update_param(key, value)
@@ -59,7 +61,7 @@ module Gitlab
end
# This function calls itself recursively
- def decorate_params_value(path_hash, value_hash)
+ def decorate_params_value(path_hash, value_hash, path_override = nil)
unless path_hash.is_a?(Hash) && path_hash.count == 1
raise "invalid path: #{path_hash.inspect}"
end
@@ -72,19 +74,19 @@ module Gitlab
case path_value
when nil
- value_hash[path_key] = open_file(value_hash.dig(path_key), '')
+ value_hash[path_key] = open_file(value_hash.dig(path_key), '', path_override)
@open_files << value_hash[path_key]
value_hash
when Hash
- decorate_params_value(path_value, value_hash[path_key])
+ decorate_params_value(path_value, value_hash[path_key], path_override)
value_hash
else
raise "unexpected path value: #{path_value.inspect}"
end
end
- def open_file(params, key)
- ::UploadedFile.from_params(params, key, allowed_paths)
+ def open_file(params, key, path_override = nil)
+ ::UploadedFile.from_params(params, key, allowed_paths, path_override)
end
# update_params ensures that both rails controllers and rack middleware can find
diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb
index f8d596b5d14..73029c934f4 100644
--- a/lib/uploaded_file.rb
+++ b/lib/uploaded_file.rb
@@ -42,13 +42,14 @@ class UploadedFile
@remote_id = remote_id
end
- def self.from_params(params, field, upload_paths)
- path = params["#{field}.path"]
+ def self.from_params(params, field, upload_paths, path_override = nil)
+ path = path_override || params["#{field}.path"]
remote_id = params["#{field}.remote_id"]
return if path.blank? && remote_id.blank?
- file_path = nil
- if path.present?
+ if remote_id.present? # don't use file_path if remote_id is set
+ file_path = nil
+ elsif path.present?
file_path = File.realpath(path)
paths = Array(upload_paths) << Dir.tmpdir
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bff051d896f..37b1459886d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1704,9 +1704,18 @@ msgstr ""
msgid "AlertManagement|End time"
msgstr ""
+msgid "AlertManagement|End time:"
+msgstr ""
+
msgid "AlertManagement|Events"
msgstr ""
+msgid "AlertManagement|Events:"
+msgstr ""
+
+msgid "AlertManagement|Full Alert Details"
+msgstr ""
+
msgid "AlertManagement|More information"
msgstr ""
@@ -1716,12 +1725,18 @@ msgstr ""
msgid "AlertManagement|No alerts to display."
msgstr ""
+msgid "AlertManagement|Overview"
+msgstr ""
+
msgid "AlertManagement|Severity"
msgstr ""
msgid "AlertManagement|Start time"
msgstr ""
+msgid "AlertManagement|Start time:"
+msgstr ""
+
msgid "AlertManagement|Status"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
index d38b8560a38..dfcbf4b44c8 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', :smoke, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/215031', type: :investigating } do
+ context 'Create', :smoke do
describe 'Snippet creation' do
it 'User creates a snippet' do
Flow::Login.sign_in
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index bd6d5614ccd..d4a12e0dc52 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -27,7 +27,7 @@ describe Admin::ClustersController do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
end
- it 'lists available clusters' do
+ it 'lists available clusters and displays html' do
get_index
expect(response).to have_gitlab_http_status(:ok)
@@ -35,20 +35,39 @@ describe Admin::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
+ it 'lists available clusters and renders json serializer' do
+ get_index(format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('cluster_list')
+ end
+
context 'when page is specified' do
let(:last_page) { Clusters::Cluster.instance_type.page.total_pages }
+ let(:total_count) { Clusters::Cluster.instance_type.page.total_count }
before do
- allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
- create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance)
+ create_list(:cluster, 30, :provided_by_gcp, :production_environment, :instance)
end
it 'redirects to the page' do
+ expect(last_page).to be > 1
+
get_index(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
+
+ it 'displays cluster list for associated page' do
+ expect(last_page).to be > 1
+
+ get_index(page: last_page, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Page'].to_i).to eq(last_page)
+ expect(response.headers['X-Total'].to_i).to eq(total_count)
+ end
end
end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 28a174560dd..1f2f6bd811b 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -32,7 +32,7 @@ describe Groups::ClustersController do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
end
- it 'lists available clusters' do
+ it 'lists available clusters and renders html' do
go
expect(response).to have_gitlab_http_status(:ok)
@@ -40,20 +40,39 @@ describe Groups::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
+ it 'lists available clusters with json serializer' do
+ go(format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('cluster_list')
+ end
+
context 'when page is specified' do
let(:last_page) { group.clusters.page.total_pages }
+ let(:total_count) { group.clusters.page.total_count }
before do
- allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
- create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
+ create_list(:cluster, 30, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
end
it 'redirects to the page' do
+ expect(last_page).to be > 1
+
go(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
+
+ it 'displays cluster list for associated page' do
+ expect(last_page).to be > 1
+
+ go(page: last_page, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Page'].to_i).to eq(last_page)
+ expect(response.headers['X-Total'].to_i).to eq(total_count)
+ end
end
end
diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb
new file mode 100644
index 00000000000..32be6a3ddb7
--- /dev/null
+++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Oauth::AuthorizedApplicationsController do
+ let(:user) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:application) { create(:oauth_application, owner: guest) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ it 'responds with 404' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 07733ec30d9..698a3773d59 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -26,7 +26,7 @@ describe Projects::ClustersController do
let!(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let!(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, :production_environment, projects: [project]) }
- it 'lists available clusters' do
+ it 'lists available clusters and renders html' do
go
expect(response).to have_gitlab_http_status(:ok)
@@ -34,20 +34,39 @@ describe Projects::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
+ it 'lists available clusters with json serializer' do
+ go(format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('cluster_list')
+ end
+
context 'when page is specified' do
let(:last_page) { project.clusters.page.total_pages }
+ let(:total_count) { project.clusters.page.total_count }
before do
- allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
- create_list(:cluster, 2, :provided_by_gcp, :production_environment, projects: [project])
+ create_list(:cluster, 30, :provided_by_gcp, :production_environment, projects: [project])
end
it 'redirects to the page' do
+ expect(last_page).to be > 1
+
go(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
+
+ it 'displays cluster list for associated page' do
+ expect(last_page).to be > 1
+
+ go(page: last_page, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Page'].to_i).to eq(last_page)
+ expect(response.headers['X-Total'].to_i).to eq(total_count)
+ end
end
end
@@ -68,9 +87,11 @@ describe Projects::ClustersController do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin)
end
+
it 'is disabled for admin when admin mode disabled' do
expect { go }.to be_denied_for(:admin)
end
+
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
it { expect { go }.to be_denied_for(:developer).of(project) }
diff --git a/spec/fixtures/api/schemas/cluster_list.json b/spec/fixtures/api/schemas/cluster_list.json
new file mode 100644
index 00000000000..ece9542eb79
--- /dev/null
+++ b/spec/fixtures/api/schemas/cluster_list.json
@@ -0,0 +1,14 @@
+{
+ "clusters": {
+ "type": "array",
+ "items": {
+ "cluster_type": "string",
+ "enabled": "boolean",
+ "environment_scope": "string",
+ "name": "string",
+ "path": "string",
+ "status": "string"
+ }
+ },
+ "has_ancestor_clusters": { "type": ["boolean", "false"] }
+}
diff --git a/spec/frontend/alert_management/components/alert_management_detail_spec.js b/spec/frontend/alert_management/components/alert_management_detail_spec.js
new file mode 100644
index 00000000000..48cdb7027b1
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_detail_spec.js
@@ -0,0 +1,34 @@
+import { shallowMount } from '@vue/test-utils';
+import AlertDetails from '~/alert_management/components/alert_details.vue';
+
+describe('AlertDetails', () => {
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(AlertDetails);
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Alert details', () => {
+ it('renders a tab with overview information', () => {
+ expect(wrapper.find('[data-testid="overviewTab"]').exists()).toBe(true);
+ });
+
+ it('renders a tab with full alert information', () => {
+ expect(wrapper.find('[data-testid="fullDetailsTab"]').exists()).toBe(true);
+ });
+
+ it('renders alert details', () => {
+ expect(wrapper.find('[data-testid="startTimeItem"]').exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 1eec2980f19..ccc29623c31 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -18,6 +18,7 @@ import {
singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
+ propsData,
} from '../mock_data';
import { panelTypes } from '~/monitoring/constants';
@@ -60,6 +61,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
+ settingsPath: propsData.settingsPath,
...props,
},
store,
@@ -239,6 +241,7 @@ describe('Dashboard Panel', () => {
describe('Edit custom metric dropdown item', () => {
const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' });
+ const mockEditPath = '/root/kubernetes-gke-project/prometheus/metrics/23/edit';
beforeEach(() => {
createWrapper();
@@ -257,7 +260,7 @@ describe('Dashboard Panel', () => {
metrics: [
{
...graphData.metrics[0],
- edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit',
+ edit_path: mockEditPath,
},
],
},
@@ -266,10 +269,11 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().exists()).toBe(true);
expect(findEditCustomMetricLink().text()).toBe('Edit metric');
+ expect(findEditCustomMetricLink().attributes('href')).toBe(mockEditPath);
});
});
- it('shows an "Edit metrics" link for a panel with multiple metrics', () => {
+ it('shows an "Edit metrics" link pointing to settingsPath for a panel with multiple metrics', () => {
wrapper.setProps({
graphData: {
...graphData,
@@ -288,6 +292,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
+ expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
});
});
});
@@ -396,6 +401,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
+ settingsPath: propsData.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@@ -445,6 +451,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
+ settingsPath: propsData.settingsPath,
namespace: mockNamespace,
},
store,
@@ -529,12 +536,12 @@ describe('Dashboard Panel', () => {
});
describe.each`
- desc | metricsSavedToDb | propsData | isShown
+ desc | metricsSavedToDb | props | isShown
${'with permission and no metrics in db'} | ${[]} | ${{}} | ${false}
${'with permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{}} | ${true}
${'without permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{ prometheusAlertsAvailable: false }} | ${false}
${'with permission and unrelated metrics in db'} | ${['another_metric_id']} | ${{}} | ${false}
- `('$desc', ({ metricsSavedToDb, isShown, propsData }) => {
+ `('$desc', ({ metricsSavedToDb, isShown, props }) => {
const showsDesc = isShown ? 'shows' : 'does not show';
beforeEach(() => {
@@ -542,7 +549,7 @@ describe('Dashboard Panel', () => {
createWrapper({
alertsEndpoint: '/endpoint',
prometheusAlertsAvailable: true,
- ...propsData,
+ ...props,
});
return wrapper.vm.$nextTick();
});
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
new file mode 100644
index 00000000000..1bd16182d47
--- /dev/null
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import TimeAgo from '~/pipelines/components/time_ago.vue';
+
+describe('Timeago component', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(TimeAgo, {
+ propsData: {
+ ...props,
+ },
+ data() {
+ return {
+ iconTimerSvg: `<svg></svg>`,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('with duration', () => {
+ beforeEach(() => {
+ createComponent({ duration: 10, finishedTime: '' });
+ });
+
+ it('should render duration and timer svg', () => {
+ expect(wrapper.find('.duration').exists()).toBe(true);
+ expect(wrapper.find('.duration svg').exists()).toBe(true);
+ });
+ });
+
+ describe('without duration', () => {
+ beforeEach(() => {
+ createComponent({ duration: 0, finishedTime: '' });
+ });
+
+ it('should not render duration and timer svg', () => {
+ expect(wrapper.find('.duration').exists()).toBe(false);
+ });
+ });
+
+ describe('with finishedTime', () => {
+ beforeEach(() => {
+ createComponent({ duration: 0, finishedTime: '2017-04-26T12:40:23.277Z' });
+ });
+
+ it('should render time and calendar icon', () => {
+ expect(wrapper.find('.finished-at').exists()).toBe(true);
+ expect(wrapper.find('.finished-at i.fa-calendar').exists()).toBe(true);
+ expect(wrapper.find('.finished-at time').exists()).toBe(true);
+ });
+ });
+
+ describe('without finishedTime', () => {
+ beforeEach(() => {
+ createComponent({ duration: 0, finishedTime: '' });
+ });
+
+ it('should not render time and calendar icon', () => {
+ expect(wrapper.find('.finished-at').exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index e7d2a027640..d679768be6c 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -280,11 +280,16 @@ describe ApplicationHelper do
end
context 'when @project is set' do
- it 'includes all possible body data elements and associates the project elements with project' do
- project = create(:project)
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ before do
assign(:project, project)
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+ it 'includes all possible body data elements and associates the project elements with project' do
+ expect(helper).to receive(:can?).with(nil, :download_code, project)
expect(helper.body_data).to eq(
{
page: 'application',
@@ -305,12 +310,11 @@ describe ApplicationHelper do
context 'when params[:id] is present and the issue exsits and action_name is show' do
it 'sets all project and id elements correctly related to the issue' do
- issue = create(:issue)
+ issue = create(:issue, project: project)
stub_controller_method(:action_name, 'show')
stub_controller_method(:params, { id: issue.id })
- assign(:project, issue.project)
-
+ expect(helper).to receive(:can?).with(nil, :download_code, project).and_return(false)
expect(helper.body_data).to eq(
{
page: 'projects:issues:show',
@@ -325,6 +329,15 @@ describe ApplicationHelper do
end
end
end
+
+ context 'when current_user has download_code permission' do
+ it 'returns find_file with the default branch' do
+ allow(helper).to receive(:current_user).and_return(user)
+
+ expect(helper).to receive(:can?).with(user, :download_code, project).and_return(true)
+ expect(helper.body_data[:find_file]).to end_with(project.default_branch)
+ end
+ end
end
def stub_controller_method(method_name, value)
diff --git a/spec/javascripts/pipelines/time_ago_spec.js b/spec/javascripts/pipelines/time_ago_spec.js
deleted file mode 100644
index 42b34c82f89..00000000000
--- a/spec/javascripts/pipelines/time_ago_spec.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import Vue from 'vue';
-import timeAgo from '~/pipelines/components/time_ago.vue';
-
-describe('Timeago component', () => {
- let TimeAgo;
- beforeEach(() => {
- TimeAgo = Vue.extend(timeAgo);
- });
-
- describe('with duration', () => {
- it('should render duration and timer svg', () => {
- const component = new TimeAgo({
- propsData: {
- duration: 10,
- finishedTime: '',
- },
- }).$mount();
-
- expect(component.$el.querySelector('.duration')).toBeDefined();
- expect(component.$el.querySelector('.duration svg')).toBeDefined();
- });
- });
-
- describe('without duration', () => {
- it('should not render duration and timer svg', () => {
- const component = new TimeAgo({
- propsData: {
- duration: 0,
- finishedTime: '',
- },
- }).$mount();
-
- expect(component.$el.querySelector('.duration')).toBe(null);
- });
- });
-
- describe('with finishedTime', () => {
- it('should render time and calendar icon', () => {
- const component = new TimeAgo({
- propsData: {
- duration: 0,
- finishedTime: '2017-04-26T12:40:23.277Z',
- },
- }).$mount();
-
- expect(component.$el.querySelector('.finished-at')).toBeDefined();
- expect(component.$el.querySelector('.finished-at i.fa-calendar')).toBeDefined();
- expect(component.$el.querySelector('.finished-at time')).toBeDefined();
- });
- });
-
- describe('without finishedTime', () => {
- it('should not render time and calendar icon', () => {
- const component = new TimeAgo({
- propsData: {
- duration: 0,
- finishedTime: '',
- },
- }).$mount();
-
- expect(component.$el.querySelector('.finished-at')).toBe(null);
- });
- });
-});
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index ec153e25d44..c99281ee12c 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -7,11 +7,11 @@ require 'tempfile'
describe Gitlab::Middleware::Multipart do
include_context 'multipart middleware context'
- shared_examples_for 'multipart upload files' do
+ RSpec.shared_examples_for 'multipart upload files' do
it 'opens top-level files' do
Tempfile.open('top-level') do |tempfile|
rewritten = { 'file' => tempfile.path }
- in_params = { 'file.name' => original_filename, 'file.path' => tempfile.path, 'file.remote_id' => remote_id }
+ in_params = { 'file.name' => original_filename, 'file.path' => file_path, 'file.remote_id' => remote_id, 'file.size' => file_size }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(file))
@@ -22,8 +22,8 @@ describe Gitlab::Middleware::Multipart do
it 'opens files one level deep' do
Tempfile.open('one-level') do |tempfile|
- in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } }
rewritten = { 'user[avatar]' => tempfile.path }
+ in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => file_path, '.remote_id' => remote_id, '.size' => file_size } } }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(user avatar))
@@ -34,7 +34,7 @@ describe Gitlab::Middleware::Multipart do
it 'opens files two levels deep' do
Tempfile.open('two-levels') do |tempfile|
- in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } }
+ in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => file_path, '.remote_id' => remote_id, '.size' => file_size } } } }
rewritten = { 'project[milestone][themesong]' => tempfile.path }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
@@ -44,13 +44,61 @@ describe Gitlab::Middleware::Multipart do
end
end
- def expect_uploaded_file(tempfile, path, remote: false)
+ def expect_uploaded_file(tempfile, path)
expect(app).to receive(:call) do |env|
file = get_params(env).dig(*path)
expect(file).to be_a(::UploadedFile)
- expect(file.path).to eq(tempfile.path)
expect(file.original_filename).to eq(original_filename)
- expect(file.remote_id).to eq(remote_id)
+
+ if remote_id
+ expect(file.remote_id).to eq(remote_id)
+ expect(file.path).to be_nil
+ else
+ expect(file.path).to eq(File.realpath(tempfile.path))
+ expect(file.remote_id).to be_nil
+ end
+ end
+ end
+ end
+
+ RSpec.shared_examples_for 'handling CI artifact upload' do
+ it 'uploads both file and metadata' do
+ Tempfile.open('file') do |file|
+ Tempfile.open('metadata') do |metadata|
+ rewritten = { 'file' => file.path, 'metadata' => metadata.path }
+ in_params = { 'file.name' => 'file.txt', 'file.path' => file_path, 'file.remote_id' => file_remote_id, 'file.size' => file_size, 'metadata.name' => 'metadata.gz' }
+ env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
+
+ with_expected_uploaded_artifact_files(file, metadata) do |uploaded_file, uploaded_metadata|
+ expect(uploaded_file).to be_a(::UploadedFile)
+ expect(uploaded_file.original_filename).to eq('file.txt')
+
+ if file_remote_id
+ expect(uploaded_file.remote_id).to eq(file_remote_id)
+ expect(uploaded_file.size).to eq(file_size)
+ expect(uploaded_file.path).to be_nil
+ else
+ expect(uploaded_file.path).to eq(File.realpath(file.path))
+ expect(uploaded_file.remote_id).to be_nil
+ end
+
+ expect(uploaded_metadata).to be_a(::UploadedFile)
+ expect(uploaded_metadata.original_filename).to eq('metadata.gz')
+ expect(uploaded_metadata.path).to eq(File.realpath(metadata.path))
+ expect(uploaded_metadata.remote_id).to be_nil
+ end
+
+ middleware.call(env)
+ end
+ end
+ end
+
+ def with_expected_uploaded_artifact_files(file, metadata)
+ expect(app).to receive(:call) do |env|
+ file = get_params(env).dig('file')
+ metadata = get_params(env).dig('metadata')
+
+ yield file, metadata
end
end
end
@@ -67,18 +115,65 @@ describe Gitlab::Middleware::Multipart do
expect { middleware.call(env) }.to raise_error(JWT::InvalidIssuerError)
end
+ context 'with invalid rewritten field' do
+ invalid_field_names = [
+ '[file]',
+ ';file',
+ 'file]',
+ ';file]',
+ 'file]]',
+ 'file;;'
+ ]
+
+ invalid_field_names.each do |invalid_field_name|
+ it "rejects invalid rewritten field name #{invalid_field_name}" do
+ env = post_env({ invalid_field_name => nil }, {}, Gitlab::Workhorse.secret, 'gitlab-workhorse')
+
+ expect { middleware.call(env) }.to raise_error(RuntimeError, "invalid field: \"#{invalid_field_name}\"")
+ end
+ end
+ end
+
context 'with remote file' do
let(:remote_id) { 'someid' }
+ let(:file_size) { 300 }
+ let(:file_path) { '' }
+
+ it_behaves_like 'multipart upload files'
+ end
+
+ context 'with remote file and a file path set' do
+ let(:remote_id) { 'someid' }
+ let(:file_size) { 300 }
+ let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'multipart upload files'
end
context 'with local file' do
let(:remote_id) { nil }
+ let(:file_size) { nil }
+ let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'multipart upload files'
end
+ context 'with remote CI artifact upload' do
+ let(:file_remote_id) { 'someid' }
+ let(:file_size) { 300 }
+ let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
+
+ it_behaves_like 'handling CI artifact upload'
+ end
+
+ context 'with local CI artifact upload' do
+ let(:file_remote_id) { nil }
+ let(:file_size) { nil }
+ let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
+
+ it_behaves_like 'handling CI artifact upload'
+ end
+
it 'allows files in uploads/tmp directory' do
with_tmp_dir('public/uploads/tmp') do |dir, env|
expect(app).to receive(:call) do |env|
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index 25536c07dd9..39055a2479f 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
describe UploadedFile do
let(:temp_dir) { Dir.tmpdir }
- let(:temp_file) { Tempfile.new("test", temp_dir) }
+ let(:temp_file) { Tempfile.new(%w[test test], temp_dir) }
before do
FileUtils.touch(temp_file)
@@ -16,13 +16,14 @@ describe UploadedFile do
describe ".from_params" do
let(:upload_path) { nil }
+ let(:file_path_override) { nil }
after do
FileUtils.rm_r(upload_path) if upload_path
end
subject do
- described_class.from_params(params, :file, upload_path)
+ described_class.from_params(params, :file, upload_path, file_path_override)
end
context 'when valid file is specified' do
@@ -31,9 +32,7 @@ describe UploadedFile do
{ 'file.path' => temp_file.path }
end
- it "succeeds" do
- is_expected.not_to be_nil
- end
+ it { is_expected.not_to be_nil }
it "generates filename from path" do
expect(subject.original_filename).to eq(::File.basename(temp_file.path))
@@ -41,33 +40,153 @@ describe UploadedFile do
end
context 'all parameters are specified' do
- let(:params) do
- { 'file.path' => temp_file.path,
- 'file.name' => 'dir/my file&.txt',
- 'file.type' => 'my/type',
- 'file.sha256' => 'sha256',
- 'file.remote_id' => 'remote_id' }
+ RSpec.shared_context 'filepath override' do
+ let(:temp_file_override) { Tempfile.new(%w[override override], temp_dir) }
+ let(:file_path_override) { temp_file_override.path }
+
+ before do
+ FileUtils.touch(temp_file_override)
+ end
+
+ after do
+ FileUtils.rm_f(temp_file_override)
+ end
end
- it "succeeds" do
- is_expected.not_to be_nil
+ RSpec.shared_examples 'using the file path' do |filename:, content_type:, sha256:, path_suffix:|
+ it 'sets properly the attributes' do
+ expect(subject.original_filename).to eq(filename)
+ expect(subject.content_type).to eq(content_type)
+ expect(subject.sha256).to eq(sha256)
+ expect(subject.remote_id).to be_nil
+ expect(subject.path).to end_with(path_suffix)
+ end
+
+ it 'handles a blank path' do
+ params['file.path'] = ''
+
+ # Not a real file, so can't determine size itself
+ params['file.size'] = 1.byte
+
+ expect { described_class.from_params(params, :file, upload_path) }
+ .not_to raise_error
+ end
end
- it "generates filename from path" do
- expect(subject.original_filename).to eq('my_file_.txt')
- expect(subject.content_type).to eq('my/type')
- expect(subject.sha256).to eq('sha256')
- expect(subject.remote_id).to eq('remote_id')
+ RSpec.shared_examples 'using the remote id' do |filename:, content_type:, sha256:, size:, remote_id:|
+ it 'sets properly the attributes' do
+ expect(subject.original_filename).to eq(filename)
+ expect(subject.content_type).to eq('application/octet-stream')
+ expect(subject.sha256).to eq('sha256')
+ expect(subject.path).to be_nil
+ expect(subject.size).to eq(123456)
+ expect(subject.remote_id).to eq('1234567890')
+ end
+ end
+
+ context 'with a filepath' do
+ let(:params) do
+ { 'file.path' => temp_file.path,
+ 'file.name' => 'dir/my file&.txt',
+ 'file.type' => 'my/type',
+ 'file.sha256' => 'sha256' }
+ end
+
+ it { is_expected.not_to be_nil }
+
+ it_behaves_like 'using the file path',
+ filename: 'my_file_.txt',
+ content_type: 'my/type',
+ sha256: 'sha256',
+ path_suffix: 'test'
+ end
+
+ context 'with a filepath override' do
+ include_context 'filepath override'
+
+ let(:params) do
+ { 'file.path' => temp_file.path,
+ 'file.name' => 'dir/my file&.txt',
+ 'file.type' => 'my/type',
+ 'file.sha256' => 'sha256' }
+ end
+
+ it { is_expected.not_to be_nil }
+
+ it_behaves_like 'using the file path',
+ filename: 'my_file_.txt',
+ content_type: 'my/type',
+ sha256: 'sha256',
+ path_suffix: 'override'
end
- it 'handles a blank path' do
- params['file.path'] = ''
+ context 'with a remote id' do
+ let(:params) do
+ {
+ 'file.name' => 'dir/my file&.txt',
+ 'file.sha256' => 'sha256',
+ 'file.remote_url' => 'http://localhost/file',
+ 'file.remote_id' => '1234567890',
+ 'file.etag' => 'etag1234567890',
+ 'file.size' => '123456'
+ }
+ end
+
+ it { is_expected.not_to be_nil }
+
+ it_behaves_like 'using the remote id',
+ filename: 'my_file_.txt',
+ content_type: 'application/octet-stream',
+ sha256: 'sha256',
+ size: 123456,
+ remote_id: '1234567890'
+ end
- # Not a real file, so can't determine size itself
- params['file.size'] = 1.byte
+ context 'with a path and a remote id' do
+ let(:params) do
+ {
+ 'file.path' => temp_file.path,
+ 'file.name' => 'dir/my file&.txt',
+ 'file.sha256' => 'sha256',
+ 'file.remote_url' => 'http://localhost/file',
+ 'file.remote_id' => '1234567890',
+ 'file.etag' => 'etag1234567890',
+ 'file.size' => '123456'
+ }
+ end
+
+ it { is_expected.not_to be_nil }
+
+ it_behaves_like 'using the remote id',
+ filename: 'my_file_.txt',
+ content_type: 'application/octet-stream',
+ sha256: 'sha256',
+ size: 123456,
+ remote_id: '1234567890'
+ end
- expect { described_class.from_params(params, :file, upload_path) }
- .not_to raise_error
+ context 'with a path override and a remote id' do
+ include_context 'filepath override'
+
+ let(:params) do
+ {
+ 'file.name' => 'dir/my file&.txt',
+ 'file.sha256' => 'sha256',
+ 'file.remote_url' => 'http://localhost/file',
+ 'file.remote_id' => '1234567890',
+ 'file.etag' => 'etag1234567890',
+ 'file.size' => '123456'
+ }
+ end
+
+ it { is_expected.not_to be_nil }
+
+ it_behaves_like 'using the remote id',
+ filename: 'my_file_.txt',
+ content_type: 'application/octet-stream',
+ sha256: 'sha256',
+ size: 123456,
+ remote_id: '1234567890'
end
end
end
diff --git a/spec/serializers/cluster_entity_spec.rb b/spec/serializers/cluster_entity_spec.rb
index e3826a7221d..16247eef655 100644
--- a/spec/serializers/cluster_entity_spec.rb
+++ b/spec/serializers/cluster_entity_spec.rb
@@ -7,7 +7,7 @@ describe ClusterEntity do
subject { described_class.new(cluster).as_json }
context 'when provider type is gcp' do
- let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
+ let(:cluster) { create(:cluster, :instance, provider_type: :gcp, provider_gcp: provider) }
context 'when status is creating' do
let(:provider) { create(:cluster_provider_gcp, :creating) }
@@ -29,7 +29,7 @@ describe ClusterEntity do
end
context 'when provider type is user' do
- let(:cluster) { create(:cluster, provider_type: :user) }
+ let(:cluster) { create(:cluster, :instance, provider_type: :user) }
it 'has corresponded data' do
expect(subject[:status]).to eq(:created)
@@ -38,7 +38,7 @@ describe ClusterEntity do
end
context 'when no application has been installed' do
- let(:cluster) { create(:cluster) }
+ let(:cluster) { create(:cluster, :instance) }
subject { described_class.new(cluster).as_json[:applications]}
diff --git a/spec/serializers/remote_mirror_entity_spec.rb b/spec/serializers/remote_mirror_entity_spec.rb
index 5f4aac213be..27472c46436 100644
--- a/spec/serializers/remote_mirror_entity_spec.rb
+++ b/spec/serializers/remote_mirror_entity_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe RemoteMirrorEntity do
- let(:project) { create(:project, :repository, :remote_mirror) }
+ let(:project) { create(:project, :repository, :remote_mirror, url: "https://test:password@gitlab.com") }
let(:remote_mirror) { project.remote_mirrors.first }
let(:entity) { described_class.new(remote_mirror) }
@@ -15,4 +15,9 @@ describe RemoteMirrorEntity do
:ssh_known_hosts, :ssh_public_key, :ssh_known_hosts_fingerprints
)
end
+
+ it 'does not expose password information' do
+ expect(subject[:url]).not_to include('password')
+ expect(subject[:url]).to eq(remote_mirror.safe_url)
+ end
end