summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/gl_dropdown.js4
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue12
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue3
-rw-r--r--app/assets/javascripts/jobs/store/actions.js26
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js19
-rw-r--r--app/assets/javascripts/jobs/store/state.js1
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js7
-rw-r--r--app/assets/javascripts/operation_settings/components/external_dashboard.vue7
-rw-r--r--app/helpers/projects_helper.rb24
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/serializers/build_details_entity.rb10
-rw-r--r--app/serializers/deployment_entity.rb33
-rw-r--r--app/serializers/pipeline_details_entity.rb5
-rw-r--r--app/serializers/pipeline_entity.rb2
-rw-r--r--app/views/profiles/_event_table.html.haml6
-rw-r--r--app/views/profiles/audit_log.html.haml4
-rw-r--r--app/views/profiles/preferences/show.html.haml26
-rw-r--r--app/views/projects/project_members/index.html.haml42
-rw-r--r--changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/api_make_protected_boolean_type.yml5
-rw-r--r--changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml5
-rw-r--r--changelogs/unreleased/fix-autodevops-postgres-versioning.yml5
-rw-r--r--changelogs/unreleased/fix-format-date-safari-ff.yml5
-rw-r--r--changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml5
-rw-r--r--config/initializers/01_secret_token.rb7
-rw-r--r--db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb16
-rw-r--r--db/migrate/20190524062810_generate_lets_encrypt_private_key.rb33
-rw-r--r--db/schema.rb4
-rw-r--r--doc/administration/high_availability/README.md6
-rw-r--r--doc/ci/caching/index.md2
-rw-r--r--doc/user/group/saml_sso/index.md21
-rw-r--r--doc/user/project/pages/getting_started_part_two.md2
-rw-r--r--lib/api/variables.rb4
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml1
-rw-r--r--lib/gitlab/diff/suggestion.rb2
-rw-r--r--lib/gitlab/lets_encrypt/client.rb2
-rw-r--r--locale/gitlab.pot48
-rw-r--r--package.json2
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb89
-rw-r--r--spec/fixtures/api/schemas/environment.json6
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js38
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js (renamed from spec/javascripts/lib/utils/datetime_utility_spec.js)22
-rw-r--r--spec/frontend/operation_settings/components/external_dashboard_spec.js15
-rw-r--r--spec/initializers/secret_token_spec.rb11
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js14
-rw-r--r--spec/javascripts/jobs/mock_data.js295
-rw-r--r--spec/javascripts/jobs/store/actions_spec.js105
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb4
-rw-r--r--spec/migrations/generate_lets_encrypt_private_key_spec.rb20
-rw-r--r--spec/serializers/build_details_entity_spec.rb24
-rw-r--r--spec/serializers/deployment_entity_spec.rb41
-rw-r--r--spec/serializers/pipeline_entity_spec.rb16
-rw-r--r--spec/services/suggestions/apply_service_spec.rb68
-rw-r--r--spec/services/suggestions/create_service_spec.rb20
-rw-r--r--yarn.lock8
56 files changed, 763 insertions, 454 deletions
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 1c6b18c0e03..6a4c1aab308 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -702,6 +702,10 @@ GitLabDropdown = (function() {
}
html = document.createElement('li');
+ if (rowHidden) {
+ html.style.display = 'none';
+ }
+
if (data === 'divider' || data === 'separator') {
html.className = data;
return html;
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 7594edfac27..79fb67d38cd 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -86,6 +86,7 @@ export default {
'isScrollTopDisabled',
'isScrolledToBottomBeforeReceivingTrace',
'hasError',
+ 'selectedStage',
]),
...mapGetters([
'headerTime',
@@ -121,7 +122,13 @@ export default {
// fetch the stages for the dropdown on the sidebar
job(newVal, oldVal) {
if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
- this.fetchStages();
+ const stages = this.job.pipeline.details.stages || [];
+
+ const defaultStage = stages.find(stage => stage && stage.name === this.selectedStage);
+
+ if (defaultStage) {
+ this.fetchJobsForStage(defaultStage);
+ }
}
if (newVal.archived) {
@@ -160,7 +167,7 @@ export default {
'setJobEndpoint',
'setTraceOptions',
'fetchJob',
- 'fetchStages',
+ 'fetchJobsForStage',
'hideSidebar',
'showSidebar',
'toggleSidebar',
@@ -269,7 +276,6 @@ export default {
:class="{ 'sticky-top border-bottom-0': hasTrace }"
>
<icon name="lock" class="align-text-bottom" />
-
{{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div>
<!-- job log -->
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 1691ac62100..24276c06486 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -34,7 +34,7 @@ export default {
},
},
computed: {
- ...mapState(['job', 'stages', 'jobs', 'selectedStage', 'isLoadingStages']),
+ ...mapState(['job', 'stages', 'jobs', 'selectedStage']),
coverage() {
return `${this.job.coverage}%`;
},
@@ -208,7 +208,6 @@ export default {
/>
<stages-dropdown
- v-if="!isLoadingStages"
:stages="stages"
:pipeline="job.pipeline"
:selected-stage="selectedStage"
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 8045f6dc3ff..12d67a43599 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -179,37 +179,13 @@ export const receiveTraceError = ({ commit }) => {
};
/**
- * Stages dropdown on sidebar
- */
-export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES);
-export const fetchStages = ({ state, dispatch }) => {
- dispatch('requestStages');
-
- axios
- .get(`${state.job.pipeline.path}.json`)
- .then(({ data }) => {
- // Set selected stage
- dispatch('receiveStagesSuccess', data.details.stages);
- const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
- dispatch('fetchJobsForStage', selectedStage);
- })
- .catch(() => dispatch('receiveStagesError'));
-};
-export const receiveStagesSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_STAGES_SUCCESS, data);
-export const receiveStagesError = ({ commit }) => {
- commit(types.RECEIVE_STAGES_ERROR);
- flash(__('An error occurred while fetching stages.'));
-};
-
-/**
* Jobs list on sidebar - depend on stages dropdown
*/
export const requestJobsForStage = ({ commit }, stage) =>
commit(types.REQUEST_JOBS_FOR_STAGE, stage);
// On stage click, set selected stage + fetch job
-export const fetchJobsForStage = ({ dispatch }, stage) => {
+export const fetchJobsForStage = ({ dispatch }, stage = {}) => {
dispatch('requestJobsForStage', stage);
axios
diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js
index fd098f13e90..39146b2eefd 100644
--- a/app/assets/javascripts/jobs/store/mutation_types.js
+++ b/app/assets/javascripts/jobs/store/mutation_types.js
@@ -24,10 +24,6 @@ export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
-export const REQUEST_STAGES = 'REQUEST_STAGES';
-export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
-export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
-
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const REQUEST_JOBS_FOR_STAGE = 'REQUEST_JOBS_FOR_STAGE';
export const RECEIVE_JOBS_FOR_STAGE_SUCCESS = 'RECEIVE_JOBS_FOR_STAGE_SUCCESS';
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index cd440d21c1f..ad08f27b147 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -65,6 +65,11 @@ export default {
state.isLoading = false;
state.job = job;
+ state.stages =
+ job.pipeline && job.pipeline.details && job.pipeline.details.stages
+ ? job.pipeline.details.stages
+ : [];
+
/**
* We only update it on the first request
* The dropdown can be changed by the user
@@ -101,19 +106,7 @@ export default {
state.isScrolledToBottomBeforeReceivingTrace = toggle;
},
- [types.REQUEST_STAGES](state) {
- state.isLoadingStages = true;
- },
- [types.RECEIVE_STAGES_SUCCESS](state, stages) {
- state.isLoadingStages = false;
- state.stages = stages;
- },
- [types.RECEIVE_STAGES_ERROR](state) {
- state.isLoadingStages = false;
- state.stages = [];
- },
-
- [types.REQUEST_JOBS_FOR_STAGE](state, stage) {
+ [types.REQUEST_JOBS_FOR_STAGE](state, stage = {}) {
state.isLoadingJobs = true;
state.selectedStage = stage.name;
},
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index 04825187c99..6019214e62c 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -25,7 +25,6 @@ export default () => ({
traceState: null,
// sidebar dropdown & list of jobs
- isLoadingStages: false,
isLoadingJobs: false,
selectedStage: '',
stages: [],
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 624878cb5d7..32cafb74d91 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -79,7 +79,12 @@ export const getDayName = date =>
* @param {date} datetime
* @returns {String}
*/
-export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+export const formatDate = datetime => {
+ if (_.isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
+ throw new Error('Invalid date');
+ }
+ return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+};
/**
* Timeago uses underscores instead of dashes to separate language from country code.
diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
index 0a87d193b72..b2b37355033 100644
--- a/app/assets/javascripts/operation_settings/components/external_dashboard.vue
+++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
+import initSettingsPanels from '~/settings_panels';
export default {
components: {
@@ -19,15 +20,19 @@ export default {
required: true,
},
},
+ mounted() {
+ initSettingsPanels();
+ },
};
</script>
<template>
- <section class="settings expanded">
+ <section class="settings no-animate">
<div class="settings-header">
<h4 class="js-section-header">
{{ s__('ExternalMetrics|External Dashboard') }}
</h4>
+ <gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
<p class="js-section-sub-header">
{{
s__(
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 91d15e0e4ea..f798bfbf703 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -319,6 +319,30 @@ module ProjectsHelper
Ability.allowed?(current_user, :admin_project_member, @project)
end
+ def project_can_be_shared?
+ !membership_locked? || @project.allowed_to_share_with_group?
+ end
+
+ def membership_locked?
+ false
+ end
+
+ def share_project_description(project)
+ share_with_group = project.allowed_to_share_with_group?
+ share_with_members = !membership_locked?
+
+ description =
+ if share_with_group && share_with_members
+ _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.")
+ elsif share_with_group
+ _("You can invite another group to <strong>%{project_name}</strong>.")
+ elsif share_with_members
+ _("You can invite a new member to <strong>%{project_name}</strong>.")
+ end
+
+ description.html_safe % { project_name: project.name }
+ end
+
private
def get_project_nav_tabs(project, current_user)
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index fb1e558e46c..bbe2d2e8fd4 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -257,6 +257,12 @@ class ApplicationSetting < ApplicationRecord
algorithm: 'aes-256-gcm',
encode: true
+ attr_encrypted :lets_encrypt_private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
before_validation :ensure_uuid!
before_validation :strip_sentry_values
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 62c26809eeb..6928968edc0 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -8,16 +8,18 @@ class BuildDetailsEntity < JobEntity
expose :stuck?, as: :stuck
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
+ expose :metadata, using: BuildMetadataEntity
expose :pipeline, using: PipelineEntity
expose :deployment_status, if: -> (*) { build.starts_environment? } do
expose :deployment_status, as: :status
-
- expose :persisted_environment, as: :environment, with: EnvironmentEntity
+ expose :persisted_environment, as: :environment do |build, options|
+ options.merge(deployment_details: false).yield_self do |opts|
+ EnvironmentEntity.represent(build.persisted_environment, opts)
+ end
+ end
end
- expose :metadata, using: BuildMetadataEntity
-
expose :artifact, if: -> (*) { can?(current_user, :read_build, build) } do
expose :download_path, if: -> (*) { build.artifacts? } do |build|
download_project_job_artifacts_path(project, build)
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 34ae06278c8..943c707218d 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -20,16 +20,39 @@ class DeploymentEntity < Grape::Entity
expose :created_at
expose :tag
expose :last?
-
expose :user, using: UserEntity
- expose :commit, using: CommitEntity
- expose :deployable, using: JobEntity
- expose :manual_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
- expose :scheduled_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
+
+ expose :deployable do |deployment, opts|
+ deployment.deployable.yield_self do |deployable|
+ if include_details?
+ JobEntity.represent(deployable, opts)
+ elsif can_read_deployables?
+ { name: deployable.name,
+ build_path: project_job_path(deployable.project, deployable) }
+ end
+ end
+ end
+
+ expose :commit, using: CommitEntity, if: -> (*) { include_details? }
+ expose :manual_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
+ expose :scheduled_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
private
+ def include_details?
+ options.fetch(:deployment_details, true)
+ end
+
def can_create_deployment?
can?(request.current_user, :create_deployment, request.project)
end
+
+ def can_read_deployables?
+ ##
+ # We intentionally do not check `:read_build, deployment.deployable`
+ # because it triggers a policy evaluation that involves multiple
+ # Gitaly calls that might not be cached.
+ #
+ can?(request.current_user, :read_build, request.project)
+ end
end
diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb
index d78ad4af4dc..dfef4364965 100644
--- a/app/serializers/pipeline_details_entity.rb
+++ b/app/serializers/pipeline_details_entity.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
class PipelineDetailsEntity < PipelineEntity
+ expose :flags do
+ expose :latest?, as: :latest
+ end
+
expose :details do
- expose :ordered_stages, as: :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity
expose :scheduled_actions, using: BuildActionEntity
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 8fe5df81e6c..9ef93b2387f 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -20,7 +20,6 @@ class PipelineEntity < Grape::Entity
end
expose :flags do
- expose :latest?, as: :latest
expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
expose :merge_request_event?, as: :merge_request
@@ -34,6 +33,7 @@ class PipelineEntity < Grape::Entity
expose :details do
expose :detailed_status, as: :status, with: DetailedStatusEntity
+ expose :ordered_stages, as: :stages, using: StageEntity
expose :duration
expose :finished_at
end
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index 9f525547dd9..977ff30d5a6 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -1,14 +1,12 @@
%h5.prepend-top-0
- History of authentications
+ = _('History of authentications')
%ul.content-list
- events.each do |event|
%li
%span.description
= audit_icon(event.details[:with], class: "append-right-5")
- Signed in with
- = event.details[:with]
- authentication
+ = _('Signed in with %{authentication} authentication') % { authentication: event.details[:with]}
%span.float-right= time_ago_with_tooltip(event.created_at)
= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index a924369050b..275c0428d34 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,4 +1,4 @@
-- page_title "Authentication log"
+- page_title _('Authentication log')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,6 +6,6 @@
%h4.prepend-top-0
= page_title
%p
- This is a security log of important events involving your account.
+ = _('This is a security log of important events involving your account.')
.col-lg-8
= render 'event_table', events: @events
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 58f2eb229ba..46384bc28ef 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,11 +1,11 @@
-- page_title 'Preferences'
+- page_title _('Preferences')
- @content_class = "limit-container-width" unless fluid_layout
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4.application-theme
%h4.prepend-top-0
= s_('Preferences|Navigation theme')
- %p Customize the appearance of the application header and navigation sidebar.
+ %p= _('Customize the appearance of the application header and navigation sidebar.')
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
@@ -18,11 +18,11 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Syntax highlighting theme
+ = _('Syntax highlighting theme')
%p
- This setting allows you to customize the appearance of the syntax.
+ = _('This setting allows you to customize the appearance of the syntax.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
.col-lg-8.syntax-theme
- Gitlab::ColorSchemes.each do |scheme|
= label_tag do
@@ -35,31 +35,31 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Behavior
+ = _('Behavior')
%p
- This setting allows you to customize the behavior of the system layout and default views.
+ = _('This setting allows you to customize the behavior of the system layout and default views.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
.col-lg-8
.form-group
= f.label :layout, class: 'label-bold' do
- Layout width
+ = _('Layout width')
= f.select :layout, layout_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose between fixed (max. 1280px) and fluid (100%) application layout.
+ = _('Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
.form-group
= f.label :dashboard, class: 'label-bold' do
- Default dashboard
+ = _('Default dashboard')
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
.form-group
= f.label :project_view, class: 'label-bold' do
- Project overview content
+ = _('Project overview content')
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose what content you want to see on a project’s overview page.
+ = _('Choose what content you want to see on a project’s overview page.')
.col-sm-12
%hr
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 8373903443e..cc98ba64f08 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,29 +1,35 @@
- page_title _("Members")
+- can_admin_project_members = can?(current_user, :admin_project_member, @project)
.row.prepend-top-default
.col-lg-12
- %h4
- = _("Project members")
- - if can?(current_user, :admin_project_member, @project)
- %p
- = _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.").html_safe % { project_name: sanitize(@project.name, tags: []) }
- - else
- %p
- = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
+ - if project_can_be_shared?
+ %h4
+ = _("Project members")
+ - if can_admin_project_members
+ %p= share_project_description(@project)
+ - else
+ %p
+ = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
+
.light
- - if can?(current_user, :admin_project_member, @project)
- %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
- %li.nav-tab{ role: 'presentation' }
- %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
- - if @project.allowed_to_share_with_group?
+ - if can_admin_project_members && project_can_be_shared?
+ - if !membership_locked? && @project.allowed_to_share_with_group?
+ %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
+ %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
+ %li.nav-tab{ role: 'presentation', class: ('active' if membership_locked?) }
%a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite group")
- .tab-content.gitlab-tab-content
- .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
- .tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
+ .tab-content.gitlab-tab-content
+ .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
+ = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
+ .tab-pane{ id: 'invite-group-pane', role: 'tabpanel', class: ('active' if membership_locked?) }
+ = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
+ - elsif !membership_locked?
+ .invite-member= render 'projects/project_members/new_project_member', tab_title: _('Invite member')
+ - elsif @project.allowed_to_share_with_group?
+ .invite-group= render 'projects/project_members/new_project_group', tab_title: _('Invite group')
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
.clearfix
diff --git a/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml
new file mode 100644
index 00000000000..821e5d70dca
--- /dev/null
+++ b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Moved EE/CE code differences for `app/assets/javascripts/gl_dropdown.js` into CE"
+merge_request: 28711
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/api_make_protected_boolean_type.yml b/changelogs/unreleased/api_make_protected_boolean_type.yml
new file mode 100644
index 00000000000..765edea6427
--- /dev/null
+++ b/changelogs/unreleased/api_make_protected_boolean_type.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: change protected attribute type to Boolean'
+merge_request: 28766
+author:
+type: other
diff --git a/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml
new file mode 100644
index 00000000000..2b5a3592775
--- /dev/null
+++ b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of jobs controller
+merge_request: 28093
+author:
+type: performance
diff --git a/changelogs/unreleased/fix-autodevops-postgres-versioning.yml b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml
new file mode 100644
index 00000000000..8ddc70f6314
--- /dev/null
+++ b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed ignored postgres version that occurs after the first autodevops deploy when specifying custom $POSTGRES_VERSION
+merge_request: 28735
+author: Brandon Dimcheff
+type: fixed
diff --git a/changelogs/unreleased/fix-format-date-safari-ff.yml b/changelogs/unreleased/fix-format-date-safari-ff.yml
new file mode 100644
index 00000000000..e71ea2867f3
--- /dev/null
+++ b/changelogs/unreleased/fix-format-date-safari-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Throw an error when formatDate's input is invalid
+merge_request: 28713
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml
new file mode 100644
index 00000000000..eae2d5f9b2a
--- /dev/null
+++ b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml
@@ -0,0 +1,5 @@
+---
+title: Allow removal of empty lines via suggestions
+merge_request: 28703
+author:
+type: fixed
diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb
index e24b5cbd510..9225a99a584 100644
--- a/config/initializers/01_secret_token.rb
+++ b/config/initializers/01_secret_token.rb
@@ -39,8 +39,7 @@ def create_tokens
secret_key_base: file_secret_key || generate_new_secure_token,
otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token,
db_key_base: generate_new_secure_token,
- openid_connect_signing_key: generate_new_rsa_private_key,
- lets_encrypt_private_key: generate_lets_encrypt_private_key
+ openid_connect_signing_key: generate_new_rsa_private_key
}
missing_secrets = set_missing_keys(defaults)
@@ -61,10 +60,6 @@ def generate_new_rsa_private_key
OpenSSL::PKey::RSA.new(2048).to_pem
end
-def generate_lets_encrypt_private_key
- OpenSSL::PKey::RSA.new(4096).to_pem
-end
-
def warn_missing_secret(secret)
warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml."
end
diff --git a/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb b/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb
new file mode 100644
index 00000000000..e1d3cca48d6
--- /dev/null
+++ b/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLetsEncryptPrivateKeyToApplicationSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :encrypted_lets_encrypt_private_key, :text
+ add_column :application_settings, :encrypted_lets_encrypt_private_key_iv, :text
+ end
+end
diff --git a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
new file mode 100644
index 00000000000..21d7049b998
--- /dev/null
+++ b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class GenerateLetsEncryptPrivateKey < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ class ApplicationSetting < ActiveRecord::Base
+ self.table_name = 'application_settings'
+
+ attr_encrypted :lets_encrypt_private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+ end
+
+ def up
+ ApplicationSetting.reset_column_information
+
+ private_key = OpenSSL::PKey::RSA.new(4096).to_pem
+ ApplicationSetting.find_each do |setting|
+ setting.update!(lets_encrypt_private_key: private_key)
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 412b5313b69..bb59af540fe 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190516011213) do
+ActiveRecord::Schema.define(version: 20190524062810) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -191,6 +191,8 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.boolean "lets_encrypt_terms_of_service_accepted", default: false, null: false
t.integer "elasticsearch_shards", default: 5, null: false
t.integer "elasticsearch_replicas", default: 1, null: false
+ t.text "encrypted_lets_encrypt_private_key"
+ t.text "encrypted_lets_encrypt_private_key_iv"
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index a23817228cb..e5701525077 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -64,8 +64,8 @@ larger one.
- 1 PostgreSQL node
- 1 Redis node
-- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq)
- 1 NFS/Gitaly storage server
+- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq)
#### Installation Instructions
@@ -88,9 +88,9 @@ in size, indicating that there is contention or not enough resources.
- 1 PostgreSQL node
- 1 Redis node
-- 2 or more GitLab application nodes (Unicorn, Workhorse)
-- 2 or more Sidekiq nodes
- 2 or more NFS/Gitaly storage servers
+- 2 or more Sidekiq nodes
+- 2 or more GitLab application nodes (Unicorn, Workhorse)
## High Availability Architecture Examples
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 3f72fe3e9eb..d703e106e76 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -100,7 +100,7 @@ From the perspective of the Runner, in order for cache to work effectively, one
of the following must be true:
- Use a single Runner for all your jobs.
-- Use multiple Runners (in autoscale mode or not) that use.
+- Use multiple Runners (in autoscale mode or not) that use
[distributed caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching),
where the cache is stored in S3 buckets (like shared Runners on GitLab.com).
- Use multiple Runners (not in autoscale mode) of the same architecture that
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 778dbaf7a29..62a3ef52c34 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -5,12 +5,12 @@
NOTE: **Note:**
This topic is for SAML on GitLab.com Silver tier and above. For SAML on self-managed GitLab instances, see [SAML OmniAuth Provider](../../../integration/saml.md).
-Currently SAML on GitLab.com can be used to automatically add users to a group, and does not yet sign users into GitLab.com. Users should already have an account on the GitLab instance, or can create one when logging in for the first time.
+SAML on GitLab.com allows users to be automatically added to a group, and then allows those users to sign into GitLab.com. Users should already have an account on the GitLab instance, or can create one when logging in for the first time.
User synchronization for GitLab.com is partially supported using [SCIM](scim_setup.md).
NOTE: **Note:**
-SAML SSO for groups is used only as a convenient way to add users and does not sync users between providers without using SCIM. If a group is not using SCIM, group Owners will still need to manage user accounts, such as removing users when necessary.
+SAML SSO for GitLab.com groups does not sync users between providers without using SCIM. If a group is not using SCIM, group Owners will still need to manage user accounts (for example, removing users when necessary).
## Configuring your Identity Provider
@@ -83,6 +83,23 @@ Once you've set up your identity provider to work with GitLab, you'll need to co
| OneLogin | [Use the OneLogin SAML Test Connector](https://onelogin.service-now.com/support?id=kb_article&sys_id=93f95543db109700d5505eea4b96198f) |
| Ping Identity | [Add and configure a new SAML application](https://docs.pingidentity.com/bundle/p1_enterpriseConfigSsoSaml_cas/page/enableAppWithoutURL.html) |
+## Linking SAML to your existing GitLab.com account
+
+To link SAML to your existing GitLab.com account:
+
+1. Sign in to your GitLab.com account.
+1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page.
+1. Visit the SSO URL and click **Authorize**.
+1. Enter your credentials on the Identity Provider if prompted.
+1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
+
+## Signing in to GitLab.com with SAML
+
+1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on a group's **Settings > SAML SSO** page. If configured, it might also be possible to sign in to GitLab starting from your Identity Provider.
+1. Visit the SSO URL and click the **Sign in with Single Sign-On** button.
+1. Enter your credentials on the Identity Provider if prompted.
+1. You will be signed in to GitLab.com and redirected to the group.
+
## Unlinking accounts
Users can unlink SAML for a group from their profile page. This can be helpful if:
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index faf51154e3b..b74520e6556 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -88,7 +88,7 @@ website from your project's **Settings > Pages**.
You can also take some **optional** further steps:
-- _Remove the fork relationship._ The fork relashionship is necessary to contribute back to the project you originally forked from. If you don't have any intentions to do so, you can remove it. To do so, navigate to your project's **Settings**, expand **Advanced settings**, and scroll down to **Remove fork relationship**:
+- _Remove the fork relationship._ The fork relationship is necessary to contribute back to the project you originally forked from. If you don't have any intentions to do so, you can remove it. To do so, navigate to your project's **Settings**, expand **Advanced settings**, and scroll down to **Remove fork relationship**:
![remove fork relationship](img/remove_fork_relationship.png)
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 38ff1b3d9cf..6c61392ed63 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -54,7 +54,7 @@ module API
params do
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
- optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
@@ -81,7 +81,7 @@ module API
params do
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
- optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 11569437cb6..1d55c64ec56 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -432,6 +432,7 @@ rollout 100%:
--set postgresql.postgresUser="$POSTGRES_USER" \
--set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
--set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set postgresql.imageTag="$POSTGRES_VERSION" \
--set application.migrateCommand="$DB_MIGRATE" \
$HELM_UPGRADE_EXTRA_ARGS \
--namespace="$KUBE_NAMESPACE" \
diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb
index 027c7a31bcf..4a3ac2106e2 100644
--- a/lib/gitlab/diff/suggestion.rb
+++ b/lib/gitlab/diff/suggestion.rb
@@ -33,6 +33,8 @@ module Gitlab
end
def to_content
+ return "" if @text.blank?
+
# The parsed suggestion doesn't have information about the correct
# ending characters (we may have a line break, or not), so we take
# this information from the last line being changed (last
diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb
index d7468b06767..5501f7981ec 100644
--- a/lib/gitlab/lets_encrypt/client.rb
+++ b/lib/gitlab/lets_encrypt/client.rb
@@ -45,7 +45,7 @@ module Gitlab
end
def private_key
- @private_key ||= OpenSSL::PKey.read(Gitlab::Application.secrets.lets_encrypt_private_key)
+ @private_key ||= OpenSSL::PKey.read(Gitlab::CurrentSettings.lets_encrypt_private_key)
end
def admin_email
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b1fe186d96a..0d47c574120 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -877,9 +877,6 @@ msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
-msgid "An error occurred while fetching stages."
-msgstr ""
-
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
@@ -1443,6 +1440,9 @@ msgstr ""
msgid "Begin with the selected commit"
msgstr ""
+msgid "Behavior"
+msgstr ""
+
msgid "Below are examples of regex for existing tools:"
msgstr ""
@@ -1851,6 +1851,9 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
+msgid "Choose between fixed (max. 1280px) and fluid (100%%) application layout."
+msgstr ""
+
msgid "Choose file…"
msgstr ""
@@ -1860,6 +1863,9 @@ msgstr ""
msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
+msgid "Choose what content you want to see on a project’s overview page."
+msgstr ""
+
msgid "Choose your merge method, options, checks, and set up a default merge request description template."
msgstr ""
@@ -3119,6 +3125,9 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
+msgid "Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3206,6 +3215,9 @@ msgstr ""
msgid "Default classification label"
msgstr ""
+msgid "Default dashboard"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
@@ -4937,6 +4949,9 @@ msgstr ""
msgid "History"
msgstr ""
+msgid "History of authentications"
+msgstr ""
+
msgid "Hook was successfully created."
msgstr ""
@@ -5638,6 +5653,9 @@ msgstr ""
msgid "Latest pipeline for this branch"
msgstr ""
+msgid "Layout width"
+msgstr ""
+
msgid "Lead"
msgstr ""
@@ -7564,6 +7582,9 @@ msgstr ""
msgid "Project name"
msgstr ""
+msgid "Project overview content"
+msgstr ""
+
msgid "Project slug"
msgstr ""
@@ -8875,6 +8896,9 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
msgid "Signing in using %{label} has been disabled"
msgstr ""
@@ -9349,6 +9373,9 @@ msgstr ""
msgid "Switch to the source to copy it to the clipboard"
msgstr ""
+msgid "Syntax highlighting theme"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
@@ -9909,6 +9936,9 @@ msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
msgid "This is the author's first Merge Request to this project."
msgstr ""
@@ -10029,6 +10059,12 @@ msgstr ""
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
+msgid "This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
msgid "This setting can be overridden in each project."
msgstr ""
@@ -11250,6 +11286,12 @@ msgstr ""
msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
msgstr ""
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
msgid "You can move around the graph by using the arrow keys."
msgstr ""
diff --git a/package.json b/package.json
index 381accff23b..a4ec2dce0df 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
- "@gitlab/svgs": "^1.62.0",
+ "@gitlab/svgs": "^1.63.0",
"@gitlab/ui": "^3.10.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index bd30d4ee88b..9ef00fff3b2 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -101,7 +101,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'GET show' do
+ describe 'GET show', :request_store do
let!(:job) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:second_job) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:third_job) { create(:ci_build, :failed) }
@@ -143,13 +143,24 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_developer(user)
sign_in(user)
- allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ allow_any_instance_of(Ci::Build)
+ .to receive(:merge_request)
+ .and_return(merge_request)
+ end
+
+ it 'does not serialize builds in exposed stages' do
+ get_show_json
- get_show(id: job.id, format: :json)
+ json_response.dig('pipeline', 'details', 'stages').tap do |stages|
+ expect(stages.map(&:keys).flatten)
+ .to eq %w[name title status path dropdown_path]
+ end
end
context 'when job failed' do
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
@@ -159,6 +170,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'when job is running' do
+ before do
+ get_show_json
+ end
+
context 'job is cancelable' do
let(:job) { create(:ci_build, :running, pipeline: pipeline) }
@@ -181,6 +196,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'when job has artifacts' do
+ before do
+ get_show_json
+ end
+
context 'with not expiry date' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
@@ -212,6 +231,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
it 'exposes empty state illustrations' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['status']['illustration']).to have_key('image')
@@ -224,6 +245,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
it 'does not exposes the deployment information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['deployment_status']).to be_nil
end
@@ -234,11 +257,20 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:environment) { create(:environment, project: project, name: 'staging', state: :available) }
let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) }
+ before do
+ create(:deployment, :success, environment: environment, project: project)
+ end
+
it 'exposes the deployment information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_schema('job/job_details')
- expect(json_response['deployment_status']["status"]).to eq 'creating'
- expect(json_response['deployment_status']["environment"]).not_to be_nil
+ expect(json_response.dig('deployment_status', 'status')).to eq 'creating'
+ expect(json_response.dig('deployment_status', 'environment')).not_to be_nil
+ expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil
+ expect(json_response.dig('deployment_status', 'environment', 'last_deployment'))
+ .not_to include('commit')
end
end
@@ -250,11 +282,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).to have_key('edit_path')
@@ -270,11 +302,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can not edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).not_to have_key('edit_path')
@@ -289,11 +321,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can not edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).not_to have_key('edit_path')
@@ -306,6 +338,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
@@ -319,6 +353,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
@@ -328,6 +364,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'settings_path' do
+ before do
+ get_show_json
+ end
+
context 'when user is developer' do
it 'settings_path is not available' do
expect(response).to have_gitlab_http_status(:ok)
@@ -354,6 +394,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when no trace is available' do
it 'has_trace is false' do
+ get_show_json
+
expect(response).to match_response_schema('job/job_details')
expect(json_response['has_trace']).to be false
end
@@ -363,17 +405,21 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
it "has_trace is true" do
+ get_show_json
+
expect(response).to match_response_schema('job/job_details')
expect(json_response['has_trace']).to be true
end
end
it 'exposes the stage the job belongs to' do
+ get_show_json
+
expect(json_response['stage']).to eq('test')
end
end
- context 'when requesting JSON job is triggered' do
+ context 'when requesting triggered job JSON' do
let!(:merge_request) { create(:merge_request, source_project: project) }
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
@@ -383,15 +429,15 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_developer(user)
sign_in(user)
- allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ allow_any_instance_of(Ci::Build)
+ .to receive(:merge_request)
+ .and_return(merge_request)
end
context 'with no variables' do
- before do
- get_show(id: job.id, format: :json)
- end
-
it 'exposes trigger information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['trigger']['short_token']).to eq 'toke'
@@ -408,7 +454,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
- get_show(id: job.id, format: :json)
+ get_show_json
end
it 'returns a job_detail' do
@@ -432,7 +478,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'user is not a mantainer' do
before do
- get_show(id: job.id, format: :json)
+ get_show_json
end
it 'returns a job_detail' do
@@ -456,6 +502,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
+ def get_show_json
+ expect { get_show(id: job.id, format: :json) }
+ .not_to change { Gitlab::GitalyClient.get_request_count }
+ end
+
def get_show(**extra_params)
params = {
namespace_id: project.namespace.to_param,
diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json
index 9a10ab18c30..5b1e3c049fa 100644
--- a/spec/fixtures/api/schemas/environment.json
+++ b/spec/fixtures/api/schemas/environment.json
@@ -31,7 +31,11 @@
"last_deployment": {
"oneOf": [
{ "type": "null" },
- { "$ref": "deployment.json" }
+ { "$ref": "deployment.json" },
+ {
+ "name": { "type": "string" },
+ "build_path": { "type": "string" }
+ }
]
}
},
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index d7908efcf13..343301b8716 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -150,44 +150,8 @@ describe('Jobs Store Mutations', () => {
});
});
- describe('REQUEST_STAGES', () => {
- it('sets isLoadingStages to true', () => {
- mutations[types.REQUEST_STAGES](stateCopy);
-
- expect(stateCopy.isLoadingStages).toEqual(true);
- });
- });
-
- describe('RECEIVE_STAGES_SUCCESS', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]);
- });
-
- it('sets isLoadingStages to false', () => {
- expect(stateCopy.isLoadingStages).toEqual(false);
- });
-
- it('sets stages', () => {
- expect(stateCopy.stages).toEqual([{ name: 'build' }]);
- });
- });
-
- describe('RECEIVE_STAGES_ERROR', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_STAGES_ERROR](stateCopy);
- });
-
- it('sets isLoadingStages to false', () => {
- expect(stateCopy.isLoadingStages).toEqual(false);
- });
-
- it('resets stages', () => {
- expect(stateCopy.stages).toEqual([]);
- });
- });
-
describe('REQUEST_JOBS_FOR_STAGE', () => {
- it('sets isLoadingStages to true', () => {
+ it('sets isLoadingJobs to true', () => {
mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
expect(stateCopy.isLoadingJobs).toEqual(true);
diff --git a/spec/javascripts/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index 5327ec9d2a0..9f49e68cfe8 100644
--- a/spec/javascripts/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -65,6 +65,26 @@ describe('Date time utils', () => {
});
});
+ describe('formatDate', () => {
+ it('should format date properly', () => {
+ const formattedDate = datetimeUtility.formatDate(new Date('07/23/2016'));
+
+ expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000');
+ });
+
+ it('should format ISO date properly', () => {
+ const formattedDate = datetimeUtility.formatDate('2016-07-23T00:00:00.559Z');
+
+ expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000');
+ });
+
+ it('should throw an error if date is invalid', () => {
+ expect(() => {
+ datetimeUtility.formatDate('2016-07-23 00:00:00 UTC');
+ }).toThrow(new Error('Invalid date'));
+ });
+ });
+
describe('get day difference', () => {
it('should return 7', () => {
const firstDay = new Date('07/01/2016');
@@ -380,7 +400,7 @@ describe('prettyTime methods', () => {
describe('calculateRemainingMilliseconds', () => {
beforeEach(() => {
- spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime());
+ jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
});
it('calculates the remaining time for a given end date', () => {
diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js
index de1dd219fe0..4d35d35965c 100644
--- a/spec/frontend/operation_settings/components/external_dashboard_spec.js
+++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js
@@ -2,6 +2,9 @@ import { shallowMount } from '@vue/test-utils';
import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui';
import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue';
import { TEST_HOST } from 'helpers/test_constants';
+import initSettingsPanels from '~/settings_panels';
+
+jest.mock('~/settings_panels');
describe('operation settings external dashboard component', () => {
let wrapper;
@@ -21,6 +24,18 @@ describe('operation settings external dashboard component', () => {
expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard');
});
+ describe('expand/collapse button', () => {
+ it('is properly instantiated as a settings panel', () => {
+ expect(initSettingsPanels).toHaveBeenCalled();
+ });
+
+ it('defaults to collapsed state', () => {
+ const button = wrapper.find(GlButton);
+
+ expect(button.text()).toBe('Expand');
+ });
+ });
+
describe('sub-header', () => {
let subHeader;
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index 77bc28a6b07..726ce07a2d1 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -45,21 +45,11 @@ describe 'create_tokens' do
expect(keys).to all(match(RSA_KEY))
end
- it "generates private key for Let's Encrypt" do
- create_tokens
-
- keys = secrets.values_at(:lets_encrypt_private_key)
-
- expect(keys.uniq).to eq(keys)
- expect(keys).to all(match(RSA_KEY))
- end
-
it 'warns about the secrets to add to secrets.yml' do
expect(self).to receive(:warn_missing_secret).with('secret_key_base')
expect(self).to receive(:warn_missing_secret).with('otp_key_base')
expect(self).to receive(:warn_missing_secret).with('db_key_base')
expect(self).to receive(:warn_missing_secret).with('openid_connect_signing_key')
- expect(self).to receive(:warn_missing_secret).with('lets_encrypt_private_key')
create_tokens
end
@@ -88,7 +78,6 @@ describe 'create_tokens' do
before do
secrets.db_key_base = 'db_key_base'
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
- secrets.lets_encrypt_private_key = 'lets_encrypt_private_key'
allow(File).to receive(:exist?).with('.secret').and_return(true)
allow(File).to receive(:read).with('.secret').and_return('file_key')
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index 26d9effcac5..740bc3d0491 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import sidebarDetailsBlock from '~/jobs/components/sidebar.vue';
import createStore from '~/jobs/store';
-import job, { stages, jobsInStage } from '../mock_data';
+import job, { jobsInStage } from '../mock_data';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { trimText } from '../../helpers/text_helper';
@@ -131,18 +131,8 @@ describe('Sidebar details block', () => {
store.dispatch('receiveJobSuccess', job);
});
- describe('while fetching stages', () => {
- it('it does not render dropdown', () => {
- store.dispatch('requestStages');
- vm = mountComponentWithStore(SidebarComponent, { store });
-
- expect(vm.$el.querySelector('.js-selected-stage')).toBeNull();
- });
- });
-
describe('with stages', () => {
beforeEach(() => {
- store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
@@ -156,7 +146,6 @@ describe('Sidebar details block', () => {
describe('without jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
@@ -168,7 +157,6 @@ describe('Sidebar details block', () => {
describe('with jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveStagesSuccess', stages);
store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
vm = mountComponentWithStore(SidebarComponent, { store });
});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 1a7f338c5fa..3d40e94d219 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -3,140 +3,6 @@ import { TEST_HOST } from 'spec/test_constants';
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
-export default {
- id: 4757,
- name: 'test',
- build_path: '/root/ci-mock/-/jobs/4757',
- retry_path: '/root/ci-mock/-/jobs/4757/retry',
- cancel_path: '/root/ci-mock/-/jobs/4757/cancel',
- new_issue_path: '/root/ci-mock/issues/new',
- playable: false,
- created_at: threeWeeksAgo.toISOString(),
- updated_at: threeWeeksAgo.toISOString(),
- finished_at: threeWeeksAgo.toISOString(),
- queued: 9.54,
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/-/jobs/4757/retry',
- method: 'post',
- },
- },
- coverage: 20,
- erased_at: threeWeeksAgo.toISOString(),
- erased: false,
- duration: 6.785563,
- tags: ['tag'],
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- erase_path: '/root/ci-mock/-/jobs/4757/erase',
- artifacts: [null],
- runner: {
- id: 1,
- description: 'local ci runner',
- edit_path: '/root/ci-mock/runners/1/edit',
- },
- pipeline: {
- id: 140,
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- active: false,
- coverage: null,
- source: 'unknown',
- created_at: '2017-05-24T09:59:58.634Z',
- updated_at: '2017-06-01T17:32:00.062Z',
- path: '/root/ci-mock/pipelines/140',
- flags: {
- latest: true,
- stuck: false,
- yaml_errors: false,
- retryable: false,
- cancelable: false,
- },
- details: {
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/pipelines/140',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- },
- duration: 6,
- finished_at: '2017-06-01T17:32:00.042Z',
- },
- ref: {
- name: 'abc',
- path: '/root/ci-mock/commits/abc',
- tag: false,
- branch: true,
- },
- commit: {
- id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- short_id: 'c5864777',
- title: 'Add new file',
- created_at: '2017-05-24T10:59:52.000+01:00',
- parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'],
- message: 'Add new file',
- author_name: 'Root',
- author_email: 'admin@example.com',
- authored_date: '2017-05-24T10:59:52.000+01:00',
- committer_name: 'Root',
- committer_email: 'admin@example.com',
- committed_date: '2017-05-24T10:59:52.000+01:00',
- author: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- author_gravatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- commit_url:
- 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- },
- },
- metadata: {
- timeout_human_readable: '1m 40s',
- timeout_source: 'runner',
- },
- merge_request: {
- iid: 2,
- path: '/root/ci-mock/merge_requests/2',
- },
- raw_path: '/root/ci-mock/builds/4757/raw',
- has_trace: true,
-};
-
export const stages = [
{
name: 'build',
@@ -1043,6 +909,167 @@ export const stages = [
},
];
+export default {
+ id: 4757,
+ name: 'test',
+ build_path: '/root/ci-mock/-/jobs/4757',
+ retry_path: '/root/ci-mock/-/jobs/4757/retry',
+ cancel_path: '/root/ci-mock/-/jobs/4757/cancel',
+ new_issue_path: '/root/ci-mock/issues/new',
+ playable: false,
+ created_at: threeWeeksAgo.toISOString(),
+ updated_at: threeWeeksAgo.toISOString(),
+ finished_at: threeWeeksAgo.toISOString(),
+ queued: 9.54,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/-/jobs/4757/retry',
+ method: 'post',
+ },
+ },
+ coverage: 20,
+ erased_at: threeWeeksAgo.toISOString(),
+ erased: false,
+ duration: 6.785563,
+ tags: ['tag'],
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ erase_path: '/root/ci-mock/-/jobs/4757/erase',
+ artifacts: [null],
+ runner: {
+ id: 1,
+ description: 'local ci runner',
+ edit_path: '/root/ci-mock/runners/1/edit',
+ },
+ pipeline: {
+ id: 140,
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ active: false,
+ coverage: null,
+ source: 'unknown',
+ created_at: '2017-05-24T09:59:58.634Z',
+ updated_at: '2017-06-01T17:32:00.062Z',
+ path: '/root/ci-mock/pipelines/140',
+ flags: {
+ latest: true,
+ stuck: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: false,
+ },
+ details: {
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/140',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ duration: 6,
+ finished_at: '2017-06-01T17:32:00.042Z',
+ stages: [
+ {
+ dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build',
+ name: 'build',
+ path: '/jashkenas/underscore/pipelines/16#build',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ },
+ title: 'build: passed',
+ },
+ {
+ dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test',
+ name: 'test',
+ path: '/jashkenas/underscore/pipelines/16#test',
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ },
+ title: 'test: passed with warnings',
+ },
+ ],
+ },
+ ref: {
+ name: 'abc',
+ path: '/root/ci-mock/commits/abc',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ short_id: 'c5864777',
+ title: 'Add new file',
+ created_at: '2017-05-24T10:59:52.000+01:00',
+ parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'],
+ message: 'Add new file',
+ author_name: 'Root',
+ author_email: 'admin@example.com',
+ authored_date: '2017-05-24T10:59:52.000+01:00',
+ committer_name: 'Root',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-05-24T10:59:52.000+01:00',
+ author: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ commit_url:
+ 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ },
+ },
+ metadata: {
+ timeout_human_readable: '1m 40s',
+ timeout_source: 'runner',
+ },
+ merge_request: {
+ iid: 2,
+ path: '/root/ci-mock/merge_requests/2',
+ },
+ raw_path: '/root/ci-mock/builds/4757/raw',
+ has_trace: true,
+};
+
export const jobsInStage = {
name: 'build',
title: 'build: running',
diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js
index 77b44995b12..7b96df85b82 100644
--- a/spec/javascripts/jobs/store/actions_spec.js
+++ b/spec/javascripts/jobs/store/actions_spec.js
@@ -16,10 +16,6 @@ import {
stopPollingTrace,
receiveTraceSuccess,
receiveTraceError,
- requestStages,
- fetchStages,
- receiveStagesSuccess,
- receiveStagesError,
requestJobsForStage,
fetchJobsForStage,
receiveJobsForStageSuccess,
@@ -307,107 +303,6 @@ describe('Job State actions', () => {
});
});
- describe('requestStages', () => {
- it('should commit REQUEST_STAGES mutation ', done => {
- testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done);
- });
- });
-
- describe('fetchStages', () => {
- let mock;
-
- beforeEach(() => {
- mockedState.job.pipeline = {
- path: `${TEST_HOST}/endpoint`,
- };
- mockedState.selectedStage = 'deploy';
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('success', () => {
- it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
- mock
- .onGet(`${TEST_HOST}/endpoint.json`)
- .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } });
-
- testAction(
- fetchStages,
- null,
- mockedState,
- [],
- [
- {
- type: 'requestStages',
- },
- {
- payload: [{ name: 'build' }, { name: 'deploy' }],
- type: 'receiveStagesSuccess',
- },
- {
- payload: { name: 'deploy' },
- type: 'fetchJobsForStage',
- },
- ],
- done,
- );
- });
- });
-
- describe('error', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
- });
-
- it('dispatches requestStages and receiveStagesError ', done => {
- testAction(
- fetchStages,
- null,
- mockedState,
- [],
- [
- {
- type: 'requestStages',
- },
- {
- type: 'receiveStagesError',
- },
- ],
- done,
- );
- });
- });
- });
-
- describe('receiveStagesSuccess', () => {
- it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => {
- testAction(
- receiveStagesSuccess,
- {},
- mockedState,
- [{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }],
- [],
- done,
- );
- });
- });
-
- describe('receiveStagesError', () => {
- it('should commit RECEIVE_STAGES_ERROR mutation ', done => {
- testAction(
- receiveStagesError,
- null,
- mockedState,
- [{ type: types.RECEIVE_STAGES_ERROR }],
- [],
- done,
- );
- });
- });
-
describe('requestJobsForStage', () => {
it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
testAction(
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index 16a16acfd25..d63a2fbee04 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -5,12 +5,14 @@ require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Client do
include LetsEncryptHelpers
+ set(:private_key) { OpenSSL::PKey::RSA.new(4096).to_pem }
let(:client) { described_class.new }
before do
stub_application_setting(
lets_encrypt_notification_email: 'myemail@test.example.com',
- lets_encrypt_terms_of_service_accepted: true
+ lets_encrypt_terms_of_service_accepted: true,
+ lets_encrypt_private_key: private_key
)
end
diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
new file mode 100644
index 00000000000..f47cc0c36ef
--- /dev/null
+++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_private_key.rb')
+
+describe GenerateLetsEncryptPrivateKey, :migration do
+ describe '#up' do
+ let(:applications_settings) { table(:applications_settings) }
+
+ it 'generates RSA private key and saves it in application settings' do
+ application_setting = described_class::ApplicationSetting.create!
+
+ described_class.new.up
+ application_setting.reload
+
+ expect(application_setting.lets_encrypt_private_key).to be_present
+ expect do
+ OpenSSL::PKey::RSA.new(application_setting.lets_encrypt_private_key)
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index 1edf69dc290..9c2e5c79a9d 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -122,5 +122,29 @@ describe BuildDetailsEntity do
it { is_expected.to include(failure_reason: 'unmet_prerequisites') }
end
+
+ context 'when a build has environment with latest deployment' do
+ let(:build) do
+ create(:ci_build, :running, environment: environment.name, pipeline: pipeline)
+ end
+
+ let(:environment) do
+ create(:environment, project: project, name: 'staging', state: :available)
+ end
+
+ before do
+ create(:deployment, :success, environment: environment, project: project)
+
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ it 'does not serialize latest deployment commit and associated builds' do
+ response = subject.with_indifferent_access
+
+ response.dig(:deployment_status, :environment, :last_deployment).tap do |deployment|
+ expect(deployment).not_to include(:commit, :manual_actions, :scheduled_actions)
+ end
+ end
+ end
end
end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 894fd7a0a12..76ad2aee5c5 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -10,6 +10,7 @@ describe DeploymentEntity do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:entity) { described_class.new(deployment, request: request) }
+
subject { entity.as_json }
before do
@@ -47,6 +48,16 @@ describe DeploymentEntity do
expect(subject[:manual_actions]).not_to be_present
end
end
+
+ context 'when deployment details serialization was disabled' do
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize manual actions details' do
+ expect(subject.with_indifferent_access).not_to include(:manual_actions)
+ end
+ end
end
describe 'scheduled_actions' do
@@ -69,5 +80,35 @@ describe DeploymentEntity do
expect(subject[:scheduled_actions]).to be_empty
end
end
+
+ context 'when deployment details serialization was disabled' do
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize scheduled actions details' do
+ expect(subject.with_indifferent_access).not_to include(:scheduled_actions)
+ end
+ end
+ end
+
+ context 'when deployment details serialization was disabled' do
+ include Gitlab::Routing
+
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize deployment details' do
+ expect(subject.with_indifferent_access)
+ .not_to include(:commit, :manual_actions, :scheduled_actions)
+ end
+
+ it 'only exposes deployable name and path' do
+ project_job_path(project, deployment.deployable).tap do |path|
+ expect(subject.fetch(:deployable))
+ .to eq(name: 'test', build_path: path)
+ end
+ end
end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 47f767ae4ab..6be612ec226 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -48,8 +48,8 @@ describe PipelineEntity do
it 'contains flags' do
expect(subject).to include :flags
expect(subject[:flags])
- .to include :latest, :stuck, :auto_devops,
- :yaml_errors, :retryable, :cancelable, :merge_request
+ .to include :stuck, :auto_devops, :yaml_errors,
+ :retryable, :cancelable, :merge_request
end
end
@@ -64,6 +64,12 @@ describe PipelineEntity do
create(:ci_build, :failed, pipeline: pipeline)
end
+ it 'does not serialize stage builds' do
+ subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage|
+ expect(stage).not_to include(:groups, :latest_statuses, :retries)
+ end
+ end
+
context 'user has ability to retry pipeline' do
before do
project.add_developer(user)
@@ -92,6 +98,12 @@ describe PipelineEntity do
create(:ci_build, :pending, pipeline: pipeline)
end
+ it 'does not serialize stage builds' do
+ subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage|
+ expect(stage).not_to include(:groups, :latest_statuses, :retries)
+ end
+ end
+
context 'user has ability to cancel pipeline' do
before do
project.add_developer(user)
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 7732767137c..bdbcb0fdb07 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -5,6 +5,16 @@ require 'spec_helper'
describe Suggestions::ApplyService do
include ProjectForksHelper
+ def build_position(args = {})
+ default_args = { old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs }
+
+ Gitlab::Diff::Position.new(default_args.merge(args))
+ end
+
shared_examples 'successfully creates commit and updates suggestion' do
def apply(suggestion)
result = subject.execute(suggestion)
@@ -43,13 +53,7 @@ describe Suggestions::ApplyService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :commit_email) }
- let(:position) do
- Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 9,
- diff_refs: merge_request.diff_refs)
- end
+ let(:position) { build_position }
let(:diff_note) do
create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project)
@@ -333,6 +337,56 @@ describe Suggestions::ApplyService do
it_behaves_like 'successfully creates commit and updates suggestion'
end
+
+ context 'remove an empty line suggestion' do
+ let(:expected_content) do
+ <<~CONTENT
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+ end
+
+ let(:position) { build_position(new_line: 13) }
+ let(:suggestion) do
+ create(:suggestion, :content_from_repo, note: diff_note, to_content: "")
+ end
+
+ it_behaves_like 'successfully creates commit and updates suggestion'
+ end
end
context 'fork-project' do
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index ce4990a34a4..ccd44e615a8 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -151,6 +151,26 @@ describe Suggestions::CreateService do
subject.execute
end
end
+
+ context 'when a patch removes an empty line' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ ```
+ MARKDOWN
+ end
+ let(:position) { build_position(new_line: 13) }
+
+ it 'creates an appliable suggestion' do
+ subject.execute
+
+ suggestion = note.suggestions.last
+
+ expect(suggestion).to be_appliable
+ expect(suggestion.from_content).to eq("\n")
+ expect(suggestion.to_content).to eq("")
+ end
+ end
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 80178b04ba8..b7947415551 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -685,10 +685,10 @@
eslint-plugin-promise "^4.1.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.62.0":
- version "1.62.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.62.0.tgz#7072220b7c3aeb31d49b770888ff923b31e44e39"
- integrity sha512-xBmIidLfJZ3N9OJTsrtphsCbmenGm25AJe6s9Hsm9ikuSJ9FLBbwDJBORw95CSmajB0GdgbkPoTOWbIh1V9KgA==
+"@gitlab/svgs@^1.63.0":
+ version "1.63.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.63.0.tgz#9dd544026d203e4ce6efed72b05db68f710c4d49"
+ integrity sha512-YztrReFTg31B7v5wtUC5j15KHNcMebtW+kACytEU42XomMaIwk4USIbygqWlq0VRHA2VHJrHApfJHIjxiCCQcA==
"@gitlab/ui@^3.10.0":
version "3.10.0"