Commit be83fe3e authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-11-07

# Conflicts:
#	app/assets/javascripts/boards/components/issue_card_inner.vue
#	app/assets/javascripts/boards/components/issue_time_estimate.vue
#	app/assets/javascripts/environments/components/environments_table.vue
#	app/assets/javascripts/jobs/components/job_app.vue
#	app/assets/stylesheets/pages/boards.scss
#	app/controllers/boards/issues_controller.rb
#	app/controllers/projects/autocomplete_sources_controller.rb
#	app/views/admin/application_settings/_email.html.haml
#	db/schema.rb
#	doc/ci/variables/README.md
#	doc/ci/yaml/README.md
#	doc/install/README.md
#	doc/user/admin_area/settings/email.md
#	lib/gitlab/ci/config/entry/job.rb
#	lib/gitlab/ci/config/normalizer.rb
#	locale/gitlab.pot
#	spec/finders/snippets_finder_spec.rb
#	spec/lib/gitlab/ci/config/normalizer_spec.rb
#	spec/models/user_spec.rb
#	spec/support/helpers/migrations_helpers.rb
#	spec/support/helpers/test_env.rb

[ci skip]
parents d8dcd0c2 8467167b
......@@ -431,7 +431,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly'
gem 'gitaly-proto', '~> 0.123.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6'
......@@ -446,6 +446,3 @@ gem 'flipper-active_support_cache_store', '~> 0.13.0'
# Structured logging
gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.4'
......@@ -58,11 +58,6 @@ GEM
asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0)
atomic (1.1.99)
attr_encrypted (3.1.0)
......@@ -298,9 +293,8 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (0.118.1)
google-protobuf (~> 3.1)
grpc (~> 1.10)
gitaly-proto (0.123.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-license (1.0.0)
gitlab-markup (1.6.4)
......@@ -968,7 +962,6 @@ DEPENDENCIES
asana (~> 0.6.0)
asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0)
awesome_print
aws-sdk
......@@ -1034,7 +1027,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0)
gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4)
......@@ -1195,4 +1188,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.6
1.17.1
......@@ -61,11 +61,6 @@ GEM
asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0)
atomic (1.1.99)
attr_encrypted (3.1.0)
......@@ -301,9 +296,8 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (0.118.1)
google-protobuf (~> 3.1)
grpc (~> 1.10)
gitaly-proto (0.123.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-license (1.0.0)
gitlab-markup (1.6.4)
......@@ -977,7 +971,6 @@ DEPENDENCIES
asana (~> 0.6.0)
asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0)
awesome_print
aws-sdk
......@@ -1043,7 +1036,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0)
gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4)
......@@ -1204,4 +1197,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.6
1.17.1
......@@ -3,7 +3,10 @@ import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
<<<<<<< HEAD
import IssueCardWeight from 'ee/boards/components/issue_card_weight.vue';
=======
>>>>>>> upstream/master
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import IssueDueDate from './issue_due_date.vue';
......@@ -16,7 +19,10 @@ export default {
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
<<<<<<< HEAD
IssueCardWeight,
=======
>>>>>>> upstream/master
IssueTimeEstimate,
},
directives: {
......@@ -216,10 +222,13 @@ export default {
/><issue-time-estimate
v-if="issue.timeEstimate"
:estimate="issue.timeEstimate"
<<<<<<< HEAD
/><issue-card-weight
v-if="issue.weight"
:weight="issue.weight"
@click="filterByWeight(issue.weight)"
=======
>>>>>>> upstream/master
/>
</span>
</div>
......
......@@ -41,8 +41,12 @@ export default {
placement="bottom"
class="js-issue-time-estimate"
>
<<<<<<< HEAD
<span class="bold">{{ __('Time estimate') }}</span>
<br />
=======
<span class="bold d-block">{{ __('Time estimate') }}</span>
>>>>>>> upstream/master
{{ title }}
</gl-tooltip>
</span>
......
......@@ -28,6 +28,7 @@ export default class Clusters {
installIngressPath,
installRunnerPath,
installJupyterPath,
installKnativePath,
installPrometheusPath,
managePrometheusPath,
clusterStatus,
......@@ -49,6 +50,7 @@ export default class Clusters {
installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath,
});
this.installApplication = this.installApplication.bind(this);
......
......@@ -7,6 +7,7 @@ import helmLogo from 'images/cluster_app_logos/helm.png';
import jeagerLogo from 'images/cluster_app_logos/jeager.png';
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
import knativeLogo from 'images/cluster_app_logos/knative.png';
import meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import { s__, sprintf } from '../../locale';
......@@ -53,6 +54,7 @@ export default {
jeagerLogo,
jupyterhubLogo,
kubernetesLogo,
knativeLogo,
meltanoLogo,
prometheusLogo,
}),
......@@ -136,6 +138,9 @@ export default {
jupyterHostname() {
return this.applications.jupyter.hostname;
},
knativeInstalled() {
return this.applications.knative.status === APPLICATION_STATUS.INSTALLED;
},
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
......@@ -321,7 +326,6 @@ export default {
:request-reason="applications.jupyter.requestReason"
:install-application-request-params="{ hostname: applications.jupyter.hostname }"
:disabled="!helmInstalled"
class="hide-bottom-border rounded-bottom"
title-link="https://jupyterhub.readthedocs.io/en/stable/"
>
<div slot="description">
......@@ -371,6 +375,58 @@ export default {
</template>
</div>
</application-row>
<application-row
id="knative"
:logo-url="knativeLogo"
:title="applications.knative.title"
:status="applications.knative.status"
:status-reason="applications.knative.statusReason"
:request-status="applications.knative.requestStatus"
:request-reason="applications.knative.requestReason"
:install-application-request-params="{ hostname: applications.knative.hostname}"
:disabled="!helmInstalled"
class="hide-bottom-border rounded-bottom"
title-link="https://github.com/knative/docs"
>
<div slot="description">
<p>
{{ s__(`ClusterIntegration|A Knative build extends Kubernetes
and utilizes existing Kubernetes primitives to provide you with
the ability to run on-cluster container builds from source.
For example, you can write a build that uses Kubernetes-native
resources to obtain your source code from a repository,
build it into container a image, and then run that image.`) }}
</p>
<template v-if="knativeInstalled">
<div class="form-group">
<label for="knative-domainname">
{{ s__('ClusterIntegration|Knative Domain Name:') }}
</label>
<input
id="knative-domainname"
v-model="applications.knative.hostname"
type="text"
class="form-control js-domainname"
readonly
/>
</div>
</template>
<template v-else>
<div class="form-group">
<label for="knative-domainname">
{{ s__('ClusterIntegration|Knative Domain Name:') }}
</label>
<input
id="knative-domainname"
v-model="applications.knative.hostname"
type="text"
class="form-control js-domainname"
/>
</div>
</template>
</div>
</application-row>
</div>
</section>
</template>
......@@ -16,3 +16,4 @@ export const REQUEST_SUCCESS = 'request-success';
export const REQUEST_FAILURE = 'request-failure';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
......@@ -9,6 +9,7 @@ export default class ClusterService {
runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
};
}
......
import { s__ } from '../../locale';
import { INGRESS, JUPYTER } from '../constants';
import { INGRESS, JUPYTER, KNATIVE } from '../constants';
export default class ClusterStore {
constructor() {
......@@ -46,6 +46,14 @@ export default class ClusterStore {
requestReason: null,
hostname: null,
},
knative: {
title: s__('ClusterIntegration|Knative'),
status: null,
statusReason: null,
requestStatus: null,
requestReason: null,
hostname: null,
},
},
};
}
......@@ -93,6 +101,9 @@ export default class ClusterStore {
(this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
: '');
} else if (appId === KNATIVE) {
this.state.applications.knative.hostname =
serverAppEntry.hostname || this.state.applications.knative.hostname;
}
});
}
......
import Vue from 'vue';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
Vue.component('gl-loading-icon', GlLoadingIcon);
......@@ -36,7 +36,7 @@ export default {
},
computed: {
...mapState('diffs', ['commit', 'showTreeList']),
...mapGetters('diffs', ['isInlineView', 'isParallelView', 'areAllFilesCollapsed']),
...mapGetters('diffs', ['isInlineView', 'isParallelView', 'hasCollapsedFile']),
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
......@@ -113,8 +113,8 @@ export default {
class="inline-parallel-buttons d-none d-md-flex ml-auto"
>
<a
v-if="areAllFilesCollapsed"
class="btn btn-default"
v-show="hasCollapsedFile"
class="btn btn-default append-right-8"
@click="expandAllFiles"
>
{{ __('Expand all') }}
......
......@@ -55,11 +55,6 @@ export default {
required: false,
default: false,
},
isContextLine: {
type: Boolean,
required: false,
default: false,
},
isHover: {
type: Boolean,
required: false,
......@@ -81,7 +76,6 @@ export default {
this.showCommentButton &&
this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
!this.isMetaLine &&
!this.hasDiscussions
);
......
......@@ -3,7 +3,6 @@ import { mapGetters } from 'vuex';
import DiffLineGutterContent from './diff_line_gutter_content.vue';
import {
MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
EMPTY_CELL_TYPE,
OLD_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
......@@ -71,9 +70,6 @@ export default {
isMatchLine() {
return this.line.type === MATCH_LINE_TYPE;
},
isContextLine() {
return this.line.type === CONTEXT_LINE_TYPE;
},
isMetaLine() {
const { type } = this.line;
......@@ -88,11 +84,7 @@ export default {
[type]: type,
[LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
[LINE_HOVER_CLASS_NAME]:
this.isLoggedIn &&
this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
!this.isMetaLine,
this.isLoggedIn && this.isHover && !this.isMatchLine && !this.isMetaLine,
};
},
lineNumber() {
......
......@@ -4,8 +4,6 @@ import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
CONTEXT_LINE_CLASS_NAME,
PARALLEL_DIFF_VIEW_TYPE,
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
......@@ -41,13 +39,9 @@ export default {
},
computed: {
...mapGetters('diffs', ['isInlineView']),
isContextLine() {
return this.line.type === CONTEXT_LINE_TYPE;
},
classNameMap() {
return {
[this.line.type]: this.line.type,
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
};
},
......
......@@ -5,8 +5,6 @@ import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
CONTEXT_LINE_CLASS_NAME,
OLD_NO_NEW_LINE_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE,
......@@ -43,12 +41,8 @@ export default {
};
},
computed: {
isContextLine() {
return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
},
classNameMap() {
return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: true,
};
},
......
......@@ -3,7 +3,6 @@ export const PARALLEL_DIFF_VIEW_TYPE = 'parallel';
export const MATCH_LINE_TYPE = 'match';
export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline';
export const NEW_NO_NEW_LINE_TYPE = 'new-nonewline';
export const CONTEXT_LINE_TYPE = 'context';
export const EMPTY_CELL_TYPE = 'empty-cell';
export const COMMENT_FORM_TYPE = 'commentForm';
export const DIFF_NOTE_TYPE = 'DiffNote';
......@@ -22,7 +21,6 @@ export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
export const LINE_HOVER_CLASS_NAME = 'is-over';
export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold';
export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
......
......@@ -5,7 +5,7 @@ export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW
export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
export const areAllFilesCollapsed = state => state.diffFiles.every(file => file.collapsed);
export const hasCollapsedFile = state => state.diffFiles.some(file => file.collapsed);
export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
......
......@@ -65,7 +65,13 @@ export default {
const { highlightedDiffLines, parallelDiffLines } = diffFile;
removeMatchLine(diffFile, lineNumbers, bottom);
const lines = addLineReferences(contextLines, lineNumbers, bottom);
const lines = addLineReferences(contextLines, lineNumbers, bottom).map(line => ({
...line,
lineCode: line.lineCode || `${fileHash}_${line.oldLine}_${line.newLine}`,
discussions: line.discussions || [],
}));
addContextLines({
inlineLines: highlightedDiffLines,
parallelLines: parallelDiffLines,
......
......@@ -10,7 +10,10 @@ import deployBoard from 'ee/environments/components/deploy_board_component.vue';
export default {
components: {
environmentItem,
<<<<<<< HEAD
deployBoard,
=======
>>>>>>> upstream/master
GlLoadingIcon,
},
......
......@@ -7,9 +7,12 @@ import { polyfillSticky } from '~/lib/utils/sticky';
import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
<<<<<<< HEAD
// ee-only start
import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue';
// ee-only end
=======
>>>>>>> upstream/master
import Icon from '~/vue_shared/components/icon.vue';
import createStore from '../store';
import EmptyState from './empty_state.vue';
......@@ -19,6 +22,8 @@ import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin';
export default {
name: 'JobPageApp',
......@@ -29,14 +34,19 @@ export default {
EmptyState,
EnvironmentsBlock,
ErasedBlock,
<<<<<<< HEAD
GlLoadingIcon,
=======
>>>>>>> upstream/master
Icon,
Log,
LogTopBar,
StuckBlock,
SharedRunner,
Sidebar,
GlLoadingIcon,
},
mixins: [delayedJobMixin],
props: {
runnerSettingsUrl: {
type: String,
......@@ -97,6 +107,17 @@ export default {
shouldRenderContent() {
return !this.isLoading && !this.hasError;
},
emptyStateTitle() {
const { emptyStateIllustration, remainingTime } = this;
const { title } = emptyStateIllustration;
if (this.isDelayedJob) {
return sprintf(title, { remainingTime });
}
return title;
},
},
watch: {
// Once the job log is loaded,
......@@ -239,16 +260,28 @@ export default {
:erased-at="job.erased_at"
/>
<<<<<<< HEAD
<div
=======
<div
>>>>>>> upstream/master
v-if="job.archived"
ref="sticky"
class="js-archived-job prepend-top-default archived-sticky sticky-top"
>
<<<<<<< HEAD
<icon
name="lock"
class="align-text-bottom"
/>
=======
<icon
name="lock"
class="align-text-bottom"
/>
>>>>>>> upstream/master
{{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div>
<!--job log -->
......@@ -285,7 +318,7 @@ export default {
class="js-job-empty-state"
:illustration-path="emptyStateIllustration.image"
:illustration-size-class="emptyStateIllustration.size"
:title="emptyStateIllustration.title"
:title="emptyStateTitle"
:content="emptyStateIllustration.content"
:action="emptyStateAction"
/>
......
<script>
import { GlTooltipDirective, GlLink } from '@gitlab-org/gitlab-ui';
import { GlLink } from '@gitlab-org/gitlab-ui';
import tooltip from '~/vue_shared/directives/tooltip';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
import { sprintf } from '~/locale';
export default {
components: {
......@@ -10,8 +13,9 @@ export default {
GlLink,
},
directives: {
GlTooltip: GlTooltipDirective,
tooltip,
},
mixins: [delayedJobMixin],
props: {
job: {
type: Object,
......@@ -24,7 +28,14 @@ export default {
},
computed: {
tooltipText() {
return `${this.job.name} - ${this.job.status.tooltip}`;
const { name, status } = this.job;
const text = `${name} - ${status.tooltip}`;
if (this.isDelayedJob) {
return sprintf(text, { remainingTime: this.remainingTime });
}
return text;
},
},
};
......@@ -39,7 +50,7 @@ export default {
}"
>
<gl-link
v-gl-tooltip
v-tooltip
:href="job.status.details_path"
:title="tooltipText"
data-boundary="viewport"
......
import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility';
export default {
data() {
return {
remainingTime: formatTime(0),
remainingTimeIntervalId: null,
};
},
mounted() {
this.startRemainingTimeInterval();
},
beforeDestroy() {
if (this.remainingTimeIntervalId) {
clearInterval(this.remainingTimeIntervalId);
}
},
computed: {
isDelayedJob() {
return this.job && this.job.scheduled;
},
},
watch: {
isDelayedJob() {
this.startRemainingTimeInterval();
},
},
methods: {
startRemainingTimeInterval() {
if (this.remainingTimeIntervalId) {
clearInterval(this.remainingTimeIntervalId);
}
if (this.isDelayedJob) {
this.updateRemainingTime();
this.remainingTimeIntervalId = setInterval(() => this.updateRemainingTime(), 1000);
}
},
updateRemainingTime() {
const remainingMilliseconds = calculateRemainingMilliseconds(this.job.scheduled_at);
this.remainingTime = formatTime(remainingMilliseconds);
},
},
};
......@@ -248,7 +248,7 @@ export default {
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
<a :href="reportAbusePath">
Report as abuse
{{ __('Report abuse to GitLab') }}
</a>
</li>
<li v-if="noteUrl">
......@@ -257,7 +257,7 @@ export default {
type="button"
class="btn-default btn-transparent js-btn-copy-note-link"
>
Copy link
{{ __('Copy link') }}
</button>
</li>
<li v-if="canEdit">
......@@ -266,7 +266,7 @@ export default {
type="button"
@click.prevent="onDelete">
<span class="text-danger">
Delete comment
{{ __('Delete comment') }}
</span>
</button>
</li>
......
......@@ -2,6 +2,8 @@
import ActionComponent from './action_component.vue';
import JobNameComponent from './job_name_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import { sprintf } from '~/locale';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
......@@ -36,6 +38,7 @@ export default {
directives: {
tooltip,
},
mixins: [delayedJobMixin],
props: {
job: {
type: Object,
......@@ -52,6 +55,7 @@ export default {
default: Infinity,
},
},
computed: {
status() {
return this.job && this.job.status ? this.job.status : {};
......@@ -59,17 +63,23 @@ export default {
tooltipText() {
const textBuilder = [];
const { name: jobName } = this.job;
if (this.job.name) {
textBuilder.push(this.job.name);
if (jobName) {
textBuilder.push(jobName);
}
if (this.job.name && this.status.tooltip) {
const { tooltip: statusTooltip } = this.status;
if (jobName && statusTooltip) {
textBuilder.push('-');
}
if (this.status.tooltip) {
textBuilder.push(this.job.status.tooltip);
if (statusTooltip) {
if (this.isDelayedJob) {
textBuilder.push(sprintf(statusTooltip, { remainingTime: this.remainingTime }));
} else {
textBuilder.push(statusTooltip);
}
}
return textBuilder.join(' ');
......@@ -88,6 +98,7 @@ export default {
return this.job.status && this.job.status.action && this.job.status.action.path;
},
},
methods: {
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
......
......@@ -722,6 +722,7 @@
}
}
<<<<<<< HEAD
.boards-switcher {
padding-right: 10px;
}
......@@ -765,6 +766,8 @@
}
}
=======
>>>>>>> upstream/master
.board-card-info {
color: $gl-text-color-secondary;
white-space: nowrap;
......
......@@ -100,7 +100,12 @@ module Boards
.merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
end
def serializer
IssueSerializer.new(current_user: current_user)
end
def serialize_as_json(resource)
<<<<<<< HEAD
resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight, :time_estimate],
labels: true,
......@@ -112,6 +117,9 @@ module Boards
milestone: { only: [:id, :title] }
}
)
=======
serializer.represent(resource, serializer: 'board', include_full_project_path: board.group_board?)
>>>>>>> upstream/master
end
def whitelist_query_limiting
......
# frozen_string_literal: true
class ChaosController < ActionController::Base
before_action :validate_request
def leakmem
memory_mb = (params[:memory_mb]&.to_i || 100)
duration_s = (params[:duration_s]&.to_i || 30).seconds
start = Time.now
retainer = []
# Add `n` 1mb chunks of memory to the retainer array
memory_mb.times { retainer << "x" * 1.megabyte }
duration_taken = (Time.now - start).seconds
Kernel.sleep duration_s - duration_taken if duration_s > duration_taken
render text: "OK", content_type: 'text/plain'
end
def cpuspin
duration_s = (params[:duration_s]&.to_i || 30).seconds
end_time = Time.now + duration_s.seconds
rand while Time.now < end_time
render text: "OK", content_type: 'text/plain'
end
def sleep
duration_s = (params[:duration_s]&.to_i || 30).seconds
Kernel.sleep duration_s
render text: "OK", content_type: 'text/plain'
end
def kill
Process.kill("KILL", Process.pid)
end
private
def validate_request
secret = ENV['GITLAB_CHAOS_SECRET']
# GITLAB_CHAOS_SECRET is required unless you're running in Development mode
if !secret && !Rails.env.development?
render text: "chaos misconfigured: please configure GITLAB_CHAOS_SECRET when using GITLAB_ENABLE_CHAOS_ENDPOINTS outside of a development environment", content_type: 'text/plain', status: 500
end
return unless secret
unless request.headers["HTTP_X_CHAOS_SECRET"] == secret
render text: "To experience chaos, please set X-Chaos-Secret header", content_type: 'text/plain', status: 401
end
end
end
......@@ -86,10 +86,10 @@ module CreatesCommit
def new_merge_request_path
project_new_merge_request_path(
@project_to_commit_into,
merge_request_source_branch: @branch_name,
merge_request: {
source_project_id: @project_to_commit_into.id,
target_project_id: @project.id,
source_branch: @branch_name,
target_branch: @start_branch
}
)
......
......@@ -54,14 +54,14 @@ class Import::BitbucketServerController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord
def status
repos = bitbucket_client.repos
@collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page)
@repos, @incompatible_repos = @collection.partition { |repo| repo.valid? }
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
@already_added_projects = find_already_added_projects('bitbucket_server')
# Use the import URL to filter beyond what BaseService#find_already_added_projects
@already_added_projects = filter_added_projects('bitbucket_server', @repos.map(&:browse_url))
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
rescue BitbucketServer::Connection::ConnectionError, BitbucketServer::Client::ServerError => e
flash[:alert] = "Unable to connect to server: #{e}"
clear_session_data
......@@ -75,6 +75,12 @@ class Import::BitbucketServerController < Import::BaseController
private
# rubocop: disable CodeReuse/ActiveRecord
def filter_added_projects(import_type, import_sources)
current_user.created_projects.where(import_type: import_type, import_source: import_sources).includes(:import_state)
end
# rubocop: enable CodeReuse/ActiveRecord
def bitbucket_client
@bitbucket_client ||= BitbucketServer::Client.new(credentials)
end
......@@ -130,4 +136,12 @@ class Import::BitbucketServerController < Import::BaseController
password: session[personal_access_token_key]
}
end
def page_offset
[0, params[:page].to_i].max
end
def limit_per_page
BitbucketServer::Paginator::PAGE_LENGTH
end
end
# frozen_string_literal: true
class Projects::AutocompleteSourcesController < Projects::ApplicationController
<<<<<<< HEAD
prepend EE::Projects::AutocompleteSourcesController
=======
>>>>>>> upstream/master
def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
end
......
......@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController
@lines.map! do |line|
# These are marked as context lines but are loaded from blobs.
# We also have context lines loaded from diffs in other places.
diff_line = Gitlab::Diff::Line.new(line, 'context', nil, nil, nil)
diff_line = Gitlab::Diff::Line.new(line, nil, nil, nil, nil)
diff_line.rich_text = line
diff_line
end
......
......@@ -89,8 +89,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
params[:merge_request][:source_branch] ||= params[:merge_request_source_branch].presence
@merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
end
......
......@@ -22,6 +22,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs
@environment = @merge_request.environments_for(current_user).last
notes_grouped_by_path = @notes.group_by { |note| note.position.file_path }
@diffs.diff_files.each do |diff_file|
notes = notes_grouped_by_path.fetch(diff_file.file_path, [])
notes.each { |note| diff_file.unfold_diff_lines(note.position) }
end
@diffs.write_cache
......
......@@ -13,8 +13,8 @@ module CompareHelper
def create_mr_path(from = params[:from], to = params[:to], project = @project)
project_new_merge_request_path(
project,
merge_request_source_branch: to,
merge_request: {
source_branch: to,
target_branch: from
}
)
......
......@@ -91,7 +91,14 @@ module EventsHelper
words << "##{event.target_iid}" if event.target_iid
words << "in"
elsif event.target
words << "##{event.target_iid}:"
prefix =
if event.merge_request?
MergeRequest.reference_prefix
else
Issue.reference_prefix
end
words << "#{prefix}#{event.target_iid}:" if event.target_iid
words << event.target.title if event.target.respond_to?(:title)
words << "at"
end
......
......@@ -13,10 +13,10 @@ module MergeRequestsHelper
def new_mr_from_push_event(event, target_project)
{
merge_request_source_branch: event.branch_name,
merge_request: {
source_project_id: event.project.id,
target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref
}
}
......@@ -53,10 +53,10 @@ module MergeRequestsHelper
def mr_change_branches_path(merge_request)
project_new_merge_request_path(
@project,
merge_request_source_branch: merge_request.source_branch,
merge_request: {
source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id,
source_branch: merge_request.source_branch,
target_branch: merge_request.target_branch
},
change_branches: true
......
......@@ -214,6 +214,7 @@ module Ci
build.deployment&.succeed
build.run_after_commit do
BuildSuccessWorker.perform_async(id)
PagesWorker.perform_async(:deploy, id) if build.pages_generator?
end
end
......@@ -223,9 +224,7 @@ module Ci
build.deployment&.drop
next if build.retries_max.zero?
if build.retries_count < build.retries_max
if build.retry_failure?
begin
Ci::Build.retry(build, build.user)
rescue Gitlab::Access::AccessDeniedError => ex
......@@ -323,7 +322,17 @@ module Ci
end
def retries_max
self.options.to_h.fetch(:retry, 0).to_i
normalized_retry.fetch(:max, 0)
end
def retry_when
normalized_retry.fetch(:when, ['always'])
end
def retry_failure?
return false if retries_max.zero? || retries_count >= retries_max
retry_when.include?('always') || retry_when.include?(failure_reason.to_s)
end
def latest?
......@@ -888,6 +897,16 @@ module Ci
options&.dig(:environment, :url) || persisted_environment&.external_url
end
# The format of the retry option changed in GitLab 11.5: Before it was
# integer only, after it is a hash. New builds are created with the new
# format, but builds created before GitLab 11.5 and saved in database still
# have the old integer only format. This method returns the retry option
# normalized as a hash in 11.5+ format.
def normalized_retry
value = options&.dig(:retry)
value.is_a?(Integer) ? { max: value } : value.to_h
end
def build_attributes_from_config
return {} unless pipeline.config_processor
......
# frozen_string_literal: true
module Clusters
module Applications
class Knative < ActiveRecord::Base
VERSION = '0.1.3'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
# This is required for helm version <= 2.10.x in order to support
# Setting up CRDs
ISTIO_CRDS = 'https://storage.googleapis.com/triggermesh-charts/istio-crds.yaml'.freeze
self.table_name = 'clusters_applications_knative'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
default_value_for :version, VERSION
validates :hostname, presence: true, hostname: true
def chart
'knative/knative'
end
def values
{ "domain" => hostname }.to_yaml
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
repository: REPOSITORY,
preinstall: install_script
)
end
private
def install_script
["/usr/bin/kubectl apply -f #{ISTIO_CRDS} >/dev/null"]
end
end
end
end
......@@ -14,7 +14,8 @@ module Clusters
Applications::Ingress.application_name => Applications::Ingress,
Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
Applications::Jupyter.application_name => Applications::Jupyter
Applications::Jupyter.application_name => Applications::Jupyter,
Applications::Knative.application_name => Applications::Knative
}.freeze
DEFAULT_ENVIRONMENT = '*'.freeze
......@@ -37,6 +38,7 @@ module Clusters
has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
has_one :application_knative, class_name: 'Clusters::Applications::Knative'
has_many :kubernetes_namespaces
has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
......@@ -102,7 +104,8 @@ module Clusters
application_ingress || build_application_ingress,
application_prometheus || build_application_prometheus,
application_runner || build_application_runner,
application_jupyter || build_application_jupyter
application_jupyter || build_application_jupyter,
application_knative || build_application_knative
]
end
......
......@@ -28,6 +28,7 @@ module Clusters
algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case
before_validation :enforce_ca_whitespace_trimming
validates :namespace,
allow_blank: true,
......@@ -203,6 +204,11 @@ module Clusters
self.namespace = self.namespace&.downcase
end
def enforce_ca_whitespace_trimming
self.ca_pem = self.ca_pem&.strip
self.token = self.token&.strip
end
def prevent_modification
return unless managed?
......
......@@ -39,7 +39,15 @@ module EachBatch
#
# of - The number of rows to retrieve per batch.
# column - The column to use for ordering the batches.
def each_batch(of: 1000, column: primary_key)
# order_hint - An optional column to append to the `ORDER BY id`
# clause to help the query planner. PostgreSQL might perform badly
# with a LIMIT 1 because the planner is guessing that scanning the
# index in ID order will come across the desired row in less time
# it will take the planner than using another index. The
# order_hint does not affect the search results. For example,
# `ORDER BY id ASC, updated_at ASC` means the same thing as `ORDER
# BY id ASC`.
def each_batch(of: 1000, column: primary_key, order_hint: nil)
unless column
raise ArgumentError,
'the column: argument must be set to a column name to use for ordering rows'
......@@ -48,7 +56,9 @@ module EachBatch
start = except(:select)
.select(column)
.reorder(column => :asc)
.take
start = start.order(order_hint) if order_hint
start = start.take
return unless start
......@@ -60,6 +70,9 @@ module EachBatch
.select(column)
.where(arel_table[column].gteq(start_id))
.reorder(column => :asc)
stop = stop.order(order_hint) if order_hint
stop = stop
.offset(of)
.limit(1)
.take
......
......@@ -10,7 +10,9 @@ class Deployment < ActiveRecord::Base
belongs_to :user
belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.deployments&.maximum(:iid) }
has_internal_id :iid, scope: :project, init: ->(s) do
Deployment.where(project: s.project).maximum(:iid) if s&.project
end
validates :sha, presence: true
validates :ref, presence: true
......
......@@ -66,6 +66,10 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs
end
def discussion_first_note?
self == discussion.first_note
end
private
def enqueue_diff_file_creation_job
......@@ -78,26 +82,33 @@ class DiffNote < Note
end
def should_create_diff_file?
on_text? && note_diff_file.nil? && self == discussion.first_note
on_text? && note_diff_file.nil? && discussion_first_note?
end
def fetch_diff_file
if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff,
repository: project.repository,
diff_refs: original_position.diff_refs)
elsif created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
# presenting a "current version" of the MR discussion diff.
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(original_position.diff_options).diff_files.first
else
original_position.diff_file(self.project.repository)
end
file =
if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff,
repository: project.repository,
diff_refs: original_position.diff_refs)
elsif created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
# presenting a "current version" of the MR discussion diff.
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(original_position.diff_options).diff_files.first
else
original_position.diff_file(self.project.repository)
end
# Since persisted diff files already have its content "unfolded"
# there's no need to make it pass through the unfolding process.
file&.unfold_diff_lines(position) unless note_diff_file
file
end
def supported?
......
......@@ -52,6 +52,7 @@ class Environment < ActiveRecord::Base
scope :in_review_folder, -> { where(environment_type: "review") }
scope :for_name, -> (name) { where(name: name) }
scope :for_project, -> (project) { where(project_id: project) }
scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) }
state_machine :state, initial: :available do
event :start do
......
......@@ -8,17 +8,16 @@ class EnvironmentStatus
delegate :id, to: :environment
delegate :name, to: :environment
delegate :project, to: :environment
delegate :status, to: :deployment, allow_nil: true
delegate :deployed_at, to: :deployment, allow_nil: true
def self.for_merge_request(mr, user)
build_environments_status(mr, user, mr.head_pipeline)
build_environments_status(mr, user, mr.diff_head_sha)
end
def self.after_merge_request(mr, user)
return [] unless mr.merged?
build_environments_status(mr, user, mr.merge_pipeline)
build_environments_status(mr, user, mr.merge_commit_sha)
end
def initialize(environment, merge_request, sha)
......@@ -29,7 +28,7 @@ class EnvironmentStatus
def deployment
strong_memoize(:deployment) do
environment.first_deployment_for(sha)
Deployment.where(environment: environment).find_by_sha(sha)
end
end
......@@ -44,6 +43,22 @@ class EnvironmentStatus
.merge_request_diff_files.where(deleted_file: false)
end
##
# Since frontend has not supported all statuses yet, BE has to
# proxy some status to a supported status.
def status
return unless deployment
case deployment.status
when 'created'
'running'
when 'canceled'
'failed'
else
deployment.status
end
end
private
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
......@@ -61,21 +76,14 @@ class EnvironmentStatus
}
end
def self.build_environments_status(mr, user, pipeline)
return [] unless pipeline.present?
def self.build_environments_status(mr, user, sha)
Environment.where(project_id: [mr.source_project_id, mr.target_project_id])
.available
.with_deployment(sha).map do |environment|
next unless Ability.allowed?(user, :read_environment, environment)
find_environments(user, pipeline).map do |environment|
EnvironmentStatus.new(environment, mr, pipeline.sha)
end
EnvironmentStatus.new(environment, mr, sha)
end.compact
end
private_class_method :build_environments_status
def self.find_environments(user, pipeline)
env_ids = Deployment.where(deployable: pipeline.builds).select(:environment_id)
Environment.available.where(id: env_ids).select do |environment|
Ability.allowed?(user, :read_environment, environment)
end
end
private_class_method :find_environments
end
......@@ -231,20 +231,6 @@ class Issue < ActiveRecord::Base
def as_json(options = {})
super(options).tap do |json|
if options.key?(:issue_endpoints) && project
url_helper = Gitlab::Routing.url_helpers
issue_reference = options[:include_full_project_path] ? to_reference(full: true) : to_reference
json.merge!(
reference_path: issue_reference,
real_path: url_helper.project_issue_path(project, self),
issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self),
assignable_labels_endpoint: url_helper.project_labels_path(project, format: :json, include_ancestor_groups: true)
)
end
if options.key?(:labels)
json[:labels] = labels.as_json(
project: project,
......
......@@ -1019,6 +1019,18 @@ class Repository
message: merge_request.title)
end
def update_submodule(user, submodule, commit_sha, message:, branch:)
with_cache_hooks do
raw.update_submodule(
user: user,
submodule: submodule,
commit_sha: commit_sha,
branch: branch,
message: message
)
end
end
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
......
......@@ -195,7 +195,7 @@ class WikiPage
update_attributes(attrs)
save(page_details: title) do
wiki.create_page(title, content, format, message)
wiki.create_page(title, content, format, attrs[:message])
end
end
......
......@@ -180,7 +180,7 @@ def index
render json: MyResourceSerializer
.new(current_user: @current_user)
.represent_details(@project.resources)
nd
end
end
```
......@@ -196,7 +196,7 @@ def index
.represent_details(@project.resources),
count: @project.resources.count
}
nd
end
end
```
......
# frozen_string_literal: true
class IssueBoardEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :iid
expose :title
expose :confidential
expose :due_date
expose :project_id
expose :relative_position
expose :weight, if: -> (*) { respond_to?(:weight) }
expose :time_estimate
expose :project do |issue|
API::Entities::Project.represent issue.project, only: [:id, :path]
end
expose :milestone, expose_nil: false do |issue|
API::Entities::Project.represent issue.milestone, only: [:id, :title]
end
expose :assignees do |issue|
API::Entities::UserBasic.represent issue.assignees, only: [:id, :name, :username, :avatar_url]
end
expose :labels do |issue|
LabelEntity.represent issue.labels, project: issue.project, only: [:id, :title, :description, :color, :priority, :text_color]
end
expose :reference_path, if: -> (issue) { issue.project } do |issue, options|
options[:include_full_project_path] ? issue.to_reference(full: true) : issue.to_reference
end
expose :real_path, if: -> (issue) { issue.project } do |issue|
project_issue_path(issue.project, issue)
end
expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue|
project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar')
end
expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue|
toggle_subscription_project_issue_path(issue.project, issue)
end
expose :assignable_labels_endpoint, if: -> (issue) { issue.project } do |issue|
project_labels_path(issue.project, format: :json, include_ancestor_groups: true)
end
end
......@@ -4,15 +4,17 @@ class IssueSerializer < BaseSerializer
# This overrided method takes care of which entity should be used
# to serialize the `issue` based on `basic` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
def represent(merge_request, opts = {})
def represent(issue, opts = {})
entity =
case opts[:serializer]
when 'sidebar'
IssueSidebarEntity
when 'board'
IssueBoardEntity
else
IssueEntity
end
super(merge_request, opts, entity)
super(issue, opts, entity)
end
end
......@@ -12,4 +12,8 @@ class LabelEntity < Grape::Entity
expose :text_color
expose :created_at
expose :updated_at
expose :priority, if: -> (*) { options.key?(:project) } do |label|
label.priority(options[:project])
end
end
......@@ -14,7 +14,8 @@ module Clusters
else
check_timeout
end
rescue Kubeclient::HttpError
rescue Kubeclient::HttpError => e
Rails.logger.error "Kubernetes error: #{e.class.name} #{e.message}"
app.make_errored!("Kubernetes error") unless app.errored?
end
......@@ -51,7 +52,8 @@ module Clusters
def remove_installation_pod
helm_api.delete_pod!(install_command.pod_name)
rescue
rescue => e
Rails.logger.error "Kubernetes error: #{e.class.name} #{e.message}"
# no-op
end
......
......@@ -45,7 +45,8 @@ module Clusters
"ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
"prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
"runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner },
"jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }
"jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter },
"knative" => -> (cluster) { cluster.application_knative || cluster.build_application_knative }
}
end
......
......@@ -12,9 +12,11 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
rescue Kubeclient::HttpError
rescue Kubeclient::HttpError => e
Rails.logger.error "Kubernetes error: #{e.class.name} #{e.message}"
app.make_errored!("Kubernetes error.")
rescue StandardError
rescue StandardError => e
Rails.logger.error "Can't start installation process: #{e.class.name} #{e.message}"
app.make_errored!("Can't start installation process.")
end
end
......
# frozen_string_literal: true
module Commits
class CommitPatchService < CreateService
# Requires:
# - project: `Project` to be committed into
# - user: `User` that will be the committer
# - params:
# - branch_name: `String` the branch that will be committed into
# - start_branch: `String` the branch that will will started from
# - patches: `Gitlab::Git::Patches::Collection` that contains the patches
def initialize(*args)
super
@patches = Gitlab::Git::Patches::Collection.new(Array(params[:patches]))
end
private
def new_branch?
!repository.branch_exists?(@branch_name)
end
def create_commit!
if @start_branch && new_branch?
prepare_branch!
end
Gitlab::Git::Patches::CommitPatches
.new(current_user, project.repository, @branch_name, @patches)
.commit
end
def prepare_branch!
branch_result = CreateBranchService.new(project, current_user)
.execute(@branch_name, @start_branch)
if branch_result[:status] != :success
raise ChangeError, branch_result[:message]
end
end
# Overridden from the Commits::CreateService, to skip some validations we
# don't need:
# - validate_on_branch!
# Not needed, the patches are applied on top of HEAD if the branch did not
# exist
# - validate_branch_existence!
# Not needed because we continue applying patches on the branch if it
# already existed, and create it if it did not exist.
def validate!
validate_patches!
validate_new_branch_name! if new_branch?
validate_permissions!
end
def validate_patches!
raise_error("Patches are too big") unless @patches.valid_size?
end
end
end
......@@ -19,7 +19,12 @@ module Commits
new_commit = create_commit!
success(result: new_commit)
rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::PreReceiveError => ex
rescue ValidationError,
ChangeError,
Gitlab::Git::Index::IndexError,
Gitlab::Git::CommitError,
Gitlab::Git::PreReceiveError,
Gitlab::Git::CommandError => ex
error(ex.message)
end
......
......@@ -128,12 +128,12 @@ class IssuableBaseService < BaseService
merge_quick_actions_into_params!(issuable)
end
def merge_quick_actions_into_params!(issuable)
def merge_quick_actions_into_params!(issuable, only: nil)
original_description = params.fetch(:description, issuable.description)
description, command_params =
QuickActions::InterpretService.new(project, current_user)
.execute(original_description, issuable)
.execute(original_description, issuable, only: only)
# Avoid a description already set on an issuable to be overwritten by a nil
params[:description] = description if description
......
......@@ -6,8 +6,12 @@ module MergeRequests
def execute
@params_issue_iid = params.delete(:issue_iid)
self.merge_request = MergeRequest.new
# TODO: this should handle all quick actions that don't have side effects
# https://gitlab.com/gitlab-org/gitlab-ce/issues/53658
merge_quick_actions_into_params!(merge_request, only: [:target_branch])
merge_request.assign_attributes(params)
self.merge_request = MergeRequest.new(params)
merge_request.author = current_user
merge_request.compare_commits = []
merge_request.source_project = find_source_project
......
......@@ -50,8 +50,8 @@ module MergeRequests
end
def url_for_new_merge_request(branch_name)
url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, branch_name)
merge_request_params = { source_branch: branch_name }
url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
{ branch_name: branch_name, url: url, new_merge_request: true }
end
......
......@@ -29,10 +29,6 @@ module MergeRequests
# rubocop: disable CodeReuse/ActiveRecord
def clear_cache(new_diff)
# Executing the iteration we cache highlighted diffs for each diff file of
# MergeRequestDiff.
cacheable_collection(new_diff).write_cache
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
# reloading the diff.
......
# frozen_string_literal: true
module Notes
class BaseService < ::BaseService
def clear_noteable_diffs_cache(note)
noteable = note.noteable
if note.is_a?(DiffNote) &&
note.discussion_first_note? &&
note.position.unfolded_diff?(project.repository)
noteable.diffs.clear_cache
end
end
end
end
# frozen_string_literal: true
module Notes
class CreateService < ::BaseService
class CreateService < ::Notes::BaseService
def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
......@@ -35,6 +35,7 @@ module Notes
if !only_commands && note.save
todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
end
if command_params.present?
......
# frozen_string_literal: true
module Notes
class DestroyService < BaseService
class DestroyService < ::Notes::BaseService
def execute(note)
TodoService.new.destroy_target(note) do |note|
note.destroy
end
clear_noteable_diffs_cache(note)
end
end
end
......@@ -22,13 +22,13 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
def execute(content, issuable)
def execute(content, issuable, only: nil)
return [content, {}] unless current_user.can?(:use_quick_actions)
@issuable = issuable
@updates = {}
content, commands = extractor.extract_commands(content)
content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands)
[content, @updates]
......
# frozen_string_literal: true
module Submodules
class UpdateService < Commits::CreateService
include Gitlab::Utils::StrongMemoize
def initialize(*args)
super
@start_branch = @branch_name
@commit_sha = params[:commit_sha].presence
@submodule = params[:submodule].presence
@commit_message = params[:commit_message].presence || "Update submodule #{@submodule} with oid #{@commit_sha}"
end
def validate!
super
raise ValidationError, 'The repository is empty' if repository.empty?
end
def execute
super
rescue StandardError => e
error(e.message)
end
def create_commit!
repository.update_submodule(current_user,
@submodule,
@commit_sha,
message: @commit_message,
branch: @branch_name)
rescue ArgumentError, TypeError
raise ValidationError, 'Invalid parameters'
end
end
end
- page_title "Report abuse"
%h3.page-title Report abuse
%p Please use this form to report users who create spam issues, comments or behave inappropriately.
- page_title _("Report abuse to GitLab")
%h3.page-title
= _('Report abuse to GitLab')
%p
= _('Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately.')
%p
= _("A member of GitLab's abuse team will review your report as soon as possible.")
%hr
= form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report)
......@@ -16,7 +20,7 @@
.col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.form-text.text-muted
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
= _('Explain the problem. If appropriate, provide a link to the relevant issue or comment.')
.form-actions
= f.submit "Send report", class: "btn btn-success"
......@@ -26,6 +26,7 @@
.form-text.text-muted
- commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email', anchor: 'custom-private-commit-email-hostname'), target: '_blank'
= _("This setting will update the hostname that is used to generate private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link }
<<<<<<< HEAD
-# EE-specific start
- if License.feature_available?(:email_additional_text)
.form-group
......@@ -34,5 +35,7 @@
.form-text.text-muted
= _('Add additional text to appear in all email communications. %{character_limit} character limit') % { character_limit: number_with_delimiter(Gitlab::CurrentSettings.email_additional_text_character_limit) }
-# EE-specific end
=======
>>>>>>> upstream/master
= f.submit 'Save changes', class: "btn btn-success"
......@@ -13,6 +13,7 @@
install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
......
......@@ -84,4 +84,6 @@
= link_to 'import flow', status_import_bitbucket_server_path
again.
= paginate_without_count(@collection)
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } }
......@@ -71,7 +71,7 @@
= link_to admin_impersonation_path, class: 'nav-link impersonation-btn', method: :delete, title: _('Stop impersonation'), aria: { label: _('Stop impersonation') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- if header_link?(:sign_in)
%li.nav-item
%li.nav-item.m-auto
%div
- sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in')
= link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
......
......@@ -11,8 +11,9 @@
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
Report as abuse
= _('Report abuse to GitLab')
- if note_editable
%li
= link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
%span.text-danger Delete comment
%span.text-danger
= _('Delete comment')
......@@ -91,7 +91,7 @@
%code \(\d+.\d+\%\) covered
%li
pytest-cov (Python) -
%code \d+\%\s*$
%code ^TOTAL\s+\d+\s+\d+\s+(\d+\%)$
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
......
......@@ -8,7 +8,7 @@
%p= _("You can also star a label to make it a priority label.")
.text-center
- if can?(current_user, :admin_label, @project)
= link_to _('New label'), new_project_label_path(@project), class: 'btn btn-success', title: _('New label'), id: 'new_label_link'
= link_to _('New label'), new_project_label_path(@project), class: 'btn btn-success qa-label-create-new', title: _('New label'), id: 'new_label_link'
= link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: _('Generate a default set of labels'), id: 'generate_labels_link'
- if can?(current_user, :admin_label, @group)
= link_to _('New label'), new_group_label_path(@group), class: 'btn btn-success', title: _('New label'), id: 'new_label_link'
......@@ -40,6 +40,8 @@ class EmailReceiverWorker
"You are not allowed to perform this action. If you believe this is in error, contact a staff member."
when Gitlab::Email::NoteableNotFoundError
"The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
when Gitlab::Email::InvalidAttachment
error.message
when Gitlab::Email::InvalidRecordError
can_retry = true
error.message
......
---
title: Issue board card design
merge_request: 21229
author:
type: changed
---
title: Add endpoints for simulating certain failure modes in the application
merge_request: 22746
author:
type: other
---
title: Fix environment status in merge request widget
merge_request: 22799
author:
type: changed
---
title: Make new merge request URL more friendly when pushing code
merge_request: 22526
author: "@blackst0ne"
type: changed
---
title: Allow adding patches when creating a merge request via email
merge_request: 22723
author: Serdar Dogruyol
type: added
---
title: Show expand all diffs button when a single diff file is collapsed
merge_request:
author:
type: fixed
---
title: Expose {closed,merged}_{at,by} in merge requests API index
merge_request:
author:
type: changed
---
title: Add endpoint to update a git submodule reference
merge_request: 20949
author:
type: added
---
title: Fix bug with wiki page create message
merge_request: 22849
author:
type: fixed
---
title: Bump Gitaly to 0.129.0
merge_request: 22868
author:
type: added
---
title: Align sign in button
merge_request: 22888
author: George Tsiolis
type: fixed
---
title: Use merge request prefix symbol in event feed title
merge_request: 22449
author: George Tsiolis
type: changed
---
title: Introduce Knative support
author: Chris Baumbauer
merge_request: 43959
type: added
---
title: Allow to configure when to retry failed CI jobs
merge_request: 21758
author: Markus Doits
type: added
---
title: Allow commenting on any diff line in Merge Requests
merge_request: 22398
author:
type: added
---
title: 'Rails5: fix mysql milliseconds issue in deployment model specs'
merge_request: 22850
author: Jasper Maes
type: other
---
title: Remove asset_sync gem from Gemfile and related code from codebase
merge_request: 22610
author:
type: other
---
title: Fix statement timeouts in RemoveRestrictedTodos migration
merge_request: 22795
author:
type: other
---
title: Paginate Bitbucket Server importer projects
merge_request: 22825
author:
type: changed
---
title: Add dynamic timer to delayed jobs
merge_request: 22382
author:
type: changed
AssetSync.configure do |config|
# Disable the asset_sync gem by default. If it is enabled, but not configured,
# asset_sync will cause the build to fail.
config.enabled = if ENV.has_key?('ASSET_SYNC_ENABLED')
ENV['ASSET_SYNC_ENABLED'] == 'true'
else
false
end
# Pulled from https://github.com/AssetSync/asset_sync/blob/v2.2.0/lib/asset_sync/engine.rb#L15-L40
# This allows us to disable asset_sync by default and configure through environment variables
# Updates to asset_sync gem should be checked
config.fog_provider = ENV['FOG_PROVIDER'] if ENV.has_key?('FOG_PROVIDER')
config.fog_directory = ENV['FOG_DIRECTORY'] if ENV.has_key?('FOG_DIRECTORY')
config.fog_region = ENV['FOG_REGION'] if ENV.has_key?('FOG_REGION')
config.aws_access_key_id = ENV['ASSETS_AWS_ACCESS_KEY_ID'] if ENV.has_key?('ASSETS_AWS_ACCESS_KEY_ID')
config.aws_secret_access_key = ENV['ASSETS_AWS_SECRET_ACCESS_KEY'] if ENV.has_key?('ASSETS_AWS_SECRET_ACCESS_KEY')
config.aws_reduced_redundancy = ENV['AWS_REDUCED_REDUNDANCY'] == true if ENV.has_key?('AWS_REDUCED_REDUNDANCY')
config.rackspace_username = ENV['RACKSPACE_USERNAME'] if ENV.has_key?('RACKSPACE_USERNAME')
config.rackspace_api_key = ENV['RACKSPACE_API_KEY'] if ENV.has_key?('RACKSPACE_API_KEY')
config.google_storage_access_key_id = ENV['GOOGLE_STORAGE_ACCESS_KEY_ID'] if ENV.has_key?('GOOGLE_STORAGE_ACCESS_KEY_ID')
config.google_storage_secret_access_key = ENV['GOOGLE_STORAGE_SECRET_ACCESS_KEY'] if ENV.has_key?('GOOGLE_STORAGE_SECRET_ACCESS_KEY')
config.existing_remote_files = ENV['ASSET_SYNC_EXISTING_REMOTE_FILES'] || "keep"
config.gzip_compression = (ENV['ASSET_SYNC_GZIP_COMPRESSION'] == 'true') if ENV.has_key?('ASSET_SYNC_GZIP_COMPRESSION')
config.manifest = (ENV['ASSET_SYNC_MANIFEST'] == 'true') if ENV.has_key?('ASSET_SYNC_MANIFEST')
end
return unless Shard.connected?
return if Gitlab::Database.read_only?
Shard.populate!
if Shard.connected? && !Gitlab::Database.read_only?
Shard.populate!
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment