Commit 221b5297 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 00a8c64f
<script>
import { DEFAULT_HEADING } from '../constants';
export default {
props: {
title: {
type: String,
required: false,
default: '',
},
},
computed: {
heading() {
return this.title || DEFAULT_HEADING;
},
},
};
</script>
<template>
<div>
<h3 ref="sseHeading">{{ heading }}</h3>
</div>
</template>
...@@ -7,6 +7,11 @@ export default { ...@@ -7,6 +7,11 @@ export default {
GlLoadingIcon, GlLoadingIcon,
}, },
props: { props: {
returnUrl: {
type: String,
required: false,
default: '',
},
saveable: { saveable: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -23,12 +28,17 @@ export default { ...@@ -23,12 +28,17 @@ export default {
<template> <template>
<div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4"> <div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4">
<gl-loading-icon :class="{ invisible: !savingChanges }" size="md" /> <gl-loading-icon :class="{ invisible: !savingChanges }" size="md" />
<gl-new-button <div>
variant="success" <gl-new-button v-if="returnUrl" ref="returnUrlLink" :href="returnUrl">{{
:disabled="!saveable || savingChanges" s__('StaticSiteEditor|Return to site')
@click="$emit('submit')" }}</gl-new-button>
> <gl-new-button
{{ __('Submit Changes') }} variant="success"
</gl-new-button> :disabled="!saveable || savingChanges"
@click="$emit('submit')"
>
{{ __('Submit Changes') }}
</gl-new-button>
</div>
</div> </div>
</template> </template>
...@@ -3,16 +3,25 @@ import { mapState, mapGetters, mapActions } from 'vuex'; ...@@ -3,16 +3,25 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { GlSkeletonLoader } from '@gitlab/ui'; import { GlSkeletonLoader } from '@gitlab/ui';
import EditArea from './edit_area.vue'; import EditArea from './edit_area.vue';
import EditHeader from './edit_header.vue';
import Toolbar from './publish_toolbar.vue'; import Toolbar from './publish_toolbar.vue';
export default { export default {
components: { components: {
EditArea, EditArea,
EditHeader,
GlSkeletonLoader, GlSkeletonLoader,
Toolbar, Toolbar,
}, },
computed: { computed: {
...mapState(['content', 'isLoadingContent', 'isSavingChanges', 'isContentLoaded']), ...mapState([
'content',
'isLoadingContent',
'isSavingChanges',
'isContentLoaded',
'returnUrl',
'title',
]),
...mapGetters(['contentChanged']), ...mapGetters(['contentChanged']),
}, },
mounted() { mounted() {
...@@ -24,7 +33,7 @@ export default { ...@@ -24,7 +33,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="d-flex justify-content-center h-100 pt-2"> <div class="d-flex justify-content-center h-100 pt-2">
<div v-if="isLoadingContent" class="w-50 h-50"> <div v-if="isLoadingContent" class="w-50 h-50">
<gl-skeleton-loader :width="500" :height="102"> <gl-skeleton-loader :width="500" :height="102">
<rect width="500" height="16" rx="4" /> <rect width="500" height="16" rx="4" />
...@@ -36,12 +45,14 @@ export default { ...@@ -36,12 +45,14 @@ export default {
</gl-skeleton-loader> </gl-skeleton-loader>
</div> </div>
<div v-if="isContentLoaded" class="d-flex flex-grow-1 flex-column"> <div v-if="isContentLoaded" class="d-flex flex-grow-1 flex-column">
<edit-header class="w-75 align-self-center py-2" :title="title" />
<edit-area <edit-area
class="w-75 h-100 shadow-none align-self-center" class="w-75 h-100 shadow-none align-self-center"
:value="content" :value="content"
@input="setContent" @input="setContent"
/> />
<toolbar <toolbar
:return-url="returnUrl"
:saveable="contentChanged" :saveable="contentChanged"
:saving-changes="isSavingChanges" :saving-changes="isSavingChanges"
@submit="submitChanges" @submit="submitChanges"
......
...@@ -10,3 +10,5 @@ export const SUBMIT_CHANGES_COMMIT_ERROR = s__( ...@@ -10,3 +10,5 @@ export const SUBMIT_CHANGES_COMMIT_ERROR = s__(
export const SUBMIT_CHANGES_MERGE_REQUEST_ERROR = s__( export const SUBMIT_CHANGES_MERGE_REQUEST_ERROR = s__(
'StaticSiteEditor|Could not create merge request.', 'StaticSiteEditor|Could not create merge request.',
); );
export const DEFAULT_HEADING = s__('StaticSiteEditor|Static site editor');
...@@ -3,10 +3,10 @@ import StaticSiteEditor from './components/static_site_editor.vue'; ...@@ -3,10 +3,10 @@ import StaticSiteEditor from './components/static_site_editor.vue';
import createStore from './store'; import createStore from './store';
const initStaticSiteEditor = el => { const initStaticSiteEditor = el => {
const { projectId, path: sourcePath } = el.dataset; const { projectId, returnUrl, path: sourcePath } = el.dataset;
const store = createStore({ const store = createStore({
initialState: { projectId, sourcePath, username: window.gon.current_username }, initialState: { projectId, returnUrl, sourcePath, username: window.gon.current_username },
}); });
return new Vue({ return new Vue({
......
const createState = (initialState = {}) => ({ const createState = (initialState = {}) => ({
username: null, username: null,
projectId: null, projectId: null,
returnUrl: null,
sourcePath: null, sourcePath: null,
isLoadingContent: false, isLoadingContent: false,
......
<script> <script>
import { __ } from '~/locale';
import { roundOffFloat } from '~/lib/utils/common_utils'; import { roundOffFloat } from '~/lib/utils/common_utils';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -27,6 +28,11 @@ export default { ...@@ -27,6 +28,11 @@ export default {
required: false, required: false,
default: 'neutral', default: 'neutral',
}, },
unavailableLabel: {
type: String,
required: false,
default: __('Not available'),
},
successCount: { successCount: {
type: Number, type: Number,
required: true, required: true,
...@@ -103,7 +109,7 @@ export default { ...@@ -103,7 +109,7 @@ export default {
<template> <template>
<div :class="cssClass" class="stacked-progress-bar"> <div :class="cssClass" class="stacked-progress-bar">
<span v-if="!totalCount" class="status-unavailable"> {{ __('Not available') }} </span> <span v-if="!totalCount" class="status-unavailable">{{ unavailableLabel }}</span>
<span <span
v-if="successPercent" v-if="successPercent"
v-tooltip v-tooltip
......
...@@ -9,7 +9,7 @@ module Groups ...@@ -9,7 +9,7 @@ module Groups
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
@images = group.container_repositories.with_api_entity_associations @images = ContainerRepositoriesFinder.new(user: current_user, subject: group).execute.with_api_entity_associations
track_event(:list_repositories) track_event(:list_repositories)
......
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
class Projects::StaticSiteEditorController < Projects::ApplicationController class Projects::StaticSiteEditorController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesCommit
layout 'fullscreen' layout 'fullscreen'
prepend_before_action :authenticate_user!, only: [:show] prepend_before_action :authenticate_user!, only: [:show]
before_action :assign_ref_and_path, only: [:show] before_action :assign_ref_and_path, only: [:show]
before_action :authorize_edit_tree!, only: [:show]
def show def show
@config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url]) @config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url])
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# active: boolean # active: boolean
# blocked: boolean # blocked: boolean
# external: boolean # external: boolean
# without_projects: boolean
# #
class UsersFinder class UsersFinder
include CreatedAtFilter include CreatedAtFilter
...@@ -36,6 +37,7 @@ class UsersFinder ...@@ -36,6 +37,7 @@ class UsersFinder
users = by_external(users) users = by_external(users)
users = by_2fa(users) users = by_2fa(users)
users = by_created_at(users) users = by_created_at(users)
users = by_without_projects(users)
users = by_custom_attributes(users) users = by_custom_attributes(users)
users users
...@@ -94,6 +96,12 @@ class UsersFinder ...@@ -94,6 +96,12 @@ class UsersFinder
users users
end end
end end
def by_without_projects(users)
return users unless params[:without_projects]
users.without_projects
end
end end
UsersFinder.prepend_if_ee('EE::UsersFinder') UsersFinder.prepend_if_ee('EE::UsersFinder')
---
title: Update aws-ecs image location in CI template
merge_request: 27382
author:
type: changed
---
title: Group level container registry show subgroups repos
merge_request: 29263
author:
type: fixed
---
title: Add API endpoint to get users without projects
merge_request: 29347
author:
type: added
---
title: Add Gitlab User-Agent to ContainerRegistry::Client
merge_request: 29294
author: Sashi Kumar
type: other
...@@ -397,9 +397,9 @@ CREATE TABLE public.application_settings ( ...@@ -397,9 +397,9 @@ CREATE TABLE public.application_settings (
email_restrictions text, email_restrictions text,
npm_package_requests_forwarding boolean DEFAULT true NOT NULL, npm_package_requests_forwarding boolean DEFAULT true NOT NULL,
namespace_storage_size_limit bigint DEFAULT 0 NOT NULL, namespace_storage_size_limit bigint DEFAULT 0 NOT NULL,
issues_create_limit integer DEFAULT 300 NOT NULL,
seat_link_enabled boolean DEFAULT true NOT NULL, seat_link_enabled boolean DEFAULT true NOT NULL,
container_expiration_policies_enable_historic_entries boolean DEFAULT false NOT NULL container_expiration_policies_enable_historic_entries boolean DEFAULT false NOT NULL,
issues_create_limit integer DEFAULT 300 NOT NULL
); );
CREATE SEQUENCE public.application_settings_id_seq CREATE SEQUENCE public.application_settings_id_seq
......
...@@ -353,7 +353,7 @@ you can flip the feature flag from a Rails console. ...@@ -353,7 +353,7 @@ you can flip the feature flag from a Rails console.
```shell ```shell
cd /home/git/gitlab cd /home/git/gitlab
RAILS_ENV=production sudo -u git -H bundle exec rails console sudo -u git -H bundle exec rails console -e production
``` ```
1. Flip the switch and disable it: 1. Flip the switch and disable it:
......
...@@ -106,7 +106,7 @@ gitlab-rails console ...@@ -106,7 +106,7 @@ gitlab-rails console
# Installation from source # Installation from source
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bin/rails console RAILS_ENV=production sudo -u git -H bin/rails console -e production
``` ```
**To check if incremental logging (trace) is enabled:** **To check if incremental logging (trace) is enabled:**
......
...@@ -30,7 +30,7 @@ sudo gitlab-rails console ...@@ -30,7 +30,7 @@ sudo gitlab-rails console
For source installations, you'll have to instead run: For source installations, you'll have to instead run:
```shell ```shell
sudo -u git -H bundle exec rails console RAILS_ENV=production sudo -u git -H bundle exec rails console -e production
``` ```
Further code examples will all take place inside the Rails console and also Further code examples will all take place inside the Rails console and also
......
...@@ -2159,6 +2159,11 @@ type Epic implements Noteable { ...@@ -2159,6 +2159,11 @@ type Epic implements Noteable {
""" """
hasIssues: Boolean! hasIssues: Boolean!
"""
Indicates if the epic has a parent epic
"""
hasParent: Boolean!
""" """
Current health status of the epic Current health status of the epic
""" """
......
...@@ -6336,6 +6336,24 @@ ...@@ -6336,6 +6336,24 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "hasParent",
"description": "Indicates if the epic has a parent epic",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "healthStatus", "name": "healthStatus",
"description": "Current health status of the epic", "description": "Current health status of the epic",
......
...@@ -359,6 +359,7 @@ Represents an epic. ...@@ -359,6 +359,7 @@ Represents an epic.
| `group` | Group! | Group to which the epic belongs | | `group` | Group! | Group to which the epic belongs |
| `hasChildren` | Boolean! | Indicates if the epic has children | | `hasChildren` | Boolean! | Indicates if the epic has children |
| `hasIssues` | Boolean! | Indicates if the epic has direct issues | | `hasIssues` | Boolean! | Indicates if the epic has direct issues |
| `hasParent` | Boolean! | Indicates if the epic has a parent epic |
| `healthStatus` | EpicHealthStatus | Current health status of the epic | | `healthStatus` | EpicHealthStatus | Current health status of the epic |
| `id` | ID! | ID of the epic | | `id` | ID! | ID of the epic |
| `iid` | ID! | Internal ID of the epic | | `iid` | ID! | Internal ID of the epic |
......
...@@ -75,6 +75,7 @@ GET /users ...@@ -75,6 +75,7 @@ GET /users
| `order_by` | string | no | Return users ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` | | `order_by` | string | no | Return users ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` |
| `sort` | string | no | Return users sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return users sorted in `asc` or `desc` order. Default is `desc` |
| `two_factor` | string | no | Filter users by Two-factor authentication. Filter values are `enabled` or `disabled`. By default it returns all users | | `two_factor` | string | no | Filter users by Two-factor authentication. Filter values are `enabled` or `disabled`. By default it returns all users |
| `without_projects` | boolean | no | Filter users without projects. Default is `false` |
```json ```json
[ [
...@@ -207,6 +208,8 @@ You can search users by creation date time range with: ...@@ -207,6 +208,8 @@ You can search users by creation date time range with:
GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060 GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060
``` ```
You can search for users without projects with: `/users?without_projects=true`
You can filter by [custom attributes](custom_attributes.md) with: You can filter by [custom attributes](custom_attributes.md) with:
```plaintext ```plaintext
......
...@@ -39,7 +39,7 @@ Some credentials are required to be able to run `aws` commands: ...@@ -39,7 +39,7 @@ Some credentials are required to be able to run `aws` commands:
```yml ```yml
deploy: deploy:
stage: deploy stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy:latest # see the note below image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest # see the note below
script: script:
- aws s3 ... - aws s3 ...
- aws create-deployment ... - aws create-deployment ...
...@@ -47,7 +47,7 @@ Some credentials are required to be able to run `aws` commands: ...@@ -47,7 +47,7 @@ Some credentials are required to be able to run `aws` commands:
NOTE: **Note:** NOTE: **Note:**
Please note that the image used in the example above Please note that the image used in the example above
(`registry.gitlab.com/gitlab-org/cloud-deploy:latest`) is hosted on the [GitLab (`registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest`) is hosted on the [GitLab
Container Registry](../../user/packages/container_registry/index.md) and is Container Registry](../../user/packages/container_registry/index.md) and is
ready to use. Alternatively, replace the image with another one hosted on [AWS ECR](#aws-ecr). ready to use. Alternatively, replace the image with another one hosted on [AWS ECR](#aws-ecr).
...@@ -119,3 +119,15 @@ After you're all set up on AWS ECS, follow these steps: ...@@ -119,3 +119,15 @@ After you're all set up on AWS ECS, follow these steps:
Finally, your AWS ECS service will be updated with the new revision of the Finally, your AWS ECS service will be updated with the new revision of the
task definition, making the cluster pull the newest version of your task definition, making the cluster pull the newest version of your
application. application.
Alternatively, if you don't wish to use the `Deploy-ECS.gitlab-ci.yml` template
to deploy to AWS ECS, you can always use our
`aws-base` Docker image to run your own [AWS CLI commands for ECS](https://docs.aws.amazon.com/cli/latest/reference/ecs/index.html#cli-aws-ecs).
```yaml
deploy:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
script:
- aws ecs register-task-definition ...
```
...@@ -41,8 +41,8 @@ Access the database via one of these commands (they all get you to the same plac ...@@ -41,8 +41,8 @@ Access the database via one of these commands (they all get you to the same plac
```ruby ```ruby
gdk psql -d gitlabhq_development gdk psql -d gitlabhq_development
bundle exec rails dbconsole RAILS_ENV=development bundle exec rails dbconsole -e development
bundle exec rails db RAILS_ENV=development bundle exec rails db -e development
``` ```
- `\q`: Quit/exit - `\q`: Quit/exit
......
...@@ -78,7 +78,7 @@ The last option is to import a project using a Rails console: ...@@ -78,7 +78,7 @@ The last option is to import a project using a Rails console:
gitlab-rails console gitlab-rails console
# For installations from source # For installations from source
sudo -u git -H bundle exec rails console RAILS_ENV=production sudo -u git -H bundle exec rails console -e production
``` ```
1. Create a project and run `Project::TreeRestorer`: 1. Create a project and run `Project::TreeRestorer`:
......
...@@ -136,28 +136,18 @@ browser performance testing using a ...@@ -136,28 +136,18 @@ browser performance testing using a
### Node pools ### Node pools
Both `review-apps-ce` and `review-apps-ee` clusters are currently set up with The `review-apps-ee` and `review-apps-ce` clusters are currently set up with
two node pools: the following node pools:
- a node pool of non-preemptible `n1-standard-2` (2 vCPU, 7.5 GB memory) nodes - `review-apps-ee` of preemptible `e2-highcpu-16` (16 vCPU, 16 GB memory) nodes with autoscaling
dedicated to the `tiller` deployment (see below) with a single node. - `review-apps-ce` of preemptible `n1-standard-8` (8 vCPU, 16 GB memory) nodes with autoscaling
- a node pool of preemptible `n1-standard-2` (2 vCPU, 7.5 GB memory) nodes,
with a minimum of 1 node and a maximum of 250 nodes.
### Helm/Tiller ### Helm
The Helm/Tiller version used is defined in the The Helm version used is defined in the
[`registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base` image](https://gitlab.com/gitlab-org/gitlab-build-images/blob/master/Dockerfile.gitlab-charts-build-base#L4) [`registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14` image](https://gitlab.com/gitlab-org/gitlab-build-images/-/blob/master/Dockerfile.gitlab-helm3-kubectl1.14#L7)
used by the `review-deploy` and `review-stop` jobs. used by the `review-deploy` and `review-stop` jobs.
The `tiller` deployment (the Helm server) is deployed to a dedicated node pool
that has the `app=helm` label and a specific
[taint](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/)
to prevent other pods from being scheduled on this node pool.
This is to ensure Tiller isn't affected by "noisy" neighbors that could put
their node under pressure.
## How to ## How to
### Get access to the GCP Review Apps cluster ### Get access to the GCP Review Apps cluster
...@@ -241,7 +231,7 @@ due to Helm or Kubernetes trying to recreate the components. ...@@ -241,7 +231,7 @@ due to Helm or Kubernetes trying to recreate the components.
**Where to look for further debugging:** **Where to look for further debugging:**
Look at a recent `review-deploy` job log, and at the Tiller logs. Look at a recent `review-deploy` job log.
**Useful commands:** **Useful commands:**
......
...@@ -42,7 +42,7 @@ gitlab-rails console ...@@ -42,7 +42,7 @@ gitlab-rails console
# Installation from source # Installation from source
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bin/rails console RAILS_ENV=production sudo -u git -H bin/rails console -e production
``` ```
Then run the following command to enable the feature flag: Then run the following command to enable the feature flag:
......
...@@ -947,7 +947,7 @@ backup beforehand. ...@@ -947,7 +947,7 @@ backup beforehand.
For installations from source: For installations from source:
```shell ```shell
sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production sudo -u git -H bundle exec rails dbconsole -e production
``` ```
1. Check the `ci_group_variables` and `ci_variables` tables: 1. Check the `ci_group_variables` and `ci_variables` tables:
...@@ -982,7 +982,7 @@ backup beforehand. ...@@ -982,7 +982,7 @@ backup beforehand.
For installations from source: For installations from source:
```shell ```shell
sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production sudo -u git -H bundle exec rails dbconsole -e production
``` ```
1. Clear all the tokens for projects, groups, and the whole instance: 1. Clear all the tokens for projects, groups, and the whole instance:
...@@ -1015,7 +1015,7 @@ backup beforehand. ...@@ -1015,7 +1015,7 @@ backup beforehand.
For installations from source: For installations from source:
```shell ```shell
sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production sudo -u git -H bundle exec rails dbconsole -e production
``` ```
1. Clear all the tokens for pending jobs: 1. Clear all the tokens for pending jobs:
......
...@@ -16,7 +16,7 @@ To unlock a locked user: ...@@ -16,7 +16,7 @@ To unlock a locked user:
sudo gitlab-rails console -e production sudo gitlab-rails console -e production
## For installations from source ## For installations from source
sudo -u git -H bundle exec rails console RAILS_ENV=production sudo -u git -H bundle exec rails console -e production
``` ```
1. Find the user to unlock. You can search by email or ID. 1. Find the user to unlock. You can search by email or ID.
......
...@@ -277,7 +277,7 @@ gitlab-rails console ...@@ -277,7 +277,7 @@ gitlab-rails console
# Installation from source # Installation from source
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bin/rails console RAILS_ENV=production sudo -u git -H bin/rails console -e production
``` ```
Then run `Feature.enable(:approval_rules)` to enable the updated interface. Then run `Feature.enable(:approval_rules)` to enable the updated interface.
......
...@@ -82,6 +82,7 @@ module API ...@@ -82,6 +82,7 @@ module API
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
optional :created_after, type: DateTime, desc: 'Return users created after the specified time' optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
optional :created_before, type: DateTime, desc: 'Return users created before the specified time' optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
optional :without_projects, type: Boolean, default: false, desc: 'Filters only users without projects'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
use :sort_params use :sort_params
...@@ -94,7 +95,7 @@ module API ...@@ -94,7 +95,7 @@ module API
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin? unless current_user&.admin?
params.except!(:created_after, :created_before, :order_by, :sort, :two_factor) params.except!(:created_after, :created_before, :order_by, :sort, :two_factor, :without_projects)
end end
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
......
...@@ -159,13 +159,13 @@ module ContainerRegistry ...@@ -159,13 +159,13 @@ module ContainerRegistry
end end
def faraday def faraday
@faraday ||= Faraday.new(@base_uri) do |conn| @faraday ||= faraday_base do |conn|
initialize_connection(conn, @options, &method(:accept_manifest)) initialize_connection(conn, @options, &method(:accept_manifest))
end end
end end
def faraday_blob def faraday_blob
@faraday_blob ||= Faraday.new(@base_uri) do |conn| @faraday_blob ||= faraday_base do |conn|
initialize_connection(conn, @options) initialize_connection(conn, @options)
end end
end end
...@@ -173,12 +173,16 @@ module ContainerRegistry ...@@ -173,12 +173,16 @@ module ContainerRegistry
# Create a new request to make sure the Authorization header is not inserted # Create a new request to make sure the Authorization header is not inserted
# via the Faraday middleware # via the Faraday middleware
def faraday_redirect def faraday_redirect
@faraday_redirect ||= Faraday.new(@base_uri) do |conn| @faraday_redirect ||= faraday_base do |conn|
conn.request :json conn.request :json
conn.adapter :net_http conn.adapter :net_http
end end
end end
def faraday_base(&block)
Faraday.new(@base_uri, headers: { user_agent: "GitLab/#{Gitlab::VERSION}" }, &block)
end
def delete_if_exists(path) def delete_if_exists(path)
result = faraday.delete(path) result = faraday.delete(path)
......
...@@ -9,7 +9,7 @@ include: ...@@ -9,7 +9,7 @@ include:
- template: Jobs/Build.gitlab-ci.yml - template: Jobs/Build.gitlab-ci.yml
.deploy_to_ecs: .deploy_to_ecs:
image: registry.gitlab.com/gitlab-org/cloud-deploy:latest image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest
script: script:
- ecs update-task-definition - ecs update-task-definition
......
...@@ -3,33 +3,49 @@ ...@@ -3,33 +3,49 @@
module Gitlab module Gitlab
module StaticSiteEditor module StaticSiteEditor
class Config class Config
SUPPORTED_EXTENSIONS = %w[.md].freeze
def initialize(repository, ref, file_path, return_url) def initialize(repository, ref, file_path, return_url)
@repository = repository @repository = repository
@ref = ref @ref = ref
@file_path = file_path @file_path = file_path
@return_url = return_url @return_url = return_url
@commit_id = repository.commit(ref)&.id if ref
end end
def payload def payload
{ {
branch: ref, branch: ref,
path: file_path, path: file_path,
commit: commit.id, commit_id: commit_id,
project_id: project.id, project_id: project.id,
project: project.path, project: project.path,
namespace: project.namespace.path, namespace: project.namespace.path,
return_url: return_url return_url: return_url,
is_supported_content: supported_content?
} }
end end
private private
attr_reader :repository, :ref, :file_path, :return_url attr_reader :repository, :ref, :file_path, :return_url, :commit_id
delegate :project, to: :repository delegate :project, to: :repository
def commit def supported_content?
repository.commit(ref) master_branch? && extension_supported? && file_exists?
end
def master_branch?
ref == 'master'
end
def extension_supported?
File.extname(file_path).in?(SUPPORTED_EXTENSIONS)
end
def file_exists?
commit_id.present? && repository.blob_at(commit_id, file_path).present?
end end
end end
end end
......
...@@ -11891,6 +11891,9 @@ msgstr "" ...@@ -11891,6 +11891,9 @@ msgstr ""
msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab." msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
msgstr "" msgstr ""
msgid "Learn how to enable synchronization"
msgstr ""
msgid "Learn more" msgid "Learn more"
msgstr "" msgstr ""
...@@ -13827,6 +13830,9 @@ msgstr "" ...@@ -13827,6 +13830,9 @@ msgstr ""
msgid "Nothing to preview." msgid "Nothing to preview."
msgstr "" msgstr ""
msgid "Nothing to synchronize"
msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""
...@@ -19452,6 +19458,9 @@ msgstr "" ...@@ -19452,6 +19458,9 @@ msgstr ""
msgid "StaticSiteEditor|Return to site" msgid "StaticSiteEditor|Return to site"
msgstr "" msgstr ""
msgid "StaticSiteEditor|Static site editor"
msgstr ""
msgid "StaticSiteEditor|Success!" msgid "StaticSiteEditor|Success!"
msgstr "" msgstr ""
...@@ -19866,6 +19875,12 @@ msgstr "" ...@@ -19866,6 +19875,12 @@ msgstr ""
msgid "Synced" msgid "Synced"
msgstr "" msgstr ""
msgid "Synchronization disabled"
msgstr ""
msgid "Synchronization of container repositories is disabled."
msgstr ""
msgid "System" msgid "System"
msgstr "" msgstr ""
......
...@@ -7,6 +7,13 @@ describe Groups::Registry::RepositoriesController do ...@@ -7,6 +7,13 @@ describe Groups::Registry::RepositoriesController do
let_it_be(:guest) { create(:user) } let_it_be(:guest) { create(:user) }
let_it_be(:group, reload: true) { create(:group) } let_it_be(:group, reload: true) { create(:group) }
subject do
get :index, params: {
group_id: group,
format: format
}
end
before do before do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
group.add_owner(user) group.add_owner(user)
...@@ -15,51 +22,67 @@ describe Groups::Registry::RepositoriesController do ...@@ -15,51 +22,67 @@ describe Groups::Registry::RepositoriesController do
end end
shared_examples 'renders a list of repositories' do shared_examples 'renders a list of repositories' do
let_it_be(:repo) { create_project_with_repo(test_group) }
it 'returns a list of projects for json format' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_kind_of(Array)
expect(json_response.first).to include(
'id' => repo.id,
'name' => repo.name
)
end
end
shared_examples 'renders correctly' do
context 'when user has access to registry' do context 'when user has access to registry' do
it 'show index page' do let_it_be(:test_group) { group }
expect(Gitlab::Tracking).not_to receive(:event)
get :index, params: { context 'html format' do
group_id: group let(:format) { :html }
}
expect(response).to have_gitlab_http_status(:ok) it 'show index page' do
end expect(Gitlab::Tracking).not_to receive(:event)
it 'has the correct response schema' do subject
get :index, params: {
group_id: group,
format: :json
}
expect(response).to match_response_schema('registry/repositories') expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers end
end end
it 'returns a list of projects for json format' do context 'json format' do
project = create(:project, group: group) let(:format) { :json }
repo = create(:container_repository, project: project)
it 'has the correct response schema' do
get :index, params: { subject
group_id: group,
format: :json expect(response).to match_response_schema('registry/repositories')
} expect(response).to include_pagination_headers
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_kind_of(Array)
expect(json_response.first).to include(
'id' => repo.id,
'name' => repo.name
)
end
it 'tracks the event' do it_behaves_like 'renders a list of repositories'
expect(Gitlab::Tracking).to receive(:event).with(anything, 'list_repositories', {})
get :index, params: { it_behaves_like 'a gitlab tracking event', described_class.name, 'list_repositories'
group_id: group,
format: :json context 'with project in subgroup' do
} let_it_be(:test_group) { create(:group, parent: group ) }
it_behaves_like 'renders a list of repositories'
context 'with project in subgroup and group' do
let_it_be(:repo_in_test_group) { create_project_with_repo(test_group) }
let_it_be(:repo_in_group) { create_project_with_repo(group) }
it 'returns all the projects' do
subject
expect(json_response).to be_kind_of(Array)
expect(json_response.length).to eq 2
end
end
end
end end
end end
...@@ -69,20 +92,30 @@ describe Groups::Registry::RepositoriesController do ...@@ -69,20 +92,30 @@ describe Groups::Registry::RepositoriesController do
sign_in(guest) sign_in(guest)
end end
it 'renders not found' do context 'json format' do
get :index, params: { let(:format) { :json }
group_id: group
} it_behaves_like 'returning response status', :not_found
expect(response).to have_gitlab_http_status(:not_found) end
context 'html format' do
let(:format) { :html }
it_behaves_like 'returning response status', :not_found
end end
end end
end end
context 'GET #index' do context 'GET #index' do
it_behaves_like 'renders a list of repositories' it_behaves_like 'renders correctly'
end end
context 'GET #show' do context 'GET #show' do
it_behaves_like 'renders a list of repositories' it_behaves_like 'renders correctly'
end
def create_project_with_repo(group)
project = create(:project, group: test_group)
create(:container_repository, project: project)
end end
end end
...@@ -26,7 +26,21 @@ describe Projects::StaticSiteEditorController do ...@@ -26,7 +26,21 @@ describe Projects::StaticSiteEditorController do
end end
end end
%w[guest developer maintainer].each do |role| context 'as guest' do
let(:user) { create(:user) }
before do
project.add_guest(user)
sign_in(user)
get :show, params: default_params
end
it 'responds with 404 page' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
%w[developer maintainer].each do |role|
context "as #{role}" do context "as #{role}" do
let(:user) { create(:user) } let(:user) { create(:user) }
......
import { shallowMount } from '@vue/test-utils';
import EditHeader from '~/static_site_editor/components/edit_header.vue';
import { DEFAULT_HEADING } from '~/static_site_editor/constants';
import { sourceContentTitle } from '../mock_data';
describe('~/static_site_editor/components/edit_header.vue', () => {
let wrapper;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(EditHeader, {
propsData: {
...propsData,
},
});
};
const findHeading = () => wrapper.find({ ref: 'sseHeading' });
beforeEach(() => {
buildWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the default heading if there is no title prop', () => {
expect(findHeading().text()).toBe(DEFAULT_HEADING);
});
it('renders the title prop value in the heading', () => {
buildWrapper({ title: sourceContentTitle });
expect(findHeading().text()).toBe(sourceContentTitle);
});
});
...@@ -3,6 +3,8 @@ import { GlNewButton, GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,6 +3,8 @@ import { GlNewButton, GlLoadingIcon } from '@gitlab/ui';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
import { returnUrl } from '../mock_data';
describe('Static Site Editor Toolbar', () => { describe('Static Site Editor Toolbar', () => {
let wrapper; let wrapper;
...@@ -15,6 +17,7 @@ describe('Static Site Editor Toolbar', () => { ...@@ -15,6 +17,7 @@ describe('Static Site Editor Toolbar', () => {
}); });
}; };
const findReturnUrlLink = () => wrapper.find({ ref: 'returnUrlLink' });
const findSaveChangesButton = () => wrapper.find(GlNewButton); const findSaveChangesButton = () => wrapper.find(GlNewButton);
const findLoadingIndicator = () => wrapper.find(GlLoadingIcon); const findLoadingIndicator = () => wrapper.find(GlLoadingIcon);
...@@ -38,6 +41,17 @@ describe('Static Site Editor Toolbar', () => { ...@@ -38,6 +41,17 @@ describe('Static Site Editor Toolbar', () => {
expect(findLoadingIndicator().classes()).toContain('invisible'); expect(findLoadingIndicator().classes()).toContain('invisible');
}); });
it('does not render returnUrl link', () => {
expect(findReturnUrlLink().exists()).toBe(false);
});
it('renders returnUrl link when returnUrl prop exists', () => {
buildWrapper({ returnUrl });
expect(findReturnUrlLink().exists()).toBe(true);
expect(findReturnUrlLink().attributes('href')).toBe(returnUrl);
});
describe('when saveable', () => { describe('when saveable', () => {
it('enables Submit Changes button', () => { it('enables Submit Changes button', () => {
buildWrapper({ saveable: true }); buildWrapper({ saveable: true });
......
...@@ -7,9 +7,10 @@ import createState from '~/static_site_editor/store/state'; ...@@ -7,9 +7,10 @@ import createState from '~/static_site_editor/store/state';
import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue'; import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue'; import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditHeader from '~/static_site_editor/components/edit_header.vue';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
import { sourceContent } from '../mock_data'; import { sourceContent, sourceContentTitle } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -60,6 +61,7 @@ describe('StaticSiteEditor', () => { ...@@ -60,6 +61,7 @@ describe('StaticSiteEditor', () => {
}; };
const findEditArea = () => wrapper.find(EditArea); const findEditArea = () => wrapper.find(EditArea);
const findEditHeader = () => wrapper.find(EditHeader);
const findPublishToolbar = () => wrapper.find(PublishToolbar); const findPublishToolbar = () => wrapper.find(PublishToolbar);
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader); const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
...@@ -77,16 +79,21 @@ describe('StaticSiteEditor', () => { ...@@ -77,16 +79,21 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().exists()).toBe(false); expect(findEditArea().exists()).toBe(false);
}); });
it('does not render edit header', () => {
expect(findEditHeader().exists()).toBe(false);
});
it('does not render toolbar', () => { it('does not render toolbar', () => {
expect(findPublishToolbar().exists()).toBe(false); expect(findPublishToolbar().exists()).toBe(false);
}); });
}); });
describe('when content is loaded', () => { describe('when content is loaded', () => {
const content = 'edit area content'; const content = sourceContent;
const title = sourceContentTitle;
beforeEach(() => { beforeEach(() => {
buildContentLoadedStore({ initialState: { content } }); buildContentLoadedStore({ initialState: { content, title } });
buildWrapper(); buildWrapper();
}); });
...@@ -94,6 +101,10 @@ describe('StaticSiteEditor', () => { ...@@ -94,6 +101,10 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().exists()).toBe(true); expect(findEditArea().exists()).toBe(true);
}); });
it('renders the edit header', () => {
expect(findEditHeader().exists()).toBe(true);
});
it('does not render skeleton loader', () => { it('does not render skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(false); expect(findSkeletonLoader().exists()).toBe(false);
}); });
...@@ -102,6 +113,10 @@ describe('StaticSiteEditor', () => { ...@@ -102,6 +113,10 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().props('value')).toBe(content); expect(findEditArea().props('value')).toBe(content);
}); });
it('passes page title to edit header', () => {
expect(findEditHeader().props('title')).toBe(title);
});
it('renders toolbar', () => { it('renders toolbar', () => {
expect(findPublishToolbar().exists()).toBe(true); expect(findPublishToolbar().exists()).toBe(true);
}); });
......
...@@ -11,11 +11,11 @@ twitter_image: '/images/tweets/handbook-gitlab.png' ...@@ -11,11 +11,11 @@ twitter_image: '/images/tweets/handbook-gitlab.png'
- TOC - TOC
{:toc .hidden-md .hidden-lg} {:toc .hidden-md .hidden-lg}
`; `;
export const sourceContentTitle = 'Handbook'; export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser'; export const username = 'gitlabuser';
export const projectId = '123456'; export const projectId = '123456';
export const returnUrl = 'https://www.gitlab.com';
export const sourcePath = 'foobar.md.html'; export const sourcePath = 'foobar.md.html';
export const savedContentMeta = { export const savedContentMeta = {
......
...@@ -6,6 +6,21 @@ describe ContainerRegistry::Client do ...@@ -6,6 +6,21 @@ describe ContainerRegistry::Client do
let(:token) { '12345' } let(:token) { '12345' }
let(:options) { { token: token } } let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) } let(:client) { described_class.new("http://container-registry", options) }
let(:push_blob_headers) do
{
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => "bearer #{token}",
'Content-Type' => 'application/octet-stream',
'User-Agent' => "GitLab/#{Gitlab::VERSION}"
}
end
let(:headers_with_accept_types) do
{
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => "bearer #{token}",
'User-Agent' => "GitLab/#{Gitlab::VERSION}"
}
end
shared_examples '#repository_manifest' do |manifest_type| shared_examples '#repository_manifest' do |manifest_type|
let(:manifest) do let(:manifest) do
...@@ -25,14 +40,15 @@ describe ContainerRegistry::Client do ...@@ -25,14 +40,15 @@ describe ContainerRegistry::Client do
"size" => 2828661 "size" => 2828661
} }
] ]
} }
end end
it 'GET /v2/:name/manifests/mytag' do it 'GET /v2/:name/manifests/mytag' do
stub_request(:get, "http://container-registry/v2/group/test/manifests/mytag") stub_request(:get, "http://container-registry/v2/group/test/manifests/mytag")
.with(headers: { .with(headers: {
'Accept' => described_class::ACCEPTED_TYPES.join(', '), 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => "bearer #{token}" 'Authorization' => "bearer #{token}",
'User-Agent' => "GitLab/#{Gitlab::VERSION}"
}) })
.to_return(status: 200, body: manifest.to_json, headers: { content_type: manifest_type }) .to_return(status: 200, body: manifest.to_json, headers: { content_type: manifest_type })
...@@ -44,12 +60,23 @@ describe ContainerRegistry::Client do ...@@ -44,12 +60,23 @@ describe ContainerRegistry::Client do
it_behaves_like '#repository_manifest', described_class::OCI_MANIFEST_V1_TYPE it_behaves_like '#repository_manifest', described_class::OCI_MANIFEST_V1_TYPE
describe '#blob' do describe '#blob' do
let(:blob_headers) do
{
'Accept' => 'application/octet-stream',
'Authorization' => "bearer #{token}",
'User-Agent' => "GitLab/#{Gitlab::VERSION}"
}
end
let(:redirect_header) do
{
'User-Agent' => "GitLab/#{Gitlab::VERSION}"
}
end
it 'GET /v2/:name/blobs/:digest' do it 'GET /v2/:name/blobs/:digest' do
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345") stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
.with(headers: { .with(headers: blob_headers)
'Accept' => 'application/octet-stream',
'Authorization' => "bearer #{token}"
})
.to_return(status: 200, body: "Blob") .to_return(status: 200, body: "Blob")
expect(client.blob('group/test', 'sha256:0123456789012345')).to eq('Blob') expect(client.blob('group/test', 'sha256:0123456789012345')).to eq('Blob')
...@@ -57,15 +84,14 @@ describe ContainerRegistry::Client do ...@@ -57,15 +84,14 @@ describe ContainerRegistry::Client do
it 'follows 307 redirect for GET /v2/:name/blobs/:digest' do it 'follows 307 redirect for GET /v2/:name/blobs/:digest' do
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345") stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
.with(headers: { .with(headers: blob_headers)
'Accept' => 'application/octet-stream',
'Authorization' => "bearer #{token}"
})
.to_return(status: 307, body: "", headers: { Location: 'http://redirected' }) .to_return(status: 307, body: "", headers: { Location: 'http://redirected' })
# We should probably use hash_excluding here, but that requires an update to WebMock: # We should probably use hash_excluding here, but that requires an update to WebMock:
# https://github.com/bblimke/webmock/blob/master/lib/webmock/matchers/hash_excluding_matcher.rb # https://github.com/bblimke/webmock/blob/master/lib/webmock/matchers/hash_excluding_matcher.rb
stub_request(:get, "http://redirected/") stub_request(:get, "http://redirected/")
.with { |request| !request.headers.include?('Authorization') } .with(headers: redirect_header) do |request|
!request.headers.include?('Authorization')
end
.to_return(status: 200, body: "Successfully redirected") .to_return(status: 200, body: "Successfully redirected")
response = client.blob('group/test', 'sha256:0123456789012345') response = client.blob('group/test', 'sha256:0123456789012345')
...@@ -76,10 +102,11 @@ describe ContainerRegistry::Client do ...@@ -76,10 +102,11 @@ describe ContainerRegistry::Client do
def stub_upload(path, content, digest, status = 200) def stub_upload(path, content, digest, status = 200)
stub_request(:post, "http://container-registry/v2/#{path}/blobs/uploads/") stub_request(:post, "http://container-registry/v2/#{path}/blobs/uploads/")
.with(headers: headers_with_accept_types)
.to_return(status: status, body: "", headers: { 'location' => 'http://container-registry/next_upload?id=someid' }) .to_return(status: status, body: "", headers: { 'location' => 'http://container-registry/next_upload?id=someid' })
stub_request(:put, "http://container-registry/next_upload?digest=#{digest}&id=someid") stub_request(:put, "http://container-registry/next_upload?digest=#{digest}&id=someid")
.with(body: content) .with(body: content, headers: push_blob_headers)
.to_return(status: status, body: "", headers: {}) .to_return(status: status, body: "", headers: {})
end end
...@@ -136,11 +163,20 @@ describe ContainerRegistry::Client do ...@@ -136,11 +163,20 @@ describe ContainerRegistry::Client do
end end
describe '#put_tag' do describe '#put_tag' do
let(:manifest_headers) do
{
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
'Authorization' => "bearer #{token}",
'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}"
}
end
subject { client.put_tag('path', 'tagA', { foo: :bar }) } subject { client.put_tag('path', 'tagA', { foo: :bar }) }
it 'uploads the manifest and returns the digest' do it 'uploads the manifest and returns the digest' do
stub_request(:put, "http://container-registry/v2/path/manifests/tagA") stub_request(:put, "http://container-registry/v2/path/manifests/tagA")
.with(body: "{\n \"foo\": \"bar\"\n}") .with(body: "{\n \"foo\": \"bar\"\n}", headers: manifest_headers)
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:123' }) .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:123' })
expect(subject).to eq 'sha256:123' expect(subject).to eq 'sha256:123'
...@@ -153,6 +189,7 @@ describe ContainerRegistry::Client do ...@@ -153,6 +189,7 @@ describe ContainerRegistry::Client do
context 'when the tag exists' do context 'when the tag exists' do
before do before do
stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a") stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a")
.with(headers: headers_with_accept_types)
.to_return(status: 200, body: "") .to_return(status: 200, body: "")
end end
...@@ -162,6 +199,7 @@ describe ContainerRegistry::Client do ...@@ -162,6 +199,7 @@ describe ContainerRegistry::Client do
context 'when the tag does not exist' do context 'when the tag does not exist' do
before do before do
stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a") stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a")
.with(headers: headers_with_accept_types)
.to_return(status: 404, body: "") .to_return(status: 404, body: "")
end end
...@@ -171,6 +209,7 @@ describe ContainerRegistry::Client do ...@@ -171,6 +209,7 @@ describe ContainerRegistry::Client do
context 'when an error occurs' do context 'when an error occurs' do
before do before do
stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a") stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a")
.with(headers: headers_with_accept_types)
.to_return(status: 500, body: "") .to_return(status: 500, body: "")
end end
......
...@@ -18,13 +18,44 @@ describe Gitlab::StaticSiteEditor::Config do ...@@ -18,13 +18,44 @@ describe Gitlab::StaticSiteEditor::Config do
it 'returns data for the frontend component' do it 'returns data for the frontend component' do
is_expected.to eq( is_expected.to eq(
branch: 'master', branch: 'master',
commit: repository.commit.id, commit_id: repository.commit.id,
namespace: 'namespace', namespace: 'namespace',
path: 'README.md', path: 'README.md',
project: 'project', project: 'project',
project_id: project.id, project_id: project.id,
return_url: 'http://example.com' return_url: 'http://example.com',
is_supported_content: true
) )
end end
context 'when branch is not master' do
let(:ref) { 'my-branch' }
it { is_expected.to include(is_supported_content: false) }
end
context 'when file does not have a markdown extension' do
let(:file_path) { 'README.txt' }
it { is_expected.to include(is_supported_content: false) }
end
context 'when file does not have an extension' do
let(:file_path) { 'README' }
it { is_expected.to include(is_supported_content: false) }
end
context 'when file does not exist' do
let(:file_path) { 'UNKNOWN.md' }
it { is_expected.to include(is_supported_content: false) }
end
context 'when repository is empty' do
let(:project) { create(:project_empty_repo) }
it { is_expected.to include(is_supported_content: false) }
end
end end
end end
...@@ -280,6 +280,18 @@ describe API::Users, :do_not_mock_admin_mode do ...@@ -280,6 +280,18 @@ describe API::Users, :do_not_mock_admin_mode do
expect(json_response.first['id']).to eq(user_with_2fa.id) expect(json_response.first['id']).to eq(user_with_2fa.id)
end end
it "returns users without projects" do
user_without_projects = create(:user)
create(:project, namespace: user.namespace)
create(:project, namespace: admin.namespace)
get api('/users', admin), params: { without_projects: true }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(1)
expect(json_response.first['id']).to eq(user_without_projects.id)
end
it 'returns 400 when provided incorrect sort params' do it 'returns 400 when provided incorrect sort params' do
get api('/users', admin), params: { order_by: 'magic', sort: 'asc' } get api('/users', admin), params: { order_by: 'magic', sort: 'asc' }
......
# frozen_string_literal: true
RSpec.shared_examples 'returning response status' do |status|
it "returns #{status}" do
subject
expect(response).to have_gitlab_http_status(status)
end
end
...@@ -596,22 +596,6 @@ describe ObjectStorage do ...@@ -596,22 +596,6 @@ describe ObjectStorage do
uploader.cache!(uploaded_file) uploader.cache!(uploaded_file)
end end
context 'when local file is used' do
context 'when valid file is used' do
let(:uploaded_file) do
fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg')
end
it "properly caches the file" do
subject
expect(uploader).to be_exists
expect(uploader.path).to start_with(uploader_class.root)
expect(uploader.filename).to eq('rails_sample.jpg')
end
end
end
context 'when local file is used' do context 'when local file is used' do
let(:temp_file) { Tempfile.new("test") } let(:temp_file) { Tempfile.new("test") }
...@@ -627,6 +611,14 @@ describe ObjectStorage do ...@@ -627,6 +611,14 @@ describe ObjectStorage do
context 'when valid file is specified' do context 'when valid file is specified' do
let(:uploaded_file) { temp_file } let(:uploaded_file) { temp_file }
it 'properly caches the file' do
subject
expect(uploader).to be_exists
expect(uploader.path).to start_with(uploader_class.root)
expect(uploader.filename).to eq(File.basename(uploaded_file.path))
end
context 'when object storage and direct upload is specified' do context 'when object storage and direct upload is specified' do
before do before do
stub_uploads_object_storage(uploader_class, enabled: true, direct_upload: true) stub_uploads_object_storage(uploader_class, enabled: true, direct_upload: true)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment