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 ...@@ -431,7 +431,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # 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 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6' gem 'google-protobuf', '~> 3.6'
...@@ -446,6 +446,3 @@ gem 'flipper-active_support_cache_store', '~> 0.13.0' ...@@ -446,6 +446,3 @@ gem 'flipper-active_support_cache_store', '~> 0.13.0'
# Structured logging # Structured logging
gem 'lograge', '~> 0.5' gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.7' gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.4'
...@@ -58,11 +58,6 @@ GEM ...@@ -58,11 +58,6 @@ GEM
asciidoctor (1.5.6.2) asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.8) asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0) ast (2.4.0)
atomic (1.1.99) atomic (1.1.99)
attr_encrypted (3.1.0) attr_encrypted (3.1.0)
...@@ -298,9 +293,8 @@ GEM ...@@ -298,9 +293,8 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.118.1) gitaly-proto (0.123.0)
google-protobuf (~> 3.1) grpc (~> 1.0)
grpc (~> 1.10)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-markup (1.6.4) gitlab-markup (1.6.4)
...@@ -968,7 +962,6 @@ DEPENDENCIES ...@@ -968,7 +962,6 @@ DEPENDENCIES
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.6) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0) attr_encrypted (~> 3.1.0)
awesome_print awesome_print
aws-sdk aws-sdk
...@@ -1034,7 +1027,7 @@ DEPENDENCIES ...@@ -1034,7 +1027,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1) gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.4)
...@@ -1195,4 +1188,4 @@ DEPENDENCIES ...@@ -1195,4 +1188,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.16.6 1.17.1
...@@ -61,11 +61,6 @@ GEM ...@@ -61,11 +61,6 @@ GEM
asciidoctor (1.5.6.2) asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.8) asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0) ast (2.4.0)
atomic (1.1.99) atomic (1.1.99)
attr_encrypted (3.1.0) attr_encrypted (3.1.0)
...@@ -301,9 +296,8 @@ GEM ...@@ -301,9 +296,8 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.118.1) gitaly-proto (0.123.0)
google-protobuf (~> 3.1) grpc (~> 1.0)
grpc (~> 1.10)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-markup (1.6.4) gitlab-markup (1.6.4)
...@@ -977,7 +971,6 @@ DEPENDENCIES ...@@ -977,7 +971,6 @@ DEPENDENCIES
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.6) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0) attr_encrypted (~> 3.1.0)
awesome_print awesome_print
aws-sdk aws-sdk
...@@ -1043,7 +1036,7 @@ DEPENDENCIES ...@@ -1043,7 +1036,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1) gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.4)
...@@ -1204,4 +1197,4 @@ DEPENDENCIES ...@@ -1204,4 +1197,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.16.6 1.17.1
...@@ -3,7 +3,10 @@ import { GlTooltipDirective } from '@gitlab-org/gitlab-ui'; ...@@ -3,7 +3,10 @@ import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
<<<<<<< HEAD
import IssueCardWeight from 'ee/boards/components/issue_card_weight.vue'; 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 UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import IssueDueDate from './issue_due_date.vue'; import IssueDueDate from './issue_due_date.vue';
...@@ -16,7 +19,10 @@ export default { ...@@ -16,7 +19,10 @@ export default {
UserAvatarLink, UserAvatarLink,
TooltipOnTruncate, TooltipOnTruncate,
IssueDueDate, IssueDueDate,
<<<<<<< HEAD
IssueCardWeight, IssueCardWeight,
=======
>>>>>>> upstream/master
IssueTimeEstimate, IssueTimeEstimate,
}, },
directives: { directives: {
...@@ -216,10 +222,13 @@ export default { ...@@ -216,10 +222,13 @@ export default {
/><issue-time-estimate /><issue-time-estimate
v-if="issue.timeEstimate" v-if="issue.timeEstimate"
:estimate="issue.timeEstimate" :estimate="issue.timeEstimate"
<<<<<<< HEAD
/><issue-card-weight /><issue-card-weight
v-if="issue.weight" v-if="issue.weight"
:weight="issue.weight" :weight="issue.weight"
@click="filterByWeight(issue.weight)" @click="filterByWeight(issue.weight)"
=======
>>>>>>> upstream/master
/> />
</span> </span>
</div> </div>
......
...@@ -41,8 +41,12 @@ export default { ...@@ -41,8 +41,12 @@ export default {
placement="bottom" placement="bottom"
class="js-issue-time-estimate" class="js-issue-time-estimate"
> >
<<<<<<< HEAD
<span class="bold">{{ __('Time estimate') }}</span> <span class="bold">{{ __('Time estimate') }}</span>
<br /> <br />
=======
<span class="bold d-block">{{ __('Time estimate') }}</span>
>>>>>>> upstream/master
{{ title }} {{ title }}
</gl-tooltip> </gl-tooltip>
</span> </span>
......
...@@ -28,6 +28,7 @@ export default class Clusters { ...@@ -28,6 +28,7 @@ export default class Clusters {
installIngressPath, installIngressPath,
installRunnerPath, installRunnerPath,
installJupyterPath, installJupyterPath,
installKnativePath,
installPrometheusPath, installPrometheusPath,
managePrometheusPath, managePrometheusPath,
clusterStatus, clusterStatus,
...@@ -49,6 +50,7 @@ export default class Clusters { ...@@ -49,6 +50,7 @@ export default class Clusters {
installRunnerEndpoint: installRunnerPath, installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath, installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath, installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath,
}); });
this.installApplication = this.installApplication.bind(this); this.installApplication = this.installApplication.bind(this);
......
...@@ -7,6 +7,7 @@ import helmLogo from 'images/cluster_app_logos/helm.png'; ...@@ -7,6 +7,7 @@ import helmLogo from 'images/cluster_app_logos/helm.png';
import jeagerLogo from 'images/cluster_app_logos/jeager.png'; import jeagerLogo from 'images/cluster_app_logos/jeager.png';
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png'; import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.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 meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import { s__, sprintf } from '../../locale'; import { s__, sprintf } from '../../locale';
...@@ -53,6 +54,7 @@ export default { ...@@ -53,6 +54,7 @@ export default {
jeagerLogo, jeagerLogo,
jupyterhubLogo, jupyterhubLogo,
kubernetesLogo, kubernetesLogo,
knativeLogo,
meltanoLogo, meltanoLogo,
prometheusLogo, prometheusLogo,
}), }),
...@@ -136,6 +138,9 @@ export default { ...@@ -136,6 +138,9 @@ export default {
jupyterHostname() { jupyterHostname() {
return this.applications.jupyter.hostname; return this.applications.jupyter.hostname;
}, },
knativeInstalled() {
return this.applications.knative.status === APPLICATION_STATUS.INSTALLED;
},
}, },
created() { created() {
this.helmInstallIllustration = helmInstallIllustration; this.helmInstallIllustration = helmInstallIllustration;
...@@ -321,7 +326,6 @@ export default { ...@@ -321,7 +326,6 @@ export default {
:request-reason="applications.jupyter.requestReason" :request-reason="applications.jupyter.requestReason"
:install-application-request-params="{ hostname: applications.jupyter.hostname }" :install-application-request-params="{ hostname: applications.jupyter.hostname }"
:disabled="!helmInstalled" :disabled="!helmInstalled"
class="hide-bottom-border rounded-bottom"
title-link="https://jupyterhub.readthedocs.io/en/stable/" title-link="https://jupyterhub.readthedocs.io/en/stable/"
> >
<div slot="description"> <div slot="description">
...@@ -371,6 +375,58 @@ export default { ...@@ -371,6 +375,58 @@ export default {
</template> </template>
</div> </div>
</application-row> </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> </div>
</section> </section>
</template> </template>
...@@ -16,3 +16,4 @@ export const REQUEST_SUCCESS = 'request-success'; ...@@ -16,3 +16,4 @@ export const REQUEST_SUCCESS = 'request-success';
export const REQUEST_FAILURE = 'request-failure'; export const REQUEST_FAILURE = 'request-failure';
export const INGRESS = 'ingress'; export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter'; export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
...@@ -9,6 +9,7 @@ export default class ClusterService { ...@@ -9,6 +9,7 @@ export default class ClusterService {
runner: this.options.installRunnerEndpoint, runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint, prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint, jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
}; };
} }
......
import { s__ } from '../../locale'; import { s__ } from '../../locale';
import { INGRESS, JUPYTER } from '../constants'; import { INGRESS, JUPYTER, KNATIVE } from '../constants';
export default class ClusterStore { export default class ClusterStore {
constructor() { constructor() {
...@@ -46,6 +46,14 @@ export default class ClusterStore { ...@@ -46,6 +46,14 @@ export default class ClusterStore {
requestReason: null, requestReason: null,
hostname: 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 { ...@@ -93,6 +101,9 @@ export default class ClusterStore {
(this.state.applications.ingress.externalIp (this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io` ? `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 { ...@@ -36,7 +36,7 @@ export default {
}, },
computed: { computed: {
...mapState('diffs', ['commit', 'showTreeList']), ...mapState('diffs', ['commit', 'showTreeList']),
...mapGetters('diffs', ['isInlineView', 'isParallelView', 'areAllFilesCollapsed']), ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'hasCollapsedFile']),
comparableDiffs() { comparableDiffs() {
return this.mergeRequestDiffs.slice(1); return this.mergeRequestDiffs.slice(1);
}, },
...@@ -113,8 +113,8 @@ export default { ...@@ -113,8 +113,8 @@ export default {
class="inline-parallel-buttons d-none d-md-flex ml-auto" class="inline-parallel-buttons d-none d-md-flex ml-auto"
> >
<a <a
v-if="areAllFilesCollapsed" v-show="hasCollapsedFile"
class="btn btn-default" class="btn btn-default append-right-8"
@click="expandAllFiles" @click="expandAllFiles"
> >
{{ __('Expand all') }} {{ __('Expand all') }}
......
...@@ -55,11 +55,6 @@ export default { ...@@ -55,11 +55,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isContextLine: {
type: Boolean,
required: false,
default: false,
},
isHover: { isHover: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -81,7 +76,6 @@ export default { ...@@ -81,7 +76,6 @@ export default {
this.showCommentButton && this.showCommentButton &&
this.isHover && this.isHover &&
!this.isMatchLine && !this.isMatchLine &&
!this.isContextLine &&
!this.isMetaLine && !this.isMetaLine &&
!this.hasDiscussions !this.hasDiscussions
); );
......
...@@ -3,7 +3,6 @@ import { mapGetters } from 'vuex'; ...@@ -3,7 +3,6 @@ import { mapGetters } from 'vuex';
import DiffLineGutterContent from './diff_line_gutter_content.vue'; import DiffLineGutterContent from './diff_line_gutter_content.vue';
import { import {
MATCH_LINE_TYPE, MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
EMPTY_CELL_TYPE, EMPTY_CELL_TYPE,
OLD_LINE_TYPE, OLD_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE, OLD_NO_NEW_LINE_TYPE,
...@@ -71,9 +70,6 @@ export default { ...@@ -71,9 +70,6 @@ export default {
isMatchLine() { isMatchLine() {
return this.line.type === MATCH_LINE_TYPE; return this.line.type === MATCH_LINE_TYPE;
}, },
isContextLine() {
return this.line.type === CONTEXT_LINE_TYPE;
},
isMetaLine() { isMetaLine() {
const { type } = this.line; const { type } = this.line;
...@@ -88,11 +84,7 @@ export default { ...@@ -88,11 +84,7 @@ export default {
[type]: type, [type]: type,
[LINE_UNFOLD_CLASS_NAME]: this.isMatchLine, [LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
[LINE_HOVER_CLASS_NAME]: [LINE_HOVER_CLASS_NAME]:
this.isLoggedIn && this.isLoggedIn && this.isHover && !this.isMatchLine && !this.isMetaLine,
this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
!this.isMetaLine,
}; };
}, },
lineNumber() { lineNumber() {
......
...@@ -4,8 +4,6 @@ import DiffTableCell from './diff_table_cell.vue'; ...@@ -4,8 +4,6 @@ import DiffTableCell from './diff_table_cell.vue';
import { import {
NEW_LINE_TYPE, NEW_LINE_TYPE,
OLD_LINE_TYPE, OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
CONTEXT_LINE_CLASS_NAME,
PARALLEL_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE,
LINE_POSITION_LEFT, LINE_POSITION_LEFT,
LINE_POSITION_RIGHT, LINE_POSITION_RIGHT,
...@@ -41,13 +39,9 @@ export default { ...@@ -41,13 +39,9 @@ export default {
}, },
computed: { computed: {
...mapGetters('diffs', ['isInlineView']), ...mapGetters('diffs', ['isInlineView']),
isContextLine() {
return this.line.type === CONTEXT_LINE_TYPE;
},
classNameMap() { classNameMap() {
return { return {
[this.line.type]: this.line.type, [this.line.type]: this.line.type,
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView, [PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
}; };
}, },
......
...@@ -5,8 +5,6 @@ import DiffTableCell from './diff_table_cell.vue'; ...@@ -5,8 +5,6 @@ import DiffTableCell from './diff_table_cell.vue';
import { import {
NEW_LINE_TYPE, NEW_LINE_TYPE,
OLD_LINE_TYPE, OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
CONTEXT_LINE_CLASS_NAME,
OLD_NO_NEW_LINE_TYPE, OLD_NO_NEW_LINE_TYPE,
PARALLEL_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE, NEW_NO_NEW_LINE_TYPE,
...@@ -43,12 +41,8 @@ export default { ...@@ -43,12 +41,8 @@ export default {
}; };
}, },
computed: { computed: {
isContextLine() {
return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
},
classNameMap() { classNameMap() {
return { return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: true, [PARALLEL_DIFF_VIEW_TYPE]: true,
}; };
}, },
......
...@@ -3,7 +3,6 @@ export const PARALLEL_DIFF_VIEW_TYPE = 'parallel'; ...@@ -3,7 +3,6 @@ export const PARALLEL_DIFF_VIEW_TYPE = 'parallel';
export const MATCH_LINE_TYPE = 'match'; export const MATCH_LINE_TYPE = 'match';
export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline'; export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline';
export const NEW_NO_NEW_LINE_TYPE = 'new-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 EMPTY_CELL_TYPE = 'empty-cell';
export const COMMENT_FORM_TYPE = 'commentForm'; export const COMMENT_FORM_TYPE = 'commentForm';
export const DIFF_NOTE_TYPE = 'DiffNote'; export const DIFF_NOTE_TYPE = 'DiffNote';
...@@ -22,7 +21,6 @@ export const LINE_SIDE_RIGHT = 'right-side'; ...@@ -22,7 +21,6 @@ export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view'; export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
export const LINE_HOVER_CLASS_NAME = 'is-over'; export const LINE_HOVER_CLASS_NAME = 'is-over';
export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold'; export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold';
export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20; export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3; export const COUNT_OF_AVATARS_IN_GUTTER = 3;
......
...@@ -5,7 +5,7 @@ export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW ...@@ -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 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); export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
......
...@@ -65,7 +65,13 @@ export default { ...@@ -65,7 +65,13 @@ export default {
const { highlightedDiffLines, parallelDiffLines } = diffFile; const { highlightedDiffLines, parallelDiffLines } = diffFile;
removeMatchLine(diffFile, lineNumbers, bottom); 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({ addContextLines({
inlineLines: highlightedDiffLines, inlineLines: highlightedDiffLines,
parallelLines: parallelDiffLines, parallelLines: parallelDiffLines,
......
...@@ -10,7 +10,10 @@ import deployBoard from 'ee/environments/components/deploy_board_component.vue'; ...@@ -10,7 +10,10 @@ import deployBoard from 'ee/environments/components/deploy_board_component.vue';
export default { export default {
components: { components: {
environmentItem, environmentItem,
<<<<<<< HEAD
deployBoard, deployBoard,
=======
>>>>>>> upstream/master
GlLoadingIcon, GlLoadingIcon,
}, },
......
...@@ -7,9 +7,12 @@ import { polyfillSticky } from '~/lib/utils/sticky'; ...@@ -7,9 +7,12 @@ import { polyfillSticky } from '~/lib/utils/sticky';
import bp from '~/breakpoints'; import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue'; import Callout from '~/vue_shared/components/callout.vue';
<<<<<<< HEAD
// ee-only start // ee-only start
import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue'; import SharedRunner from 'ee/jobs/components/shared_runner_limit_block.vue';
// ee-only end // ee-only end
=======
>>>>>>> upstream/master
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import createStore from '../store'; import createStore from '../store';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
...@@ -19,6 +22,8 @@ import Log from './job_log.vue'; ...@@ -19,6 +22,8 @@ import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue'; import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue'; import StuckBlock from './stuck_block.vue';
import Sidebar from './sidebar.vue'; import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin';
export default { export default {
name: 'JobPageApp', name: 'JobPageApp',
...@@ -29,14 +34,19 @@ export default { ...@@ -29,14 +34,19 @@ export default {
EmptyState, EmptyState,
EnvironmentsBlock, EnvironmentsBlock,
ErasedBlock, ErasedBlock,
<<<<<<< HEAD
GlLoadingIcon, GlLoadingIcon,
=======
>>>>>>> upstream/master
Icon, Icon,
Log, Log,
LogTopBar, LogTopBar,
StuckBlock, StuckBlock,
SharedRunner, SharedRunner,
Sidebar, Sidebar,
GlLoadingIcon,
}, },
mixins: [delayedJobMixin],
props: { props: {
runnerSettingsUrl: { runnerSettingsUrl: {
type: String, type: String,
...@@ -97,6 +107,17 @@ export default { ...@@ -97,6 +107,17 @@ export default {
shouldRenderContent() { shouldRenderContent() {
return !this.isLoading && !this.hasError; return !this.isLoading && !this.hasError;
}, },
emptyStateTitle() {
const { emptyStateIllustration, remainingTime } = this;
const { title } = emptyStateIllustration;
if (this.isDelayedJob) {
return sprintf(title, { remainingTime });
}
return title;
},
}, },
watch: { watch: {
// Once the job log is loaded, // Once the job log is loaded,
...@@ -239,16 +260,28 @@ export default { ...@@ -239,16 +260,28 @@ export default {
:erased-at="job.erased_at" :erased-at="job.erased_at"
/> />
<<<<<<< HEAD
<div <div
=======
<div
>>>>>>> upstream/master
v-if="job.archived" v-if="job.archived"
ref="sticky" ref="sticky"
class="js-archived-job prepend-top-default archived-sticky sticky-top" class="js-archived-job prepend-top-default archived-sticky sticky-top"
> >
<<<<<<< HEAD
<icon
name="lock"
class="align-text-bottom"
/>
=======
<icon <icon
name="lock" name="lock"
class="align-text-bottom" class="align-text-bottom"
/> />
>>>>>>> upstream/master
{{ __('This job is archived. Only the complete pipeline can be retried.') }} {{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div> </div>
<!--job log --> <!--job log -->
...@@ -285,7 +318,7 @@ export default { ...@@ -285,7 +318,7 @@ export default {
class="js-job-empty-state" class="js-job-empty-state"
:illustration-path="emptyStateIllustration.image" :illustration-path="emptyStateIllustration.image"
:illustration-size-class="emptyStateIllustration.size" :illustration-size-class="emptyStateIllustration.size"
:title="emptyStateIllustration.title" :title="emptyStateTitle"
:content="emptyStateIllustration.content" :content="emptyStateIllustration.content"
:action="emptyStateAction" :action="emptyStateAction"
/> />
......
<script> <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 CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
import { sprintf } from '~/locale';
export default { export default {
components: { components: {
...@@ -10,8 +13,9 @@ export default { ...@@ -10,8 +13,9 @@ export default {
GlLink, GlLink,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, tooltip,
}, },
mixins: [delayedJobMixin],
props: { props: {
job: { job: {
type: Object, type: Object,
...@@ -24,7 +28,14 @@ export default { ...@@ -24,7 +28,14 @@ export default {
}, },
computed: { computed: {
tooltipText() { 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 { ...@@ -39,7 +50,7 @@ export default {
}" }"
> >
<gl-link <gl-link
v-gl-tooltip v-tooltip
:href="job.status.details_path" :href="job.status.details_path"
:title="tooltipText" :title="tooltipText"
data-boundary="viewport" 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 { ...@@ -248,7 +248,7 @@ export default {
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left"> <ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse"> <li v-if="canReportAsAbuse">
<a :href="reportAbusePath"> <a :href="reportAbusePath">
Report as abuse {{ __('Report abuse to GitLab') }}
</a> </a>
</li> </li>
<li v-if="noteUrl"> <li v-if="noteUrl">
...@@ -257,7 +257,7 @@ export default { ...@@ -257,7 +257,7 @@ export default {
type="button" type="button"
class="btn-default btn-transparent js-btn-copy-note-link" class="btn-default btn-transparent js-btn-copy-note-link"
> >
Copy link {{ __('Copy link') }}
</button> </button>
</li> </li>
<li v-if="canEdit"> <li v-if="canEdit">
...@@ -266,7 +266,7 @@ export default { ...@@ -266,7 +266,7 @@ export default {
type="button" type="button"
@click.prevent="onDelete"> @click.prevent="onDelete">
<span class="text-danger"> <span class="text-danger">
Delete comment {{ __('Delete comment') }}
</span> </span>
</button> </button>
</li> </li>
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
import ActionComponent from './action_component.vue'; import ActionComponent from './action_component.vue';
import JobNameComponent from './job_name_component.vue'; import JobNameComponent from './job_name_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; 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. * Renders the badge for the pipeline graph and the job's dropdown.
...@@ -36,6 +38,7 @@ export default { ...@@ -36,6 +38,7 @@ export default {
directives: { directives: {
tooltip, tooltip,
}, },
mixins: [delayedJobMixin],
props: { props: {
job: { job: {
type: Object, type: Object,
...@@ -52,6 +55,7 @@ export default { ...@@ -52,6 +55,7 @@ export default {
default: Infinity, default: Infinity,
}, },
}, },
computed: { computed: {
status() { status() {
return this.job && this.job.status ? this.job.status : {}; return this.job && this.job.status ? this.job.status : {};
...@@ -59,17 +63,23 @@ export default { ...@@ -59,17 +63,23 @@ export default {
tooltipText() { tooltipText() {
const textBuilder = []; const textBuilder = [];
const { name: jobName } = this.job;
if (this.job.name) { if (jobName) {
textBuilder.push(this.job.name); textBuilder.push(jobName);
} }
if (this.job.name && this.status.tooltip) { const { tooltip: statusTooltip } = this.status;
if (jobName && statusTooltip) {
textBuilder.push('-'); textBuilder.push('-');
} }
if (this.status.tooltip) { if (statusTooltip) {
textBuilder.push(this.job.status.tooltip); if (this.isDelayedJob) {
textBuilder.push(sprintf(statusTooltip, { remainingTime: this.remainingTime }));
} else {
textBuilder.push(statusTooltip);
}
} }
return textBuilder.join(' '); return textBuilder.join(' ');
...@@ -88,6 +98,7 @@ export default { ...@@ -88,6 +98,7 @@ export default {
return this.job.status && this.job.status.action && this.job.status.action.path; return this.job.status && this.job.status.action && this.job.status.action.path;
}, },
}, },
methods: { methods: {
pipelineActionRequestComplete() { pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete'); this.$emit('pipelineActionRequestComplete');
......
...@@ -722,6 +722,7 @@ ...@@ -722,6 +722,7 @@
} }
} }
<<<<<<< HEAD
.boards-switcher { .boards-switcher {
padding-right: 10px; padding-right: 10px;
} }
...@@ -765,6 +766,8 @@ ...@@ -765,6 +766,8 @@
} }
} }
=======
>>>>>>> upstream/master
.board-card-info { .board-card-info {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
white-space: nowrap; white-space: nowrap;
......
...@@ -100,7 +100,12 @@ module Boards ...@@ -100,7 +100,12 @@ module Boards
.merge(board_id: params[:board_id], list_id: params[:list_id], request: request) .merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
end end
def serializer
IssueSerializer.new(current_user: current_user)
end
def serialize_as_json(resource) def serialize_as_json(resource)
<<<<<<< HEAD
resource.as_json( resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight, :time_estimate], only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight, :time_estimate],
labels: true, labels: true,
...@@ -112,6 +117,9 @@ module Boards ...@@ -112,6 +117,9 @@ module Boards
milestone: { only: [:id, :title] } milestone: { only: [:id, :title] }
} }
) )
=======
serializer.represent(resource, serializer: 'board', include_full_project_path: board.group_board?)
>>>>>>> upstream/master
end end
def whitelist_query_limiting 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 ...@@ -86,10 +86,10 @@ module CreatesCommit
def new_merge_request_path def new_merge_request_path
project_new_merge_request_path( project_new_merge_request_path(
@project_to_commit_into, @project_to_commit_into,
merge_request_source_branch: @branch_name,
merge_request: { merge_request: {
source_project_id: @project_to_commit_into.id, source_project_id: @project_to_commit_into.id,
target_project_id: @project.id, target_project_id: @project.id,
source_branch: @branch_name,
target_branch: @start_branch target_branch: @start_branch
} }
) )
......
...@@ -54,14 +54,14 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -54,14 +54,14 @@ class Import::BitbucketServerController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def status 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? } # 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 = find_already_added_projects('bitbucket_server')
already_added_projects_names = @already_added_projects.pluck(:import_source) 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 rescue BitbucketServer::Connection::ConnectionError, BitbucketServer::Client::ServerError => e
flash[:alert] = "Unable to connect to server: #{e}" flash[:alert] = "Unable to connect to server: #{e}"
clear_session_data clear_session_data
...@@ -75,6 +75,12 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -75,6 +75,12 @@ class Import::BitbucketServerController < Import::BaseController
private 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 def bitbucket_client
@bitbucket_client ||= BitbucketServer::Client.new(credentials) @bitbucket_client ||= BitbucketServer::Client.new(credentials)
end end
...@@ -130,4 +136,12 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -130,4 +136,12 @@ class Import::BitbucketServerController < Import::BaseController
password: session[personal_access_token_key] password: session[personal_access_token_key]
} }
end end
def page_offset
[0, params[:page].to_i].max
end
def limit_per_page
BitbucketServer::Paginator::PAGE_LENGTH
end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Projects::AutocompleteSourcesController < Projects::ApplicationController class Projects::AutocompleteSourcesController < Projects::ApplicationController
<<<<<<< HEAD
prepend EE::Projects::AutocompleteSourcesController prepend EE::Projects::AutocompleteSourcesController
=======
>>>>>>> upstream/master
def members def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target) render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
end end
......
...@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController
@lines.map! do |line| @lines.map! do |line|
# These are marked as context lines but are loaded from blobs. # These are marked as context lines but are loaded from blobs.
# We also have context lines loaded from diffs in other places. # 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.rich_text = line
diff_line diff_line
end end
......
...@@ -89,8 +89,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -89,8 +89,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def build_merge_request def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) 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 @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
end end
......
...@@ -22,6 +22,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -22,6 +22,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs def render_diffs
@environment = @merge_request.environments_for(current_user).last @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 @diffs.write_cache
......
...@@ -13,8 +13,8 @@ module CompareHelper ...@@ -13,8 +13,8 @@ module CompareHelper
def create_mr_path(from = params[:from], to = params[:to], project = @project) def create_mr_path(from = params[:from], to = params[:to], project = @project)
project_new_merge_request_path( project_new_merge_request_path(
project, project,
merge_request_source_branch: to,
merge_request: { merge_request: {
source_branch: to,
target_branch: from target_branch: from
} }
) )
......
...@@ -91,7 +91,14 @@ module EventsHelper ...@@ -91,7 +91,14 @@ module EventsHelper
words << "##{event.target_iid}" if event.target_iid words << "##{event.target_iid}" if event.target_iid
words << "in" words << "in"
elsif event.target 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 << event.target.title if event.target.respond_to?(:title)
words << "at" words << "at"
end end
......
...@@ -13,10 +13,10 @@ module MergeRequestsHelper ...@@ -13,10 +13,10 @@ module MergeRequestsHelper
def new_mr_from_push_event(event, target_project) def new_mr_from_push_event(event, target_project)
{ {
merge_request_source_branch: event.branch_name,
merge_request: { merge_request: {
source_project_id: event.project.id, source_project_id: event.project.id,
target_project_id: target_project.id, target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref target_branch: target_project.repository.root_ref
} }
} }
...@@ -53,10 +53,10 @@ module MergeRequestsHelper ...@@ -53,10 +53,10 @@ module MergeRequestsHelper
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
project_new_merge_request_path( project_new_merge_request_path(
@project, @project,
merge_request_source_branch: merge_request.source_branch,
merge_request: { merge_request: {
source_project_id: merge_request.source_project_id, source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id, target_project_id: merge_request.target_project_id,
source_branch: merge_request.source_branch,
target_branch: merge_request.target_branch target_branch: merge_request.target_branch
}, },
change_branches: true change_branches: true
......
...@@ -214,6 +214,7 @@ module Ci ...@@ -214,6 +214,7 @@ module Ci
build.deployment&.succeed build.deployment&.succeed
build.run_after_commit do build.run_after_commit do
BuildSuccessWorker.perform_async(id)
PagesWorker.perform_async(:deploy, id) if build.pages_generator? PagesWorker.perform_async(:deploy, id) if build.pages_generator?
end end
end end
...@@ -223,9 +224,7 @@ module Ci ...@@ -223,9 +224,7 @@ module Ci
build.deployment&.drop build.deployment&.drop
next if build.retries_max.zero? if build.retry_failure?
if build.retries_count < build.retries_max
begin begin
Ci::Build.retry(build, build.user) Ci::Build.retry(build, build.user)
rescue Gitlab::Access::AccessDeniedError => ex rescue Gitlab::Access::AccessDeniedError => ex
...@@ -323,7 +322,17 @@ module Ci ...@@ -323,7 +322,17 @@ module Ci
end end
def retries_max 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 end
def latest? def latest?
...@@ -888,6 +897,16 @@ module Ci ...@@ -888,6 +897,16 @@ module Ci
options&.dig(:environment, :url) || persisted_environment&.external_url options&.dig(:environment, :url) || persisted_environment&.external_url
end 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 def build_attributes_from_config
return {} unless pipeline.config_processor 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 ...@@ -14,7 +14,8 @@ module Clusters
Applications::Ingress.application_name => Applications::Ingress, Applications::Ingress.application_name => Applications::Ingress,
Applications::Prometheus.application_name => Applications::Prometheus, Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner, 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 }.freeze
DEFAULT_ENVIRONMENT = '*'.freeze DEFAULT_ENVIRONMENT = '*'.freeze
...@@ -37,6 +38,7 @@ module Clusters ...@@ -37,6 +38,7 @@ module Clusters
has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus' has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
has_one :application_runner, class_name: 'Clusters::Applications::Runner' has_one :application_runner, class_name: 'Clusters::Applications::Runner'
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter' has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
has_one :application_knative, class_name: 'Clusters::Applications::Knative'
has_many :kubernetes_namespaces has_many :kubernetes_namespaces
has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace' has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
...@@ -102,7 +104,8 @@ module Clusters ...@@ -102,7 +104,8 @@ module Clusters
application_ingress || build_application_ingress, application_ingress || build_application_ingress,
application_prometheus || build_application_prometheus, application_prometheus || build_application_prometheus,
application_runner || build_application_runner, application_runner || build_application_runner,
application_jupyter || build_application_jupyter application_jupyter || build_application_jupyter,
application_knative || build_application_knative
] ]
end end
......
...@@ -28,6 +28,7 @@ module Clusters ...@@ -28,6 +28,7 @@ module Clusters
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case before_validation :enforce_namespace_to_lower_case
before_validation :enforce_ca_whitespace_trimming
validates :namespace, validates :namespace,
allow_blank: true, allow_blank: true,
...@@ -203,6 +204,11 @@ module Clusters ...@@ -203,6 +204,11 @@ module Clusters
self.namespace = self.namespace&.downcase self.namespace = self.namespace&.downcase
end end
def enforce_ca_whitespace_trimming
self.ca_pem = self.ca_pem&.strip
self.token = self.token&.strip
end
def prevent_modification def prevent_modification
return unless managed? return unless managed?
......
...@@ -39,7 +39,15 @@ module EachBatch ...@@ -39,7 +39,15 @@ module EachBatch
# #
# of - The number of rows to retrieve per batch. # of - The number of rows to retrieve per batch.
# column - The column to use for ordering the batches. # 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 unless column
raise ArgumentError, raise ArgumentError,
'the column: argument must be set to a column name to use for ordering rows' 'the column: argument must be set to a column name to use for ordering rows'
...@@ -48,7 +56,9 @@ module EachBatch ...@@ -48,7 +56,9 @@ module EachBatch
start = except(:select) start = except(:select)
.select(column) .select(column)
.reorder(column => :asc) .reorder(column => :asc)
.take
start = start.order(order_hint) if order_hint
start = start.take
return unless start return unless start
...@@ -60,6 +70,9 @@ module EachBatch ...@@ -60,6 +70,9 @@ module EachBatch
.select(column) .select(column)
.where(arel_table[column].gteq(start_id)) .where(arel_table[column].gteq(start_id))
.reorder(column => :asc) .reorder(column => :asc)
stop = stop.order(order_hint) if order_hint
stop = stop
.offset(of) .offset(of)
.limit(1) .limit(1)
.take .take
......
...@@ -10,7 +10,9 @@ class Deployment < ActiveRecord::Base ...@@ -10,7 +10,9 @@ class Deployment < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations 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 :sha, presence: true
validates :ref, presence: true validates :ref, presence: true
......
...@@ -66,6 +66,10 @@ class DiffNote < Note ...@@ -66,6 +66,10 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs self.original_position.diff_refs == diff_refs
end end
def discussion_first_note?
self == discussion.first_note
end
private private
def enqueue_diff_file_creation_job def enqueue_diff_file_creation_job
...@@ -78,10 +82,11 @@ class DiffNote < Note ...@@ -78,10 +82,11 @@ class DiffNote < Note
end end
def should_create_diff_file? 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 end
def fetch_diff_file def fetch_diff_file
file =
if note_diff_file if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash) diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff, Gitlab::Diff::File.new(diff,
...@@ -98,6 +103,12 @@ class DiffNote < Note ...@@ -98,6 +103,12 @@ class DiffNote < Note
else else
original_position.diff_file(self.project.repository) original_position.diff_file(self.project.repository)
end 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 end
def supported? def supported?
......
...@@ -52,6 +52,7 @@ class Environment < ActiveRecord::Base ...@@ -52,6 +52,7 @@ class Environment < ActiveRecord::Base
scope :in_review_folder, -> { where(environment_type: "review") } scope :in_review_folder, -> { where(environment_type: "review") }
scope :for_name, -> (name) { where(name: name) } scope :for_name, -> (name) { where(name: name) }
scope :for_project, -> (project) { where(project_id: project) } 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 state_machine :state, initial: :available do
event :start do event :start do
......
...@@ -8,17 +8,16 @@ class EnvironmentStatus ...@@ -8,17 +8,16 @@ class EnvironmentStatus
delegate :id, to: :environment delegate :id, to: :environment
delegate :name, to: :environment delegate :name, to: :environment
delegate :project, to: :environment delegate :project, to: :environment
delegate :status, to: :deployment, allow_nil: true
delegate :deployed_at, to: :deployment, allow_nil: true delegate :deployed_at, to: :deployment, allow_nil: true
def self.for_merge_request(mr, user) 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 end
def self.after_merge_request(mr, user) def self.after_merge_request(mr, user)
return [] unless mr.merged? return [] unless mr.merged?
build_environments_status(mr, user, mr.merge_pipeline) build_environments_status(mr, user, mr.merge_commit_sha)
end end
def initialize(environment, merge_request, sha) def initialize(environment, merge_request, sha)
...@@ -29,7 +28,7 @@ class EnvironmentStatus ...@@ -29,7 +28,7 @@ class EnvironmentStatus
def deployment def deployment
strong_memoize(:deployment) do strong_memoize(:deployment) do
environment.first_deployment_for(sha) Deployment.where(environment: environment).find_by_sha(sha)
end end
end end
...@@ -44,6 +43,22 @@ class EnvironmentStatus ...@@ -44,6 +43,22 @@ class EnvironmentStatus
.merge_request_diff_files.where(deleted_file: false) .merge_request_diff_files.where(deleted_file: false)
end 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 private
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
...@@ -61,21 +76,14 @@ class EnvironmentStatus ...@@ -61,21 +76,14 @@ class EnvironmentStatus
} }
end end
def self.build_environments_status(mr, user, pipeline) def self.build_environments_status(mr, user, sha)
return [] unless pipeline.present? 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, sha)
EnvironmentStatus.new(environment, mr, pipeline.sha) end.compact
end
end end
private_class_method :build_environments_status 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 end
...@@ -231,20 +231,6 @@ class Issue < ActiveRecord::Base ...@@ -231,20 +231,6 @@ class Issue < ActiveRecord::Base
def as_json(options = {}) def as_json(options = {})
super(options).tap do |json| 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) if options.key?(:labels)
json[:labels] = labels.as_json( json[:labels] = labels.as_json(
project: project, project: project,
......
...@@ -1019,6 +1019,18 @@ class Repository ...@@ -1019,6 +1019,18 @@ class Repository
message: merge_request.title) message: merge_request.title)
end 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) def blob_data_at(sha, path)
blob = blob_at(sha, path) blob = blob_at(sha, path)
return unless blob return unless blob
......
...@@ -195,7 +195,7 @@ class WikiPage ...@@ -195,7 +195,7 @@ class WikiPage
update_attributes(attrs) update_attributes(attrs)
save(page_details: title) do save(page_details: title) do
wiki.create_page(title, content, format, message) wiki.create_page(title, content, format, attrs[:message])
end end
end end
......
...@@ -180,7 +180,7 @@ def index ...@@ -180,7 +180,7 @@ def index
render json: MyResourceSerializer render json: MyResourceSerializer
.new(current_user: @current_user) .new(current_user: @current_user)
.represent_details(@project.resources) .represent_details(@project.resources)
nd end
end end
``` ```
...@@ -196,7 +196,7 @@ def index ...@@ -196,7 +196,7 @@ def index
.represent_details(@project.resources), .represent_details(@project.resources),
count: @project.resources.count count: @project.resources.count
} }
nd end
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 ...@@ -4,15 +4,17 @@ class IssueSerializer < BaseSerializer
# This overrided method takes care of which entity should be used # This overrided method takes care of which entity should be used
# to serialize the `issue` based on `basic` key in `opts` param. # to serialize the `issue` based on `basic` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope. # Hence, `entity` doesn't need to be declared on the class scope.
def represent(merge_request, opts = {}) def represent(issue, opts = {})
entity = entity =
case opts[:serializer] case opts[:serializer]
when 'sidebar' when 'sidebar'
IssueSidebarEntity IssueSidebarEntity
when 'board'
IssueBoardEntity
else else
IssueEntity IssueEntity
end end
super(merge_request, opts, entity) super(issue, opts, entity)
end end
end end
...@@ -12,4 +12,8 @@ class LabelEntity < Grape::Entity ...@@ -12,4 +12,8 @@ class LabelEntity < Grape::Entity
expose :text_color expose :text_color
expose :created_at expose :created_at
expose :updated_at expose :updated_at
expose :priority, if: -> (*) { options.key?(:project) } do |label|
label.priority(options[:project])
end
end end
...@@ -14,7 +14,8 @@ module Clusters ...@@ -14,7 +14,8 @@ module Clusters
else else
check_timeout check_timeout
end 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? app.make_errored!("Kubernetes error") unless app.errored?
end end
...@@ -51,7 +52,8 @@ module Clusters ...@@ -51,7 +52,8 @@ module Clusters
def remove_installation_pod def remove_installation_pod
helm_api.delete_pod!(install_command.pod_name) helm_api.delete_pod!(install_command.pod_name)
rescue rescue => e
Rails.logger.error "Kubernetes error: #{e.class.name} #{e.message}"
# no-op # no-op
end end
......
...@@ -45,7 +45,8 @@ module Clusters ...@@ -45,7 +45,8 @@ module Clusters
"ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress }, "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
"prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus }, "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
"runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner }, "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 end
......
...@@ -12,9 +12,11 @@ module Clusters ...@@ -12,9 +12,11 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in( ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) 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.") 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.") app.make_errored!("Can't start installation process.")
end end
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 ...@@ -19,7 +19,12 @@ module Commits
new_commit = create_commit! new_commit = create_commit!
success(result: new_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) error(ex.message)
end end
......
...@@ -128,12 +128,12 @@ class IssuableBaseService < BaseService ...@@ -128,12 +128,12 @@ class IssuableBaseService < BaseService
merge_quick_actions_into_params!(issuable) merge_quick_actions_into_params!(issuable)
end end
def merge_quick_actions_into_params!(issuable) def merge_quick_actions_into_params!(issuable, only: nil)
original_description = params.fetch(:description, issuable.description) original_description = params.fetch(:description, issuable.description)
description, command_params = description, command_params =
QuickActions::InterpretService.new(project, current_user) 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 # Avoid a description already set on an issuable to be overwritten by a nil
params[:description] = description if description params[:description] = description if description
......
...@@ -6,8 +6,12 @@ module MergeRequests ...@@ -6,8 +6,12 @@ module MergeRequests
def execute def execute
@params_issue_iid = params.delete(:issue_iid) @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.author = current_user
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.source_project = find_source_project merge_request.source_project = find_source_project
......
...@@ -50,8 +50,8 @@ module MergeRequests ...@@ -50,8 +50,8 @@ module MergeRequests
end end
def url_for_new_merge_request(branch_name) 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 } { branch_name: branch_name, url: url, new_merge_request: true }
end end
......
...@@ -29,10 +29,6 @@ module MergeRequests ...@@ -29,10 +29,6 @@ module MergeRequests
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def clear_cache(new_diff) 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 # 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 # model, as that will interfere with other actions happening when
# reloading the diff. # 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 # frozen_string_literal: true
module Notes module Notes
class CreateService < ::BaseService class CreateService < ::Notes::BaseService
def execute def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
...@@ -35,6 +35,7 @@ module Notes ...@@ -35,6 +35,7 @@ module Notes
if !only_commands && note.save if !only_commands && note.save
todo_service.new_note(note, current_user) todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
end end
if command_params.present? if command_params.present?
......
# frozen_string_literal: true # frozen_string_literal: true
module Notes module Notes
class DestroyService < BaseService class DestroyService < ::Notes::BaseService
def execute(note) def execute(note)
TodoService.new.destroy_target(note) do |note| TodoService.new.destroy_target(note) do |note|
note.destroy note.destroy
end end
clear_noteable_diffs_cache(note)
end end
end end
end end
...@@ -22,13 +22,13 @@ module QuickActions ...@@ -22,13 +22,13 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it. # 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. # 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) return [content, {}] unless current_user.can?(:use_quick_actions)
@issuable = issuable @issuable = issuable
@updates = {} @updates = {}
content, commands = extractor.extract_commands(content) content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands) extract_updates(commands)
[content, @updates] [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" - page_title _("Report abuse to GitLab")
%h3.page-title Report abuse %h3.page-title
%p Please use this form to report users who create spam issues, comments or behave inappropriately. = _('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 %hr
= form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f| = form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report) = form_errors(@abuse_report)
...@@ -16,7 +20,7 @@ ...@@ -16,7 +20,7 @@
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url) = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.form-text.text-muted .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 .form-actions
= f.submit "Send report", class: "btn btn-success" = f.submit "Send report", class: "btn btn-success"
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
.form-text.text-muted .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' - 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 } = _("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 -# EE-specific start
- if License.feature_available?(:email_additional_text) - if License.feature_available?(:email_additional_text)
.form-group .form-group
...@@ -34,5 +35,7 @@ ...@@ -34,5 +35,7 @@
.form-text.text-muted .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) } = _('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 -# EE-specific end
=======
>>>>>>> upstream/master
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus), install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner), install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter), 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', toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name, cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason, cluster_status_reason: @cluster.status_reason,
......
...@@ -84,4 +84,6 @@ ...@@ -84,4 +84,6 @@
= link_to 'import flow', status_import_bitbucket_server_path = link_to 'import flow', status_import_bitbucket_server_path
again. again.
= paginate_without_count(@collection)
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } }
...@@ -71,7 +71,7 @@ ...@@ -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 = 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') = icon('user-secret')
- if header_link?(:sign_in) - if header_link?(:sign_in)
%li.nav-item %li.nav-item.m-auto
%div %div
- sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in') - 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' = link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
......
...@@ -11,8 +11,9 @@ ...@@ -11,8 +11,9 @@
- unless is_current_user - unless is_current_user
%li %li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do = 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 - if note_editable
%li %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 = 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 @@ ...@@ -91,7 +91,7 @@
%code \(\d+.\d+\%\) covered %code \(\d+.\d+\%\) covered
%li %li
pytest-cov (Python) - pytest-cov (Python) -
%code \d+\%\s*$ %code ^TOTAL\s+\d+\s+\d+\s+(\d+\%)$
%li %li
phpunit --coverage-text --colors=never (PHP) - phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\% %code ^\s*Lines:\s*\d+.\d+\%
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%p= _("You can also star a label to make it a priority label.") %p= _("You can also star a label to make it a priority label.")
.text-center .text-center
- if can?(current_user, :admin_label, @project) - 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' = 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) - 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' = 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 ...@@ -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." "You are not allowed to perform this action. If you believe this is in error, contact a staff member."
when Gitlab::Email::NoteableNotFoundError 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." "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 when Gitlab::Email::InvalidRecordError
can_retry = true can_retry = true
error.message 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? if Shard.connected? && !Gitlab::Database.read_only?
return if Gitlab::Database.read_only? Shard.populate!
end
Shard.populate!
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