Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
adafb996ef
commit
3aeda4e614
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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', {});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import AlertDetails from '~/alert_management/details';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
AlertDetails('#js-alert_details');
|
||||
});
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class RemoteMirrorEntity < Grape::Entity
|
||||
expose :id
|
||||
expose :url
|
||||
expose :safe_url, as: :url
|
||||
expose :enabled
|
||||
|
||||
expose :auth_method
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
- page_title _('Alert Details')
|
||||
|
||||
#js-alert_details
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Multiple metrics edit navigates to prom edit page
|
||||
merge_request: 30666
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -131,6 +131,7 @@ module Gitlab
|
|||
encrypted_key
|
||||
hook
|
||||
import_url
|
||||
elasticsearch_url
|
||||
otp_attempt
|
||||
sentry_dsn
|
||||
trace
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
|
|
@ -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.
|
||||
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.
|
||||
|
||||
## Overview
|
||||
To better understand GitLab's Jenkins integration, watch the following videos:
|
||||
|
||||
[Jenkins](https://jenkins.io/) is a great Continuous Integration tool, similar to our built-in
|
||||
[GitLab CI/CD](../ci/README.md).
|
||||
- [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)
|
||||
|
||||
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.
|
||||
Use the Jenkins integration with GitLab when:
|
||||
|
||||
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.
|
||||
- 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 project](#configure-the-jenkins-project)
|
||||
|
||||
## Grant Jenkins access to GitLab project
|
||||
|
||||
Grant a GitLab user access to the select GitLab projects.
|
||||
|
||||
1. Create a new GitLab user, or choose an existing GitLab user.
|
||||
|
||||
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 GitLab API access
|
||||
|
||||
Create a personal access token to authorize Jenkins' access to GitLab.
|
||||
|
||||
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 the Jenkins server
|
||||
|
||||
Install [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
|
||||
and [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin).
|
||||
Install and configure the Jenkins plugins. Both plugins must be installed and configured to
|
||||
authorize the connection to GitLab.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
For more information, see GitLab Plugin documentation about
|
||||
[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication)
|
||||
[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication).
|
||||
|
||||

|
||||
|
||||
## Configure a Jenkins project
|
||||
## Configure the Jenkins project
|
||||
|
||||
Follow the GitLab Plugin documentation about [Jenkins Job Configuration](https://github.com/jenkinsci/gitlab-plugin#jenkins-job-configuration).
|
||||
Set up the Jenkins project you’re going to run your build on.
|
||||
|
||||
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. 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:
|
||||
|
||||
## Configure a GitLab project
|
||||
```plaintext
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
Create a new GitLab project or choose an existing one. Then, go to **Integrations ->
|
||||
Jenkins CI**.
|
||||
stages {
|
||||
stage('gitlab') {
|
||||
steps {
|
||||
echo 'Notify GitLab'
|
||||
updateGitlabCommitStatus name: 'build', state: 'pending'
|
||||
updateGitlabCommitStatus name: 'build', state: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
## Configure the GitLab project
|
||||
|
||||
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.
|
||||
Configure the GitLab integration with Jenkins.
|
||||
|
||||
Optionally, enter a username and password if your Jenkins server requires
|
||||
authentication.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
it 'handles a blank path' do
|
||||
params['file.path'] = ''
|
||||
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
|
||||
|
||||
# Not a real file, so can't determine size itself
|
||||
params['file.size'] = 1.byte
|
||||
it { is_expected.not_to be_nil }
|
||||
|
||||
expect { described_class.from_params(params, :file, upload_path) }
|
||||
.not_to raise_error
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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]}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue