Commit 84521bef authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 422d06a3 a8d7e020
......@@ -368,7 +368,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 6.1.0'
gem 'rspec-rails', '~> 4.1.2'
gem 'rspec-rails', '~> 5.0.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.11.0'
......
......@@ -1076,10 +1076,10 @@ GEM
proc_to_ast
rspec (>= 2.13, < 4)
unparser
rspec-rails (4.1.2)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-rails (5.0.1)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
......@@ -1566,7 +1566,7 @@ DEPENDENCIES
rouge (~> 3.26.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 4.1.2)
rspec-rails (~> 5.0.1)
rspec-retry (~> 0.6.1)
rspec_junit_formatter
rspec_profiling (~> 0.0.6)
......
This diff is collapsed.
......@@ -20,7 +20,7 @@ export default {
</script>
<template>
<div class="d-flex-center flex-nowrap text-nowrap js-ide-status-mr">
<div class="d-flex-center gl-flex-nowrap text-nowrap js-ide-status-mr">
<gl-icon name="merge-request" />
<span class="ml-1 d-none d-sm-block">{{ s__('WebIDE|Merge request') }}</span>
<gl-link class="ml-1" :href="url">{{ text }}</gl-link>
......
......@@ -31,7 +31,7 @@ export default {
<template>
<dropdown-button>
<span class="row flex-nowrap">
<span class="row gl-flex-nowrap">
<span class="col-auto flex-fill text-truncate">
<gl-icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }}
</span>
......
......@@ -111,7 +111,7 @@ export default {
</gl-link>
</td>
<td
class="gl-display-flex gl-flex-sm-wrap gl-p-4 gl-pt-5 gl-vertical-align-top"
class="gl-display-flex gl-sm-flex-wrap gl-p-4 gl-pt-5 gl-vertical-align-top"
data-testid="fullPath"
data-qa-selector="project_path_content"
>
......
......@@ -15,7 +15,7 @@ export default {
<template>
<div
ref="linksSection"
class="gl-sm-display-flex gl-flex-sm-wrap gl-mt-5 gl-p-3 gl-bg-gray-10 border gl-rounded-base links-section"
class="gl-sm-display-flex gl-sm-flex-wrap gl-mt-5 gl-p-3 gl-bg-gray-10 border gl-rounded-base links-section"
>
<div
v-for="(link, key) in links"
......
......@@ -96,6 +96,9 @@ export default {
<p class="gl-text-gray-700 gl-mb-6">{{ $options.i18n.plan.description }}</p>
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4">
<div class="col gl-mb-6">
<learn-gitlab-info-card v-bind="infoProps('issueCreated')" />
</div>
<div class="col gl-mb-6">
<learn-gitlab-info-card v-bind="infoProps('mergeRequestCreated')" />
</div>
......
......@@ -61,7 +61,7 @@ export default {
<div
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img :src="svg" />
<img :src="svg" :alt="actionLabel" />
<h6>{{ title }}</h6>
<p class="gl-font-sm gl-text-gray-700">{{ description }}</p>
<gl-link :href="url" target="_blank">{{ actionLabel }}</gl-link>
......
......@@ -63,6 +63,15 @@ export const ACTION_LABELS = {
section: 'deploy',
position: 1,
},
issueCreated: {
title: s__('LearnGitLab|Create an issue'),
actionLabel: s__('LearnGitLab|Create an issue'),
description: s__(
'LearnGitLab|Create/import issues (tickets) to collaborate on ideas and plan work.',
),
section: 'plan',
position: 0,
},
};
export const ACTION_SECTIONS = {
......
......@@ -18,6 +18,17 @@ module BoardIssueFilterable
end
def set_filter_values(filters)
filter_by_assignee(filters)
end
def filter_by_assignee(filters)
if filters[:assignee_username] && filters[:assignee_wildcard_id]
raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: assigneeUsername, assigneeWildcardId.'
end
if filters[:assignee_wildcard_id]
filters[:assignee_id] = filters.delete(:assignee_wildcard_id)
end
end
end
......
# frozen_string_literal: true
module Types
module Boards
class AssigneeWildcardIdEnum < BaseEnum
graphql_name 'AssigneeWildcardId'
description 'Assignee ID wildcard values'
value 'NONE', 'No assignee is assigned.'
value 'ANY', 'An assignee is assigned.'
end
end
end
......@@ -18,6 +18,10 @@ module Types
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query for issue title or description.'
argument :assignee_wildcard_id, ::Types::Boards::AssigneeWildcardIdEnum,
required: false,
description: 'Filter by assignee wildcard. Incompatible with assigneeUsername.'
end
end
end
......
......@@ -84,3 +84,4 @@ module AppearancesHelper
end
AppearancesHelper.prepend_if_ee('EE::AppearancesHelper')
AppearancesHelper.prepend_if_jh('JH::AppearancesHelper')
......@@ -194,10 +194,16 @@ module ApplicationHelper
end
end
def promo_host
# This needs to be used outside of Rails
def self.promo_host
'about.gitlab.com'
end
# Convenient method for Rails helper
def promo_host
ApplicationHelper.promo_host
end
def promo_url
'https://' + promo_host
end
......@@ -406,3 +412,4 @@ module ApplicationHelper
end
ApplicationHelper.prepend_if_ee('EE::ApplicationHelper')
ApplicationHelper.prepend_if_jh('JH::ApplicationHelper')
......@@ -24,6 +24,7 @@ module LearnGitlabHelper
private
ACTION_ISSUE_IDS = {
issue_created: 4,
git_write: 6,
pipeline_created: 7,
merge_request_created: 9,
......
# frozen_string_literal: true
module Boards
module Lists
class BaseUpdateService < Boards::BaseService
def execute(list)
if execute_by_params(list)
success(list: list)
else
error(list.errors.messages, 422)
end
end
private
def execute_by_params(list)
update_preferences_result = update_preferences(list) if can_read?(list)
update_position_result = update_position(list) if can_admin?(list)
update_preferences_result || update_position_result
end
def update_preferences(list)
return unless preferences?
list.update_preferences_for(current_user, preferences)
end
def update_position(list)
return unless position?
move_service = Boards::Lists::MoveService.new(parent, current_user, params)
move_service.execute(list)
end
def preferences
{ collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) }
end
def preferences?
params.has_key?(:collapsed)
end
def position?
params.has_key?(:position)
end
def can_read?(list)
raise NotImplementedError
end
def can_admin?(list)
raise NotImplementedError
end
end
end
end
......@@ -2,50 +2,7 @@
module Boards
module Lists
class UpdateService < Boards::BaseService
def execute(list)
if execute_by_params(list)
success(list: list)
else
error(list.errors.messages, 422)
end
end
private
def execute_by_params(list)
update_preferences_result = update_preferences(list) if can_read?(list)
update_position_result = update_position(list) if can_admin?(list)
update_preferences_result || update_position_result
end
def update_preferences(list)
return unless preferences?
list.update_preferences_for(current_user, preferences)
end
def update_position(list)
return unless position?
move_service = Boards::Lists::MoveService.new(parent, current_user, params)
move_service.execute(list)
end
def preferences
{ collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) }
end
def preferences?
params.has_key?(:collapsed)
end
def position?
params.has_key?(:position)
end
class UpdateService < Boards::Lists::BaseUpdateService
def can_read?(list)
Ability.allowed?(current_user, :read_issue_board_list, parent)
end
......
......@@ -31,9 +31,9 @@ module Projects
# Create status notifying the deployment of pages
@status = create_status
@status.update_older_statuses_retried! if Feature.enabled?(:ci_fix_commit_status_retried, project, default_enabled: :yaml)
@status.enqueue!
@status.run!
@status.update_older_statuses_retried! if Feature.enabled?(:ci_fix_commit_status_retried, project, default_enabled: :yaml)
raise InvalidStateError, 'missing pages artifacts' unless build.artifacts?
raise InvalidStateError, 'build SHA is outdated for this ref' unless latest?
......
......@@ -5,9 +5,9 @@
= render "devise/shared/error_messages", resource: resource
.form-group
= f.label :email
= f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.'
= f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
.clearfix
= f.submit "Reset password", class: "gl-button btn-confirm btn"
= f.submit _("Reset password"), class: "gl-button btn-confirm btn"
.clearfix.prepend-top-20
= render 'devise/shared/sign_in_link'
......@@ -5,7 +5,7 @@
.form-group.col-12.col-sm-6
= label_tag :namespace_id, _('Project URL'), class: 'label-bold'
.form-group
.input-group.flex-nowrap
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
.input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
......
......@@ -12,7 +12,7 @@
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do
%span= s_("Project URL")
.input-group.flex-nowrap
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
.input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
......
......@@ -3,7 +3,7 @@
- flex_grow_and_shrink_xs = 'd-flex flex-xs-grow-1 flex-xs-shrink-1 flex-grow-0 flex-shrink-0'
.filtered-search-block.row-content-block.bt-0
.filtered-search-wrapper.d-flex.flex-nowrap.flex-column.flex-sm-wrap.flex-sm-row.flex-xl-nowrap
.filtered-search-wrapper.d-flex.gl-flex-nowrap.flex-column.flex-sm-wrap.flex-sm-row.flex-xl-nowrap
- unless project_tab_filter == :starred
.filtered-search-nav.mb-2.mb-lg-0{ class: flex_grow_and_shrink_xs }
= render 'dashboard/projects/nav', project_tab_filter: project_tab_filter
......
---
title: Support filtering by assignee wildcard in GraphQL board list issues query
merge_request: 58996
author:
type: added
---
title: Externalize strings in passwords/new.html.haml
merge_request: 58236
author: nuwe1
type: other
---
title: Bump rspec-rails to 5.0.1
merge_request: 59194
author:
type: other
......@@ -57,21 +57,29 @@ module Gitlab
config.generators.templates.push("#{config.root}/generator_templates")
if Gitlab.ee?
ee_paths = config.eager_load_paths.each_with_object([]) do |path, memo|
ee_path = config.root.join('ee', Pathname.new(path).relative_path_from(config.root))
memo << ee_path.to_s
load_paths = lambda do |dir:|
ext_paths = config.eager_load_paths.each_with_object([]) do |path, memo|
ext_path = config.root.join(dir, Pathname.new(path).relative_path_from(config.root))
memo << ext_path.to_s
end
ee_paths << "#{config.root}/ee/app/replicators"
ext_paths << "#{config.root}/#{dir}/app/replicators"
# Eager load should load CE first
config.eager_load_paths.push(*ee_paths)
config.helpers_paths.push "#{config.root}/ee/app/helpers"
config.eager_load_paths.push(*ext_paths)
config.helpers_paths.push "#{config.root}/#{dir}/app/helpers"
# Other than Ruby modules we load EE first
config.paths['lib/tasks'].unshift "#{config.root}/ee/lib/tasks"
config.paths['app/views'].unshift "#{config.root}/ee/app/views"
# Other than Ruby modules we load extensions first
config.paths['lib/tasks'].unshift "#{config.root}/#{dir}/lib/tasks"
config.paths['app/views'].unshift "#{config.root}/#{dir}/app/views"
end
Gitlab.ee do
load_paths.call(dir: 'ee')
end
Gitlab.jh do
load_paths.call(dir: 'jh')
end
# Rake tasks ignore the eager loading settings, so we need to set the
......
......@@ -31,6 +31,12 @@ module InjectEnterpriseEditionModule
include(ee_module) if Gitlab.ee?
end
def prepend_if_jh(constant, with_descendants: false)
return unless Gitlab.jh?
prepend_module(constant.constantize, with_descendants)
end
private
def prepend_module(mod, with_descendants)
......
# frozen_string_literal: true
Gitlab.ee do
load_license = lambda do |dir:, license_name:|
prefix = ENV['GITLAB_LICENSE_MODE'] == 'test' ? 'test_' : ''
public_key_file = File.read(Rails.root.join(".#{prefix}license_encryption_key.pub"))
public_key_file = File.read(Rails.root.join(dir, ".#{prefix}license_encryption_key.pub"))
public_key = OpenSSL::PKey::RSA.new(public_key_file)
Gitlab::License.encryption_key = public_key
rescue
warn "WARNING: No valid license encryption key provided."
warn "WARNING: No valid #{license_name} encryption key provided."
end
Gitlab.ee do
load_license.call(dir: '.', license_name: 'license')
end
Gitlab.jh do
load_license.call(dir: 'jh', license_name: 'JH license')
end
......@@ -35,5 +35,6 @@ ActiveSupport::Inflector.inflections do |inflect|
vulnerability_feedback
)
inflect.acronym 'EE'
inflect.acronym 'JH'
inflect.acronym 'CSP'
end
......@@ -7525,6 +7525,15 @@ The kind of an approval rule.
| `REGULAR` | A `regular` approval rule. |
| `REPORT_APPROVER` | A `report_approver` approval rule. |
### `AssigneeWildcardId`
Assignee ID wildcard values.
| Value | Description |
| ----- | ----------- |
| `ANY` | An assignee is assigned. |
| `NONE` | No assignee is assigned. |
### `AvailabilityEnum`
User availability status.
......
......@@ -58,6 +58,8 @@ To add a new application for your user:
## Group owned applications
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16227) in GitLab 13.11.
To add a new application for a group:
1. Navigate to the desired group.
......
......@@ -97,7 +97,7 @@ export default {
@click="toggleProject"
>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-70 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-70 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -119,7 +119,7 @@ export default {
</div>
</div>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-30 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-30 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......
......@@ -110,7 +110,7 @@ export default {
data-testid="projectTableRow"
>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-50 gl-text-truncate gl-pr-5"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-50 gl-text-truncate gl-pr-5"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -140,7 +140,7 @@ export default {
</div>
</div>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-15 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-15 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -149,7 +149,7 @@ export default {
<div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</div>
</div>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-15 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-15 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......
......@@ -99,7 +99,7 @@ export default {
<template>
<div class="epics-filters epics-roadmap-filters epics-roadmap-filters-gl-ui">
<div
class="epics-details-filters filtered-search-block gl-display-flex gl-flex-direction-column flex-xl-row row-content-block second-block"
class="epics-details-filters filtered-search-block gl-display-flex gl-flex-direction-column gl-xl-flex-direction-row row-content-block second-block"
>
<gl-form-group class="mb-0">
<gl-segmented-control
......
<script>
import { GlAlert } from '@gitlab/ui';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import instanceProjectsQuery from 'ee/security_dashboard/graphql/queries/instance_projects.query.graphql';
import { PROJECT_LOADING_ERROR_MESSAGE } from '../helpers';
import ProjectManager from './first_class_project_manager/project_manager.vue';
export default {
components: {
ProjectManager,
SecurityDashboardLayout,
GlAlert,
},
apollo: {
projects: {
query: instanceProjectsQuery,
update(data) {
return data.instanceSecurityDashboard.projects.nodes;
},
error() {
this.hasError = true;
},
},
},
data() {
return {
projects: [],
hasError: false,
};
},
PROJECT_LOADING_ERROR_MESSAGE,
};
</script>
<template>
<security-dashboard-layout>
<gl-alert v-if="hasError" variant="danger">
{{ $options.PROJECT_LOADING_ERROR_MESSAGE }}
</gl-alert>
<div v-else class="gl-display-flex gl-justify-content-center">
<project-manager :projects="projects" />
</div>
</security-dashboard-layout>
</template>
<script>
import { GlBadge, GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { PROJECT_LOADING_ERROR_MESSAGE } from 'ee/security_dashboard/helpers';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
import projectsQuery from '../../graphql/queries/instance_projects.query.graphql';
export default {
i18n: {
projectsAdded: s__('SecurityReports|Projects added'),
removeLabel: s__('SecurityReports|Remove project from dashboard'),
emptyMessage: s__(
'SecurityReports|Select a project to add by using the project search field above.',
),
},
components: {
GlBadge,
......@@ -16,57 +23,73 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
apollo: {
projects: {
type: Array,
required: true,
query: projectsQuery,
update(data) {
const projects = data?.instanceSecurityDashboard?.projects?.nodes;
if (projects === undefined) {
this.showErrorFlash();
}
return projects || [];
},
error() {
this.showErrorFlash();
},
},
showLoadingIndicator: {
type: Boolean,
required: true,
},
data() {
return {
projects: [],
};
},
computed: {
isLoadingProjects() {
return this.$apollo.queries.projects.loading;
},
},
methods: {
projectRemoved(project) {
this.$emit('projectRemoved', project);
},
showErrorFlash() {
createFlash({ message: PROJECT_LOADING_ERROR_MESSAGE });
},
},
};
</script>
<template>
<section>
<div>
<h4 class="h5 font-weight-bold text-secondary border-bottom mb-3 pb-2">
{{ s__('SecurityReports|Projects added') }}
<gl-badge class="gl-font-weight-bold">{{ projects.length }}</gl-badge>
<gl-loading-icon v-if="showLoadingIndicator" size="sm" class="float-right" />
</h4>
<ul v-if="projects.length" class="list-unstyled">
<li
v-for="project in projects"
:key="project.id"
class="d-flex align-items-center py-1 js-projects-list-project-item"
>
<project-avatar class="flex-shrink-0" :project="project" :size="32" />
<span>
{{ project.name_with_namespace || project.nameWithNamespace }}
</span>
<gl-button
v-gl-tooltip
icon="remove"
class="gl-ml-auto js-projects-list-project-remove"
:title="$options.i18n.removeLabel"
:aria-label="$options.i18n.removeLabel"
@click="projectRemoved(project)"
/>
</li>
</ul>
<p v-else class="text-secondary js-projects-list-empty-message">
{{
s__('SecurityReports|Select a project to add by using the project search field above.')
}}
</p>
</div>
<h5
class="gl-font-weight-bold gl-text-gray-500 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-mb-5 gl-pb-3"
>
{{ $options.i18n.projectsAdded }}
<gl-badge class="gl-font-weight-bold">{{ projects.length }}</gl-badge>
</h5>
<gl-loading-icon v-if="isLoadingProjects" size="lg" />
<ul v-else-if="projects.length" class="gl-p-0">
<li
v-for="project in projects"
:key="project.id"
class="gl-display-flex gl-align-items-center gl-py-2 js-projects-list-project-item"
>
<project-avatar class="gl-flex-shrink-0" :project="project" :size="32" />
{{ project.nameWithNamespace }}
<gl-button
v-gl-tooltip
icon="remove"
class="gl-ml-auto js-projects-list-project-remove"
:title="$options.i18n.removeLabel"
:aria-label="$options.i18n.removeLabel"
@click="projectRemoved(project)"
/>
</li>
</ul>
<p v-else class="gl-text-gray-500 js-projects-list-empty-message" data-testid="empty-message">
{{ $options.i18n.emptyMessage }}
</p>
</section>
</template>
......@@ -19,17 +19,6 @@ export default {
ProjectList,
ProjectSelector,
},
props: {
isManipulatingProjects: {
type: Boolean,
required: false,
default: false,
},
projects: {
type: Array,
required: true,
},
},
data() {
return {
searchQuery: '',
......@@ -49,7 +38,7 @@ export default {
},
computed: {
canAddProjects() {
return !this.isManipulatingProjects && this.selectedProjects.length > 0;
return this.selectedProjects.length > 0;
},
isSearchingProjects() {
return this.searchCount > 0;
......@@ -288,12 +277,7 @@ export default {
</div>
</div>
<div class="row justify-content-center mt-md-3">
<project-list
:projects="projects"
:show-loading-indicator="isManipulatingProjects"
class="col col-lg-7"
@projectRemoved="removeProject"
/>
<project-list class="col col-lg-7" @projectRemoved="removeProject" />
</div>
</section>
</template>
import Vue from 'vue';
import InstanceSecurityDashboardSettings from './components/first_class_instance_security_dashboard_settings.vue';
import ProjectManager from './components/first_class_project_manager/project_manager.vue';
import apolloProvider from './graphql/provider';
export default (el) => {
......@@ -11,7 +11,7 @@ export default (el) => {
el,
apolloProvider,
render(createElement) {
return createElement(InstanceSecurityDashboardSettings);
return createElement(ProjectManager);
},
});
};
......@@ -97,7 +97,7 @@ export default {
@click="toggleProject"
>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-70 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-70 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -119,7 +119,7 @@ export default {
</div>
</div>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-30 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-30 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......
......@@ -110,7 +110,7 @@ export default {
data-testid="projectTableRow"
>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-50 gl-text-truncate gl-pr-5"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-50 gl-text-truncate gl-pr-5"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -140,7 +140,7 @@ export default {
</div>
</div>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-15 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-15 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -149,7 +149,7 @@ export default {
<div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</div>
</div>
<div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-15 gl-text-truncate"
class="table-section gl-white-space-normal! gl-sm-flex-wrap section-15 gl-text-truncate"
role="gridcell"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......
<template>
<div
class="row gl-flex-grow-1 gl-flex-direction-column flex-nowrap gl-lg-flex-direction-row flex-xl-row flex-lg-wrap flex-xl-wrap"
class="row gl-flex-grow-1 gl-flex-direction-column gl-flex-nowrap gl-lg-flex-direction-row gl-xl-flex-direction-row gl-lg-flex-wrap gl-xl-flex-wrap"
>
<div
class="checkout-pane gl-px-3 gl-align-items-center gl-bg-gray-10 col-lg-7 gl-display-flex gl-flex-direction-column gl-flex-grow-1"
......@@ -8,7 +8,7 @@
<slot name="checkout"></slot>
</div>
<div
class="gl-pb-3 gl-px-3 px-lg-7 col-lg-5 gl-display-flex gl-flex-direction-row gl-justify-content-center"
class="gl-pb-3 gl-px-3 gl-lg-px-7 col-lg-5 gl-display-flex gl-flex-direction-row gl-justify-content-center"
>
<slot name="order-summary"></slot>
</div>
......
......@@ -10,6 +10,8 @@ module EE
def set_filter_values(filters)
filter_by_epic(filters)
filter_by_iteration(filters)
super
end
private
......
......@@ -29,5 +29,9 @@ module Boards
end
end
end
def board
epic_board
end
end
end
# frozen_string_literal: true
module Boards
module EpicLists
class UpdateService < ::Boards::Lists::BaseUpdateService
def can_read?(list)
Ability.allowed?(current_user, :read_epic_board_list, parent)
end
def can_admin?(list)
Ability.allowed?(current_user, :admin_epic_board_list, parent)
end
end
end
end
......@@ -65,7 +65,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do
it 'displays the contact sales link' do
# see ApplicationHelper#contact_sales_url
contact_sales_url = 'https://about.gitlab.com/sales'
contact_sales_url = "https://#{ApplicationHelper.promo_host}/sales"
page.within('.content') do
expect(page).to have_link('Contact sales', href: %r{#{contact_sales_url}\?test=inappcontactsales(bronze|premium|gold)})
end
......
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import FirstClassInstanceDashboardSettings from 'ee/security_dashboard/components/first_class_instance_security_dashboard_settings.vue';
import ProjectManager from 'ee/security_dashboard/components/first_class_project_manager/project_manager.vue';
describe('First Class Instance Dashboard Component', () => {
let wrapper;
const defaultMocks = ({ loading = false } = {}) => ({
$apollo: { queries: { projects: { loading } } },
});
const findProjectManager = () => wrapper.find(ProjectManager);
const findAlert = () => wrapper.find(GlAlert);
const createWrapper = ({ mocks = defaultMocks(), data = {} }) => {
return shallowMount(FirstClassInstanceDashboardSettings, {
mocks,
data() {
return data;
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when there is no error', () => {
beforeEach(() => {
wrapper = createWrapper({});
});
it('displays the project manager', () => {
expect(findProjectManager().exists()).toBe(true);
});
it('does not render the alert component', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe('when there is a loading error', () => {
beforeEach(() => {
wrapper = createWrapper({ data: { hasError: true } });
});
it('does not display the project manager', () => {
expect(findProjectManager().exists()).toBe(false);
});
it('renders the alert component', () => {
expect(findAlert().text()).toBe('An error occurred while retrieving projects.');
});
});
});
import { GlBadge, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import ProjectList from 'ee/security_dashboard/components/first_class_project_manager/project_list.vue';
import projectsQuery from 'ee/security_dashboard/graphql/queries/instance_projects.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
const getArrayWithLength = (n) => [...Array(n).keys()];
const generateMockProjects = (projectsCount, mockProject = {}) =>
getArrayWithLength(projectsCount).map((id) => ({ id, ...mockProject }));
const localVue = createLocalVue();
localVue.use(VueApollo);
const generateMockProjects = (count) => {
const projects = [];
for (let i = 0; i < count; i += 1) {
projects.push({
id: i,
name: `project${i}`,
nameWithNamespace: `group/project${i}`,
});
}
return projects;
};
describe('Project List component', () => {
let wrapper;
const factory = ({ projects = [], stubs = {}, showLoadingIndicator = false } = {}) => {
wrapper = shallowMount(ProjectList, {
stubs,
propsData: {
projects,
showLoadingIndicator,
const getMockData = (projects) => ({
data: {
instanceSecurityDashboard: {
projects: {
nodes: projects,
},
},
});
},
});
const createWrapper = ({ projects }) => {
const mockData = getMockData(projects);
wrapper = extendedWrapper(
shallowMount(ProjectList, {
localVue,
apolloProvider: createMockApollo([[projectsQuery, jest.fn().mockResolvedValue(mockData)]]),
}),
);
};
const getAllProjectItems = () => wrapper.findAll('.js-projects-list-project-item');
const getFirstProjectItem = () => wrapper.find('.js-projects-list-project-item');
const getFirstRemoveButton = () => getFirstProjectItem().find('.js-projects-list-project-remove');
const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
afterEach(() => wrapper.destroy());
it('shows an empty state if there are no projects', () => {
factory();
it('shows an empty state if there are no projects', async () => {
createWrapper({ projects: [] });
await wrapper.vm.$nextTick();
expect(wrapper.text()).toContain(
'Select a project to add by using the project search field above.',
);
expect(wrapper.findByTestId('empty-message').exists()).toBe(true);
});
it('does not show a loading indicator when showLoadingIndicator = false', () => {
factory();
describe('loading indicator', () => {
it('shows the loading indicator when query is loading', () => {
createWrapper({ projects: [] });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
expect(getLoadingIcon().exists()).toBe(true);
});
it('shows a loading indicator when showLoadingIndicator = true', () => {
factory({ showLoadingIndicator: true });
it('hides the loading indicator when query is not loading', async () => {
createWrapper({ projects: [] });
await wrapper.vm.$nextTick();
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(getLoadingIcon().exists()).toBe(false);
});
});
it.each([0, 1, 2])(
'renders a list of projects and displays a count of how many there are',
(projectsCount) => {
factory({ projects: generateMockProjects(projectsCount) });
'renders a list of projects and displays the correct count for %s projects',
async (projectsCount) => {
createWrapper({ projects: generateMockProjects(projectsCount) });
await wrapper.vm.$nextTick();
expect(getAllProjectItems()).toHaveLength(projectsCount);
expect(wrapper.find(GlBadge).text()).toBe(`${projectsCount}`);
expect(wrapper.find(GlBadge).text()).toBe(projectsCount.toString());
},
);
it('renders a project-item with an avatar', () => {
factory({ projects: generateMockProjects(1) });
expect(getFirstProjectItem().find(ProjectAvatar).exists()).toBe(true);
});
it('renders a project-item with a project name', () => {
const projectNameWithNamespace = 'foo';
describe('project item', () => {
const projects = generateMockProjects(1);
factory({
projects: generateMockProjects(1, { name_with_namespace: projectNameWithNamespace }),
beforeEach(() => {
createWrapper({ projects });
});
expect(getFirstProjectItem().text()).toContain(projectNameWithNamespace);
});
it('renders a project-item with a GraphQL project name', () => {
const projectNameWithNamespace = 'foo';
factory({
projects: generateMockProjects(1, { nameWithNamespace: projectNameWithNamespace }),
it('renders a project item with an avatar', () => {
expect(getFirstProjectItem().find(ProjectAvatar).exists()).toBe(true);
});
expect(getFirstProjectItem().text()).toContain(projectNameWithNamespace);
});
it('renders a project-item with a remove button', () => {
factory({ projects: generateMockProjects(1) });
expect(getFirstRemoveButton().exists()).toBe(true);
});
it(`emits a 'projectRemoved' event when a project's remove button has been clicked`, () => {
const mockProjects = generateMockProjects(1);
const [projectData] = mockProjects;
it('renders a project item with a project name', () => {
expect(getFirstProjectItem().text()).toContain(projects[0].nameWithNamespace);
});
factory({ projects: mockProjects, stubs: { GlButton } });
it('renders a project item with a remove button', () => {
expect(getFirstRemoveButton().exists()).toBe(true);
});
getFirstRemoveButton().vm.$emit('click');
it(`emits a 'projectRemoved' event when a project's remove button has been clicked`, () => {
getFirstRemoveButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted('projectRemoved')).toHaveLength(1);
expect(wrapper.emitted('projectRemoved')).toEqual([[projectData]]);
expect(wrapper.emitted('projectRemoved')[0][0]).toEqual(projects[0]);
});
});
});
......@@ -32,11 +32,6 @@ describe('Project Manager component', () => {
},
};
const defaultProps = {
isManipulatingProjects: false,
projects: [],
};
const createWrapper = ({ data = {}, mocks = {}, props = {} }) => {
spyQuery = defaultMocks.$apollo.query;
spyMutate = defaultMocks.$apollo.mutate;
......@@ -45,7 +40,7 @@ describe('Project Manager component', () => {
return { ...data };
},
mocks: { ...defaultMocks, ...mocks },
propsData: { ...defaultProps, ...props },
propsData: props,
});
};
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Boards::EpicLists::UpdateService do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:epic_board, group: group) }
let_it_be(:list) { create(:epic_list, epic_board: board, position: 0) }
before do
stub_licensed_features(epics: true)
end
describe '#execute' do
let(:service) { described_class.new(board.resource_parent, user, params) }
context 'when position parameter is present' do
let(:params) { { position: 1 } }
it_behaves_like 'moving list'
end
context 'when collapsed parameter is present' do
let(:params) { { collapsed: true } }
it_behaves_like 'updating list preferences'
end
context 'when position and collapsed are both present' do
let(:params) { { collapsed: true, position: 1 } }
it_behaves_like 'moving list'
it_behaves_like 'updating list preferences'
end
end
end
......@@ -108,10 +108,21 @@ module Gitlab
!%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
end
def self.jh?
@is_jh ||=
ee? &&
root.join('jh').exist? &&
!%w[true 1].include?(ENV['EE_ONLY'].to_s)
end
def self.ee
yield if ee?
end
def self.jh
yield if jh?
end
def self.http_proxy_env?
HTTP_PROXY_ENV_VARS.any? { |name| ENV[name] }
end
......
......@@ -6,6 +6,11 @@ module Gitlab
::Gitlab.dev_or_test_env? ? 'https://customers.stg.gitlab.com' : 'https://customers.gitlab.com'
end
SUBSCRIPTIONS_URL = ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url).freeze
def self.subscriptions_url
ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url)
end
end
end
Gitlab::SubscriptionPortal.prepend_if_jh('JH::Gitlab::SubscriptionPortal')
Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL = Gitlab::SubscriptionPortal.subscriptions_url.freeze
......@@ -18496,12 +18496,18 @@ msgstr ""
msgid "LearnGitLab|Create a workflow for your new workspace, and learn how GitLab features work together:"
msgstr ""
msgid "LearnGitLab|Create an issue"
msgstr ""
msgid "LearnGitLab|Create or import a repository"
msgstr ""
msgid "LearnGitLab|Create or import your first repository into your new project."
msgstr ""
msgid "LearnGitLab|Create/import issues (tickets) to collaborate on ideas and plan work."
msgstr ""
msgid "LearnGitLab|Deploy"
msgstr ""
......@@ -26743,6 +26749,9 @@ msgstr ""
msgid "Reset key"
msgstr ""
msgid "Reset password"
msgstr ""
msgid "Reset registration token"
msgstr ""
......
......@@ -97,6 +97,10 @@ function rspec_paralellized_job() {
spec_folder_prefix="ee/"
fi
if [[ "${test_tool}" =~ "-jh" ]]; then
spec_folder_prefix="jh/"
fi
export KNAPSACK_LOG_LEVEL="debug"
export KNAPSACK_REPORT_PATH="knapsack/${report_name}_report.json"
......
......@@ -596,7 +596,7 @@ RSpec.describe 'Admin updates settings' do
context 'Nav bar' do
it 'shows default help links in nav' do
default_support_url = 'https://about.gitlab.com/getting-help/'
default_support_url = "https://#{ApplicationHelper.promo_host}/getting-help/"
visit root_dashboard_path
......
......@@ -29,21 +29,21 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
class="gl-text-gray-500 gl-mb-2"
data-testid="completion-percentage"
>
25% completed
22% completed
</p>
<div
class="progress"
max="8"
max="9"
value="2"
>
<div
aria-valuemax="8"
aria-valuemax="9"
aria-valuemin="0"
aria-valuenow="2"
class="progress-bar"
role="progressbar"
style="width: 25%;"
style="width: 22.22222222222222%;"
>
<!---->
</div>
......@@ -234,6 +234,20 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
</p>
</div>
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
href="http://example.com/"
>
Create an issue
</a>
</span>
<!---->
</div>
<div
class="gl-mb-4"
>
......
......@@ -29,21 +29,21 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-gray-500 gl-mb-2"
data-testid="completion-percentage"
>
25% completed
22% completed
</p>
<div
class="progress"
max="8"
max="9"
value="2"
>
<div
aria-valuemax="8"
aria-valuemax="9"
aria-valuemin="0"
aria-valuenow="2"
class="progress-bar"
role="progressbar"
style="width: 25%;"
style="width: 22.22222222222222%;"
>
<!---->
</div>
......@@ -94,6 +94,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Invite your colleagues"
src="http://example.com/images/illustration.svg"
/>
......@@ -151,6 +152,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Create or import a repository"
src="http://example.com/images/illustration.svg"
/>
......@@ -200,6 +202,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Set-up CI/CD"
src="http://example.com/images/illustration.svg"
/>
......@@ -249,6 +252,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Try GitLab Ultimate for free"
src="http://example.com/images/illustration.svg"
/>
......@@ -303,6 +307,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Add code owners"
src="http://example.com/images/illustration.svg"
/>
......@@ -357,6 +362,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Enable require merge approvals"
src="http://example.com/images/illustration.svg"
/>
......@@ -422,6 +428,57 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Create an issue"
src="http://example.com/images/illustration.svg"
/>
<h6>
Create an issue
</h6>
<p
class="gl-font-sm gl-text-gray-700"
>
Create/import issues (tickets) to collaborate on ideas and plan work.
</p>
<a
class="gl-link"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Create an issue
</a>
</div>
</div>
<!---->
</div>
</div>
<div
class="col gl-mb-6"
>
<div
class="gl-card gl-pt-0"
>
<!---->
<div
class="gl-card-body"
>
<div
class="gl-text-right gl-h-5"
>
<!---->
</div>
<div
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Submit a merge request (MR)"
src="http://example.com/images/illustration.svg"
/>
......@@ -487,6 +544,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
>
<img
alt="Run a Security scan using CI/CD"
src="http://example.com/images/illustration.svg"
/>
......
......@@ -31,6 +31,10 @@ exports[`Learn GitLab Section Card renders correctly 1`] = `
action="userAdded"
value="[object Object]"
/>
<learn-gitlab-section-link-stub
action="issueCreated"
value="[object Object]"
/>
<learn-gitlab-section-link-stub
action="gitWrite"
value="[object Object]"
......
......@@ -26,13 +26,13 @@ describe('Learn GitLab Design A', () => {
it('renders the progress percentage', () => {
const text = wrapper.find('[data-testid="completion-percentage"]').text();
expect(text).toEqual('25% completed');
expect(text).toBe('22% completed');
});
it('renders the progress bar with correct values', () => {
const progressBar = wrapper.find(GlProgressBar);
const progressBar = wrapper.findComponent(GlProgressBar);
expect(progressBar.attributes('value')).toBe('2');
expect(progressBar.attributes('max')).toBe('8');
expect(progressBar.attributes('max')).toBe('9');
});
});
......@@ -26,13 +26,13 @@ describe('Learn GitLab Design B', () => {
it('renders the progress percentage', () => {
const text = wrapper.find('[data-testid="completion-percentage"]').text();
expect(text).toEqual('25% completed');
expect(text).toBe('22% completed');
});
it('renders the progress bar with correct values', () => {
const progressBar = wrapper.find(GlProgressBar);
const progressBar = wrapper.findComponent(GlProgressBar);
expect(progressBar.attributes('value')).toBe('2');
expect(progressBar.attributes('max')).toBe('8');
expect(progressBar.attributes('max')).toBe('9');
});
});
......@@ -39,4 +39,9 @@ export const testActions = {
completed: false,
svg: 'http://example.com/images/illustration.svg',
},
issueCreated: {
url: 'http://example.com/',
completed: false,
svg: 'http://example.com/images/illustration.svg',
},
};
......@@ -39,6 +39,24 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
expect(result).to match_array([issue1])
end
it 'raises an exception if both assignee_username and assignee_wildcard_id are present' do
expect do
resolve_board_list_issues(args: { filters: { assignee_username: ['username'], assignee_wildcard_id: 'NONE' } })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'accepts assignee wildcard id NONE' do
result = resolve_board_list_issues(args: { filters: { assignee_wildcard_id: 'NONE' } })
expect(result).to match_array([issue1, issue2, issue3])
end
it 'accepts assignee wildcard id ANY' do
result = resolve_board_list_issues(args: { filters: { assignee_wildcard_id: 'ANY' } })
expect(result).to match_array([])
end
end
end
......
......@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['BoardIssueInput'] do
it { expect(described_class.graphql_name).to eq('BoardIssueInput') }
it 'exposes negated issue arguments' do
it 'has specific fields' do
allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername
releaseTag myReactionEmoji not search)
releaseTag myReactionEmoji not search assigneeWildcardId)
expect(described_class.arguments.keys).to include(*allowed_args)
expect(described_class.arguments['not'].type).to eq(Types::Boards::NegatedBoardIssueInputType)
......
......@@ -168,11 +168,13 @@ RSpec.describe ApplicationHelper do
it { expect(helper.active_when(false)).to eq(nil) }
end
describe '#promo_host' do
subject { helper.promo_host }
unless Gitlab.jh?
describe '#promo_host' do
subject { helper.promo_host }
it 'returns the url' do
is_expected.to eq('about.gitlab.com')
it 'returns the url' do
is_expected.to eq('about.gitlab.com')
end
end
end
......@@ -180,7 +182,7 @@ RSpec.describe ApplicationHelper do
subject { helper.promo_url }
it 'returns the url' do
is_expected.to eq('https://about.gitlab.com')
is_expected.to eq("https://#{helper.promo_host}")
end
it 'changes if promo_host changes' do
......@@ -194,7 +196,7 @@ RSpec.describe ApplicationHelper do
subject { helper.contact_sales_url }
it 'returns the url' do
is_expected.to eq('https://about.gitlab.com/sales')
is_expected.to eq("https://#{helper.promo_host}/sales")
end
it 'changes if promo_url changes' do
......
......@@ -27,6 +27,7 @@ RSpec.describe LearnGitlabHelper do
it 'has all actions' do
expect(onboarding_actions_data.keys).to contain_exactly(
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
......
......@@ -3,39 +3,41 @@
require 'spec_helper'
RSpec.describe ::Gitlab::SubscriptionPortal do
describe '.default_subscriptions_url' do
subject { described_class.default_subscriptions_url }
context 'on non test and non dev environments' do
before do
allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
unless Gitlab.jh?
describe '.default_subscriptions_url' do
subject { described_class.default_subscriptions_url }
context 'on non test and non dev environments' do
before do
allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
end
it 'returns production subscriptions app URL' do
is_expected.to eq('https://customers.gitlab.com')
end
end
it 'returns production subscriptions app URL' do
is_expected.to eq('https://customers.gitlab.com')
end
end
context 'on dev environment' do
before do
allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
end
context 'on dev environment' do
before do
allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
it 'returns staging subscriptions app url' do
is_expected.to eq('https://customers.stg.gitlab.com')
end
end
it 'returns staging subscriptions app url' do
is_expected.to eq('https://customers.stg.gitlab.com')
end
end
context 'on test environment' do
before do
allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
end
context 'on test environment' do
before do
allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
end
it 'returns staging subscriptions app url' do
is_expected.to eq('https://customers.stg.gitlab.com')
it 'returns staging subscriptions app url' do
is_expected.to eq('https://customers.stg.gitlab.com')
end
end
end
end
......
......@@ -247,75 +247,117 @@ RSpec.describe Gitlab do
end
end
describe '.ee?' do
describe 'ee? and jh?' do
before do
stub_env('FOSS_ONLY', nil) # Make sure the ENV is clean
# Make sure the ENV is clean
stub_env('FOSS_ONLY', nil)
stub_env('EE_ONLY', nil)
described_class.instance_variable_set(:@is_ee, nil)
described_class.instance_variable_set(:@is_jh, nil)
end
after do
described_class.instance_variable_set(:@is_ee, nil)
described_class.instance_variable_set(:@is_jh, nil)
end
context 'for EE' do
before do
root = Pathname.new('dummy')
license_path = double(:path, exist?: true)
def stub_path(*paths, **arguments)
root = Pathname.new('dummy')
pathname = double(:path, **arguments)
allow(described_class)
.to receive(:root)
.and_return(root)
allow(described_class)
.to receive(:root)
.and_return(root)
allow(root).to receive(:join)
paths.each do |path|
allow(root)
.to receive(:join)
.with('ee/app/models/license.rb')
.and_return(license_path)
.with(path)
.and_return(pathname)
end
end
context 'when using FOSS_ONLY=1' do
describe '.ee?' do
context 'for EE' do
before do
stub_env('FOSS_ONLY', '1')
stub_path('ee/app/models/license.rb', exist?: true)
end
it 'returns not to be EE' do
expect(described_class).not_to be_ee
context 'when using FOSS_ONLY=1' do
before do
stub_env('FOSS_ONLY', '1')
end
it 'returns not to be EE' do
expect(described_class).not_to be_ee
end
end
end
context 'when using FOSS_ONLY=0' do
before do
stub_env('FOSS_ONLY', '0')
context 'when using FOSS_ONLY=0' do
before do
stub_env('FOSS_ONLY', '0')
end
it 'returns to be EE' do
expect(described_class).to be_ee
end
end
it 'returns to be EE' do
expect(described_class).to be_ee
context 'when using default FOSS_ONLY' do
it 'returns to be EE' do
expect(described_class).to be_ee
end
end
end
context 'when using default FOSS_ONLY' do
it 'returns to be EE' do
expect(described_class).to be_ee
context 'for CE' do
before do
stub_path('ee/app/models/license.rb', exist?: false)
end
it 'returns not to be EE' do
expect(described_class).not_to be_ee
end
end
end
context 'for CE' do
before do
root = double(:path)
license_path = double(:path, exists?: false)
describe '.jh?' do
context 'for JH' do
before do
stub_path(
'ee/app/models/license.rb',
'jh',
exist?: true)
end
allow(described_class)
.to receive(:root)
.and_return(Pathname.new('dummy'))
context 'when using default FOSS_ONLY and EE_ONLY' do
it 'returns to be JH' do
expect(described_class).to be_jh
end
end
allow(root)
.to receive(:join)
.with('ee/app/models/license.rb')
.and_return(license_path)
end
context 'when using FOSS_ONLY=1' do
before do
stub_env('FOSS_ONLY', '1')
end
it 'returns not to be JH' do
expect(described_class).not_to be_jh
end
end
context 'when using EE_ONLY=1' do
before do
stub_env('EE_ONLY', '1')
end
it 'returns not to be EE' do
expect(described_class).not_to be_ee
it 'returns not to be JH' do
expect(described_class).not_to be_jh
end
end
end
end
end
......
......@@ -6,47 +6,6 @@ RSpec.describe Boards::Lists::UpdateService do
let(:user) { create(:user) }
let!(:list) { create(:list, board: board, position: 0) }
shared_examples 'moving list' do
context 'when user can admin list' do
it 'calls Lists::MoveService to update list position' do
board.resource_parent.add_developer(user)
expect(Boards::Lists::MoveService).to receive(:new).with(board.resource_parent, user, params).and_call_original
expect_any_instance_of(Boards::Lists::MoveService).to receive(:execute).with(list)
service.execute(list)
end
end
context 'when user cannot admin list' do
it 'does not call Lists::MoveService to update list position' do
expect(Boards::Lists::MoveService).not_to receive(:new)
service.execute(list)
end
end
end
shared_examples 'updating list preferences' do
context 'when user can read list' do
it 'updates list preference for user' do
board.resource_parent.add_guest(user)
service.execute(list)
expect(list.preferences_for(user).collapsed).to eq(true)
end
end
context 'when user cannot read list' do
it 'does not update list preference for user' do
service.execute(list)
expect(list.preferences_for(user).collapsed).to be_nil
end
end
end
describe '#execute' do
let(:service) { described_class.new(board.resource_parent, user, params) }
......
......@@ -55,6 +55,7 @@ require 'rainbow/ext/string'
Rainbow.enabled = false
require_relative('../ee/spec/spec_helper') if Gitlab.ee?
require_relative('../jh/spec/spec_helper') if Gitlab.jh?
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
......
# frozen_string_literal: true
RSpec.shared_examples 'moving list' do
context 'when user can admin list' do
it 'calls Lists::MoveService to update list position' do
board.resource_parent.add_developer(user)
expect_next_instance_of(Boards::Lists::MoveService, board.resource_parent, user, params) do |move_service|
expect(move_service).to receive(:execute).with(list).and_call_original
end
service.execute(list)
end
end
context 'when user cannot admin list' do
it 'does not call Lists::MoveService to update list position' do
expect(Boards::Lists::MoveService).not_to receive(:new)
service.execute(list)
end
end
end
RSpec.shared_examples 'updating list preferences' do
context 'when user can read list' do
it 'updates list preference for user' do
board.resource_parent.add_guest(user)
service.execute(list)
expect(list.preferences_for(user).collapsed).to eq(true)
end
end
context 'when user cannot read list' do
it 'does not update list preference for user' do
service.execute(list)
expect(list.preferences_for(user).collapsed).to be_falsy
end
end
end
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