Commit 45a4bc30 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents c7e6c8ef 4891e8a5
......@@ -8,6 +8,7 @@ require:
- rubocop-rspec
AllCops:
TargetRubyVersion: 2.5
TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
......@@ -184,3 +185,8 @@ Cop/InjectEnterpriseEditionModule:
Style/ReturnNil:
Enabled: true
# It isn't always safe to replace `=~` with `.match?`, especially when there are
# nil values on the left hand side
Performance/RegexpMatch:
Enabled: false
<script>
import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view_mixin';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
......@@ -8,7 +9,7 @@ export default {
StopEnvironmentModal,
},
mixins: [environmentsMixin, CIPaginationMixin],
mixins: [environmentsMixin, CIPaginationMixin, folderMixin],
props: {
endpoint: {
......@@ -41,7 +42,8 @@ export default {
<div v-if="!isLoading" class="top-area">
<h4 class="js-folder-name environments-folder-name">
{{ s__('Environments|Environments') }} / <b>{{ folderName }}</b>
{{ s__('Environments|Environments') }} /
<b>{{ folderName }}</b>
</h4>
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
......@@ -52,6 +54,11 @@ export default {
:environments="state.environments"
:pagination="state.paginationInformation"
:can-read-environment="canReadEnvironment"
:canary-deployment-feature-id="canaryDeploymentFeatureId"
:show-canary-deployment-callout="showCanaryDeploymentCallout"
:user-callouts-path="userCalloutsPath"
:lock-promotion-svg-path="lockPromotionSvgPath"
:help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
/>
</div>
......
import Vue from 'vue';
import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsComponent from './components/environments_app.vue';
import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
......@@ -11,6 +12,7 @@ export default () =>
components: {
environmentsComponent,
},
mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
......@@ -32,6 +34,7 @@ export default () =>
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canReadEnvironment: this.canReadEnvironment,
...this.canaryCalloutProps,
},
});
},
......
export default {
computed: {
canaryCalloutProps() {},
},
};
export default {
props: {
canaryDeploymentFeatureId: {
type: String,
required: false,
default: '',
},
showCanaryDeploymentCallout: {
type: Boolean,
required: false,
default: false,
},
userCalloutsPath: {
type: String,
required: false,
default: '',
},
lockPromotionSvgPath: {
type: String,
required: false,
default: '',
},
helpCanaryDeploymentsPath: {
type: String,
required: false,
default: '',
},
},
};
......@@ -3,13 +3,13 @@
*/
import _ from 'underscore';
import Visibility from 'visibilityjs';
import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
......
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { setDeployBoard } from 'ee_else_ce/environments/stores/helpers';
/**
* Environments Store.
*
......@@ -31,6 +33,14 @@ export default class EnvironmentsStore {
* If the `size` is bigger than 1, it means it should be rendered as a folder.
* In those cases we add `isFolder` key in order to render it properly.
*
* Top level environments - when the size is 1 - with `rollout_status`
* can render a deploy board. We add `isDeployBoardVisible` and `deployBoardData`
* keys to those environments.
* The first key will let's us know if we should or not render the deploy board.
* It will be toggled when the user clicks to seee the deploy board.
*
* The second key will allow us to update the environment with the received deploy board data.
*
* @param {Array} environments
* @returns {Array}
*/
......@@ -63,6 +73,7 @@ export default class EnvironmentsStore {
filtered = Object.assign(filtered, env);
}
filtered = setDeployBoard(oldEnvironmentState, filtered);
return filtered;
});
......@@ -71,6 +82,20 @@ export default class EnvironmentsStore {
return filteredEnvironments;
}
/**
* Stores the pagination information needed to render the pagination for the
* table.
*
* Normalizes the headers to uppercase since they can be provided either
* in uppercase or lowercase.
*
* Parses to an integer the normalized ones needed for the pagination component.
*
* Stores the normalized and parsed information.
*
* @param {Object} pagination = {}
* @return {Object}
*/
setPagination(pagination = {}) {
const normalizedHeaders = normalizeHeaders(pagination);
const paginationInformation = parseIntPagination(normalizedHeaders);
......
/**
* Deploy boards are EE only.
*
* @param {Object} environment
* @returns {Object}
*/
// eslint-disable-next-line import/prefer-default-export
export const setDeployBoard = (oldEnvironmentState, environment) => environment;
import initGroupDetails from '../shared/group_details';
document.addEventListener('DOMContentLoaded', () => {
initGroupDetails('details');
});
/* eslint-disable no-new */
import { getPagePath } from '~/lib/utils/common_utils';
import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
import NewGroupChild from '~/groups/new_group_child';
import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs';
export default function initGroupDetails(actionName = 'show') {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
const paths = window.location.pathname.split('/');
const subpath = paths[paths.length - 1];
let action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
if (actionName && action === actionName) {
action = 'show'; // 'show' resets GroupTabs to default action through base class
}
new GroupTabs({ parentEl: '.groups-listing', action });
new ShortcutsNavigation();
new NotificationsForm();
notificationsDropdown();
new ProjectsList();
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
}
/* eslint-disable no-new */
import { getPagePath } from '~/lib/utils/common_utils';
import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
import NewGroupChild from '~/groups/new_group_child';
import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs';
import initGroupDetails from '../shared/group_details';
document.addEventListener('DOMContentLoaded', () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
const paths = window.location.pathname.split('/');
const subpath = paths[paths.length - 1];
const action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
new GroupTabs({ parentEl: '.groups-listing', action });
new ShortcutsNavigation();
new NotificationsForm();
notificationsDropdown();
new ProjectsList();
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
initGroupDetails();
});
......@@ -91,6 +91,7 @@ export default class UserTabs {
this.actions = Object.keys(this.loaded);
this.bindEvents();
// TODO: refactor to make this configurable via constructor params with a default value of 'show'
if (this.action === 'show') {
this.action = this.defaultAction;
}
......
<script>
import _ from 'underscore';
import stageColumnMixin from 'ee_else_ce/pipelines/mixins/stage_column_mixin';
import JobItem from './job_item.vue';
import JobGroupDropdown from './job_group_dropdown.vue';
......@@ -8,6 +9,7 @@ export default {
JobItem,
JobGroupDropdown,
},
mixins: [stageColumnMixin],
props: {
title: {
type: String,
......@@ -32,9 +34,6 @@ export default {
groupId(group) {
return `ci-badge-${_.escape(group.name)}`;
},
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
pipelineActionRequestComplete() {
this.$emit('refreshPipelineGraph');
},
......
export default {
methods: {
clickTriggeredByPipeline() {},
clickTriggeredPipeline() {},
},
};
export default {
methods: {
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
},
};
......@@ -2,8 +2,9 @@ import Vue from 'vue';
import Flash from '~/flash';
import Translate from '~/vue_shared/translate';
import { __ } from '~/locale';
import pipelineGraph from 'ee_else_ce/pipelines/components/graph/graph_component.vue';
import GraphEEMixin from 'ee_else_ce/pipelines/mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator';
import pipelineGraph from './components/graph/graph_component.vue';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
......@@ -22,6 +23,7 @@ export default () => {
components: {
pipelineGraph,
},
mixins: [GraphEEMixin],
data() {
return {
mediator,
......@@ -44,6 +46,10 @@ export default () => {
},
on: {
refreshPipelineGraph: this.requestRefreshPipelineGraph,
onClickTriggeredBy: (parentPipeline, pipeline) =>
this.clickTriggeredByPipeline(parentPipeline, pipeline),
onClickTriggered: (parentPipeline, pipeline) =>
this.clickTriggeredPipeline(parentPipeline, pipeline),
},
});
},
......
......@@ -376,18 +376,21 @@ img.emoji {
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-16 { margin-top: 16px; }
.prepend-top-20 { margin-top: 20px; }
.prepend-top-32 { margin-top: 32px; }
.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; }
.prepend-left-8 { margin-left: 8px; }
.prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
.prepend-left-32 { margin-left: 32px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
.prepend-right-32 { margin-right: 32px; }
.append-bottom-0 { margin-bottom: 0; }
.append-bottom-4 { margin-bottom: $gl-padding-4; }
.append-bottom-5 { margin-bottom: 5px; }
......@@ -396,6 +399,7 @@ img.emoji {
.append-bottom-15 { margin-bottom: 15px; }
.append-bottom-20 { margin-bottom: 20px; }
.append-bottom-default { margin-bottom: $gl-padding; }
.prepend-bottom-32 { margin-bottom: 32px; }
.inline { display: inline-block; }
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
......
......@@ -693,10 +693,6 @@
}
}
.project-empty-note-panel {
border-bottom: 1px solid $border-color;
}
.project-stats,
.project-buttons {
.scrolling-tabs-container {
......
......@@ -58,11 +58,24 @@ class GroupsController < Groups::ApplicationController
def show
respond_to do |format|
format.html
format.html do
render_show_html
end
format.atom do
load_events
render layout: 'xml.atom'
render_details_view_atom
end
end
end
def details
respond_to do |format|
format.html do
render_details_html
end
format.atom do
render_details_view_atom
end
end
end
......@@ -119,6 +132,19 @@ class GroupsController < Groups::ApplicationController
protected
def render_show_html
render 'groups/show'
end
def render_details_html
render 'groups/show'
end
def render_details_view_atom
load_events
render layout: 'xml.atom', template: 'groups/show'
end
# rubocop: disable CodeReuse/ActiveRecord
def authorize_create_group!
allowed = if params[:parent_id].present?
......@@ -178,8 +204,8 @@ class GroupsController < Groups::ApplicationController
.includes(:namespace)
@events = EventCollection
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
Events::RenderService
.new(current_user)
......
......@@ -4,6 +4,7 @@ module GroupsHelper
def group_overview_nav_link_paths
%w[
groups#show
groups#details
groups#activity
groups#subgroups
analytics#show
......
......@@ -286,13 +286,11 @@ class MergeRequestDiff < ActiveRecord::Base
return yield(@external_diff_file) if @external_diff_file
external_diff.open do |file|
begin
@external_diff_file = file
@external_diff_file = file
yield(@external_diff_file)
ensure
@external_diff_file = nil
end
yield(@external_diff_file)
ensure
@external_diff_file = nil
end
end
......
......@@ -23,6 +23,6 @@ class MergeRequestDiffFile < ActiveRecord::Base
super
end
binary? ? content.unpack('m0').first : content
binary? ? content.unpack1('m0') : content
end
end
......@@ -1209,11 +1209,9 @@ class Project < ActiveRecord::Base
def repo_exists?
strong_memoize(:repo_exists) do
begin
repository.exists?
rescue
false
end
repository.exists?
rescue
false
end
end
......
......@@ -205,12 +205,10 @@ class JiraService < IssueTrackerService
# if any transition fails it will log the error message and stop the transition sequence
def transition_issue(issue)
jira_issue_transition_id.scan(Gitlab::Regex.jira_transition_id_regex).each do |transition_id|
begin
issue.transitions.build.save!(transition: { id: transition_id })
rescue => error
log_error("Issue transition failed", error: error.message, client_url: client_url)
return false
end
issue.transitions.build.save!(transition: { id: transition_id })
rescue => error
log_error("Issue transition failed", error: error.message, client_url: client_url)
return false
end
end
......
......@@ -265,16 +265,14 @@ class Repository
# to avoid unnecessary syncing.
def keep_around(*shas)
shas.each do |sha|
begin
next unless sha.present? && commit_by(oid: sha)
next unless sha.present? && commit_by(oid: sha)
next if kept_around?(sha)
next if kept_around?(sha)
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha)
rescue Gitlab::Git::CommandError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha)
rescue Gitlab::Git::CommandError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end
end
......
......@@ -26,16 +26,14 @@ class UserInteractedProject < ActiveRecord::Base
cached_exists?(attributes) do
transaction(requires_new: true) do
begin
where(attributes).select(1).first || create!(attributes)
true # not caching the whole record here for now
rescue ActiveRecord::RecordNotUnique
# Note, above queries are not atomic and prone
# to race conditions (similar like #find_or_create!).
# In the case where we hit this, the record we want
# already exists - shortcut and return.
true
end
where(attributes).select(1).first || create!(attributes)
true # not caching the whole record here for now
rescue ActiveRecord::RecordNotUnique
# Note, above queries are not atomic and prone
# to race conditions (similar like #find_or_create!).
# In the case where we hit this, the record we want
# already exists - shortcut and return.
true
end
end
end
......
......@@ -42,11 +42,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def empty_repo_statistics_anchors
[
license_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
files_anchor_data
license_anchor_data
].compact.select { |item| item.is_link }
end
......@@ -55,9 +51,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
new_file_anchor_data,
readme_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data,
autodevops_anchor_data,
kubernetes_cluster_anchor_data
contribution_guide_anchor_data
].compact.reject { |item| item.is_link }
end
......
......@@ -9,16 +9,14 @@ class DetailedStatusEntity < Grape::Entity
expose :details_path
expose :illustration do |status|
begin
illustration = {
image: ActionController::Base.helpers.image_path(status.illustration[:image])
}
illustration = status.illustration.merge(illustration)
illustration = {
image: ActionController::Base.helpers.image_path(status.illustration[:image])
}
illustration = status.illustration.merge(illustration)
illustration
rescue NotImplementedError
# ignored
end
illustration
rescue NotImplementedError
# ignored
end
expose :favicon do |status|
......
......@@ -42,17 +42,15 @@ module Projects
def parse_response_links(objects_response)
objects_response.each_with_object([]) do |entry, link_list|
begin
link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
raise DownloadLinkNotFound unless link
raise DownloadLinkNotFound unless link
link_list << LfsDownloadObject.new(oid: entry['oid'],
size: entry['size'],
link: add_credentials(link))
rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
end
link_list << LfsDownloadObject.new(oid: entry['oid'],
size: entry['size'],
link: add_credentials(link))
rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
end
end
......
......@@ -75,17 +75,15 @@ module Projects
create_tmp_storage_dir
File.open(tmp_filename, 'wb') do |file|
begin
yield file
rescue StandardError => e
# If the lfs file is successfully downloaded it will be removed
# when it is added to the project's lfs files.
# Nevertheless if any excetion raises the file would remain
# in the file system. Here we ensure to remove it
File.unlink(file) if File.exist?(file)
raise e
end
yield file