Commit 82bf55c8 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into...

Merge remote-tracking branch 'upstream/master' into 54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname

* upstream/master: (115 commits)
  [CE] Speed up login page usage
  Add new line and comments
  Fix the seeder 24_forks.rb cannot find public project
  Milestones on community contribution issues
  Removed Gitlab Upgrader found in /lib/gitlab/upgrader.rb
  Fix and move specs into admin_disables_git_access_protocol_spec.rb
  Fix HTTP/SSH clone panel for mobile
  Add spec for HTTP/SSH clone panel
  Fix missing Git clone button when protocol restriction setting enabled
  Fix deprecation: Using positional arguments in integration tests
  Extend override check to also check arity
  Update tm cli version
  Bump Gitaly version to v1.12.0
  Add @dbalexandre to CODEOWNERS
  Update verbiage for clarity
  Change group-cluster beta to regular note
  Change alpha states to use note instead of warning
  Update registry section. Update serverless.yaml formatting
  Clarify obtaining application URL
  Add @godfat to CODEOWNERS
  ...
parents cc06bb2c 145079b3
# Backend Maintainers are the default for all ruby files # Backend Maintainers are the default for all ruby files
*.rb @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern *.rb @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
*.rake @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern *.rake @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
# Technical writing team are the default reviewers for everything in `doc/` # Technical writing team are the default reviewers for everything in `doc/`
/doc/ @axil @marcia /doc/ @axil @marcia
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
### Target audience ### Target audience
<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/#/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" --> <!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" -->
### Further details ### Further details
......
This diff is collapsed.
...@@ -130,7 +130,7 @@ gem 'asciidoctor-plantuml', '0.0.8' ...@@ -130,7 +130,7 @@ gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1' gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.9' gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0' gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2' gem 'nokogiri', '~> 1.8.4'
gem 'escape_utils', '~> 1.1' gem 'escape_utils', '~> 1.1'
# Calendar rendering # Calendar rendering
...@@ -165,7 +165,7 @@ gem 'acts-as-taggable-on', '~> 5.0' ...@@ -165,7 +165,7 @@ gem 'acts-as-taggable-on', '~> 5.0'
gem 'sidekiq', '~> 5.2.1' gem 'sidekiq', '~> 5.2.1'
gem 'sidekiq-cron', '~> 0.6.0' gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.6.0' gem 'redis-namespace', '~> 1.6.0'
gem 'gitlab-sidekiq-fetcher', '~> 0.1.0', require: 'sidekiq-reliable-fetch' gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
# Cron Parser # Cron Parser
gem 'rufus-scheduler', '~> 3.4' gem 'rufus-scheduler', '~> 3.4'
...@@ -419,7 +419,7 @@ group :ed25519 do ...@@ -419,7 +419,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 1.3.0', require: 'gitaly' gem 'gitaly-proto', '~> 1.5.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0' gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6' gem 'google-protobuf', '~> 3.6'
......
...@@ -274,13 +274,13 @@ GEM ...@@ -274,13 +274,13 @@ 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 (1.3.0) gitaly-proto (1.5.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-default_value_for (3.1.1) gitlab-default_value_for (3.1.1)
activerecord (>= 3.2.0, < 6.0) activerecord (>= 3.2.0, < 6.0)
gitlab-markup (1.6.5) gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.1.0) gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
rubocop (~> 0.54.0) rubocop (~> 0.54.0)
...@@ -1008,11 +1008,11 @@ DEPENDENCIES ...@@ -1008,11 +1008,11 @@ 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 (~> 1.3.0) gitaly-proto (~> 1.5.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1) gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.6.5) gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher (~> 0.1.0) gitlab-sidekiq-fetcher (~> 0.4.0)
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2) gon (~> 6.2)
...@@ -1059,7 +1059,7 @@ DEPENDENCIES ...@@ -1059,7 +1059,7 @@ DEPENDENCIES
nakayoshi_fork (~> 0.0.4) nakayoshi_fork (~> 0.0.4)
net-ldap net-ldap
net-ssh (~> 5.0) net-ssh (~> 5.0)
nokogiri (~> 1.8.2) nokogiri (~> 1.8.4)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.9) octokit (~> 4.9)
omniauth (~> 1.8) omniauth (~> 1.8)
......
...@@ -58,6 +58,18 @@ their contributions accepted by meeting our [Definition of done][done]. ...@@ -58,6 +58,18 @@ their contributions accepted by meeting our [Definition of done][done].
What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/. What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
### Milestones on community contribution issues
The milestone of an issue that is currently being worked on by a community contributor
should not be set to a named GitLab milestone (e.g. 11.7, 11.8), until the associated
merge request is very close to being merged, and we will likely know in which named
GitLab milestone the issue will land. There are many factors that influence when
a community contributor finishes an issue, or even at all. So we should set this
milestone only when we have more certainty.
Note this only applies to issues currently assigned to community contributors. For
issues assigned to GitLabbers, we are [ambitious in assigning milestones to issues](https://about.gitlab.com/direction/#how-we-plan-releases).
## Assigning issues ## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover. If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
......
11.6.0-pre 11.7.0-pre
...@@ -29,6 +29,7 @@ const Api = { ...@@ -29,6 +29,7 @@ const Api = {
commitPipelinesPath: '/:project_id/commit/:sha/pipelines', commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches', createBranchPath: '/api/:version/projects/:id/repository/branches',
releasesPath: '/api/:version/projects/:id/releases',
group(groupId, callback) { group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
...@@ -307,6 +308,12 @@ const Api = { ...@@ -307,6 +308,12 @@ const Api = {
}); });
}, },
releases(id) {
const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id));
return axios.get(url);
},
buildUrl(url) { buildUrl(url) {
let urlRoot = ''; let urlRoot = '';
if (gon.relative_url_root != null) { if (gon.relative_url_root != null) {
......
...@@ -36,7 +36,9 @@ export default class VariableList { ...@@ -36,7 +36,9 @@ export default class VariableList {
}, },
protected: { protected: {
selector: '.js-ci-variable-input-protected', selector: '.js-ci-variable-input-protected',
default: 'false', // use `attr` instead of `data` as we don't want the value to be
// converted. we need the value as a string.
default: $('.js-ci-variable-input-protected').attr('data-default'),
}, },
environment_scope: { environment_scope: {
// We can't use a `.js-` class here because // We can't use a `.js-` class here because
......
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
/> />
<inline-diff-comment-row <inline-diff-comment-row
:key="`icr-${index}`" :key="`icr-${line.line_code || index}`"
:diff-file-hash="diffFile.file_hash" :diff-file-hash="diffFile.file_hash"
:line="line" :line="line"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
......
...@@ -43,14 +43,14 @@ export default { ...@@ -43,14 +43,14 @@ export default {
<tbody> <tbody>
<template v-for="(line, index) in diffLines"> <template v-for="(line, index) in diffLines">
<parallel-diff-table-row <parallel-diff-table-row
:key="index" :key="line.line_code"
:file-hash="diffFile.file_hash" :file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path" :context-lines-path="diffFile.context_lines_path"
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
/> />
<parallel-diff-comment-row <parallel-diff-comment-row
:key="`dcr-${index}`" :key="`dcr-${line.line_code || index}`"
:line="line" :line="line"
:diff-file-hash="diffFile.file_hash" :diff-file-hash="diffFile.file_hash"
:line-index="index" :line-index="index"
......
...@@ -196,6 +196,15 @@ export function trimFirstCharOfLineContent(line = {}) { ...@@ -196,6 +196,15 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine; return parsedLine;
} }
function getLineCode({ left, right }, index) {
if (left && left.line_code) {
return left.line_code;
} else if (right && right.line_code) {
return right.line_code;
}
return index;
}
// This prepares and optimizes the incoming diff data from the server // This prepares and optimizes the incoming diff data from the server
// by setting up incremental rendering and removing unneeded data // by setting up incremental rendering and removing unneeded data
export function prepareDiffData(diffData) { export function prepareDiffData(diffData) {
...@@ -208,6 +217,8 @@ export function prepareDiffData(diffData) { ...@@ -208,6 +217,8 @@ export function prepareDiffData(diffData) {
const linesLength = file.parallel_diff_lines.length; const linesLength = file.parallel_diff_lines.length;
for (let u = 0; u < linesLength; u += 1) { for (let u = 0; u < linesLength; u += 1) {
const line = file.parallel_diff_lines[u]; const line = file.parallel_diff_lines[u];
line.line_code = getLineCode(line, u);
if (line.left) { if (line.left) {
line.left = trimFirstCharOfLineContent(line.left); line.left = trimFirstCharOfLineContent(line.left);
line.left.hasForm = false; line.left.hasForm = false;
......
...@@ -14,6 +14,7 @@ import MonitoringButtonComponent from './environment_monitoring.vue'; ...@@ -14,6 +14,7 @@ import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit.vue'; import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { CLUSTER_TYPE } from '~/clusters/constants';
/** /**
* Environment Item Component * Environment Item Component
...@@ -84,6 +85,15 @@ export default { ...@@ -84,6 +85,15 @@ export default {
return this.model && this.model.is_protected; return this.model && this.model.is_protected;
}, },
/**
* Hide group cluster features which are not currently implemented.
*
* @returns {Boolean}
*/
disableGroupClusterFeatures() {
return this.model && this.model.cluster_type === CLUSTER_TYPE.GROUP;
},
/** /**
* Returns whether the environment can be stopped. * Returns whether the environment can be stopped.
* *
...@@ -547,6 +557,7 @@ export default { ...@@ -547,6 +557,7 @@ export default {
<terminal-button-component <terminal-button-component
v-if="model && model.terminal_path" v-if="model && model.terminal_path"
:terminal-path="model.terminal_path" :terminal-path="model.terminal_path"
:disabled="disableGroupClusterFeatures"
/> />
<rollback-component <rollback-component
......
...@@ -19,6 +19,11 @@ export default { ...@@ -19,6 +19,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
disabled: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
title() { title() {
...@@ -33,6 +38,7 @@ export default { ...@@ -33,6 +38,7 @@ export default {
:title="title" :title="title"
:aria-label="title" :aria-label="title"
:href="terminalPath" :href="terminalPath"
:class="{ disabled: disabled }"
class="btn terminal-button d-none d-sm-none d-md-block" class="btn terminal-button d-none d-sm-none d-md-block"
> >
<icon name="terminal" /> <icon name="terminal" />
......
...@@ -28,27 +28,29 @@ export default { ...@@ -28,27 +28,29 @@ export default {
</script> </script>
<template> <template>
<div class="block"> <div class="block">
<div class="title">{{ s__('Job|Job artifacts') }}</div> <div class="title font-weight-bold">{{ s__('Job|Job artifacts') }}</div>
<p v-if="isExpired" class="js-artifacts-removed build-detail-row"> <p
{{ s__('Job|The artifacts were removed') }} v-if="isExpired || willExpire"
:class="{
'js-artifacts-removed': isExpired,
'js-artifacts-will-be-removed': willExpire,
}"
class="build-detail-row"
>
<span v-if="isExpired">{{ s__('Job|The artifacts were removed') }}</span>
<span v-if="willExpire">{{ s__('Job|The artifacts will be removed') }}</span>
<timeago-tooltip v-if="artifact.expire_at" :time="artifact.expire_at" />
</p> </p>
<p v-else-if="willExpire" class="js-artifacts-will-be-removed build-detail-row"> <div class="btn-group d-flex prepend-top-10" role="group">
{{ s__('Job|The artifacts will be removed in') }}
</p>
<timeago-tooltip v-if="artifact.expire_at" :time="artifact.expire_at" />
<div class="btn-group d-flex" role="group">
<gl-link <gl-link
v-if="artifact.keep_path" v-if="artifact.keep_path"
:href="artifact.keep_path" :href="artifact.keep_path"
class="js-keep-artifacts btn btn-sm btn-default" class="js-keep-artifacts btn btn-sm btn-default"
data-method="post" data-method="post"
>{{ s__('Job|Keep') }}</gl-link
> >
{{ s__('Job|Keep') }}
</gl-link>
<gl-link <gl-link
v-if="artifact.download_path" v-if="artifact.download_path"
...@@ -56,17 +58,15 @@ export default { ...@@ -56,17 +58,15 @@ export default {
class="js-download-artifacts btn btn-sm btn-default" class="js-download-artifacts btn btn-sm btn-default"
download download
rel="nofollow" rel="nofollow"
>{{ s__('Job|Download') }}</gl-link
> >
{{ s__('Job|Download') }}
</gl-link>
<gl-link <gl-link
v-if="artifact.browse_path" v-if="artifact.browse_path"
:href="artifact.browse_path" :href="artifact.browse_path"
class="js-browse-artifacts btn btn-sm btn-default" class="js-browse-artifacts btn btn-sm btn-default"
>{{ s__('Job|Browse') }}</gl-link
> >
{{ s__('Job|Browse') }}
</gl-link>
</div> </div>
</div> </div>
</template> </template>
...@@ -31,12 +31,12 @@ export default { ...@@ -31,12 +31,12 @@ export default {
block: !isLastBlock, block: !isLastBlock,
}" }"
> >
<p> <p class="append-bottom-5">
{{ __('Commit') }} <span class="font-weight-bold">{{ __('Commit') }}</span>
<gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">{{ <gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">
commit.short_id {{ commit.short_id }}
}}</gl-link> </gl-link>
<clipboard-button <clipboard-button
:text="commit.short_id" :text="commit.short_id"
...@@ -44,11 +44,14 @@ export default { ...@@ -44,11 +44,14 @@ export default {
css-class="btn btn-clipboard btn-transparent" css-class="btn btn-clipboard btn-transparent"
/> />
<gl-link v-if="mergeRequest" :href="mergeRequest.path" class="js-link-commit link-commit" <span v-if="mergeRequest">
>!{{ mergeRequest.iid }}</gl-link {{ __('in') }}
> <gl-link :href="mergeRequest.path" class="js-link-commit link-commit"
>!{{ mergeRequest.iid }}</gl-link
>
</span>
</p> </p>
<p class="build-light-text append-bottom-0">{{ commit.title }}</p> <p class="append-bottom-0">{{ commit.title }}</p>
</div> </div>
</template> </template>
...@@ -110,22 +110,20 @@ export default { ...@@ -110,22 +110,20 @@ export default {
<aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix"> <aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix">
<div class="sidebar-container"> <div class="sidebar-container">
<div class="blocks-container"> <div class="blocks-container">
<div class="block"> <div class="block d-flex align-items-center">
<strong class="inline prepend-top-8"> {{ job.name }} </strong> <h4 class="flex-grow-1 prepend-top-8 m-0">{{ job.name }}</h4>
<gl-link <gl-link
v-if="job.retry_path" v-if="job.retry_path"
:class="retryButtonClass" :class="retryButtonClass"
:href="job.retry_path" :href="job.retry_path"
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
>{{ __('Retry') }}</gl-link
> >
{{ __('Retry') }}
</gl-link>
<gl-link <gl-link
v-if="job.terminal_path" v-if="job.terminal_path"
:href="job.terminal_path" :href="job.terminal_path"
class="js-terminal-link pull-right btn btn-primary class="js-terminal-link pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block"
btn-inverted visible-md-block visible-lg-block"
target="_blank" target="_blank"
> >
{{ __('Debug') }} <icon name="external-link" /> {{ __('Debug') }} <icon name="external-link" />
...@@ -133,8 +131,7 @@ export default { ...@@ -133,8 +131,7 @@ export default {
<gl-button <gl-button
:aria-label="__('Toggle Sidebar')" :aria-label="__('Toggle Sidebar')"
type="button" type="button"
class="btn btn-blank gutter-toggle class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle"
float-right d-block d-md-none js-sidebar-build-toggle"
@click="toggleSidebar" @click="toggleSidebar"
> >
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i> <i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
...@@ -145,25 +142,18 @@ export default { ...@@ -145,25 +142,18 @@ export default {
v-if="job.new_issue_path" v-if="job.new_issue_path"
:href="job.new_issue_path" :href="job.new_issue_path"
class="js-new-issue btn btn-success btn-inverted" class="js-new-issue btn btn-success btn-inverted"
>{{ __('New issue') }}</gl-link
> >
{{ __('New issue') }}
</gl-link>
<gl-link <gl-link
v-if="job.retry_path" v-if="job.retry_path"
:href="job.retry_path" :href="job.retry_path"
class="js-retry-job btn btn-inverted-secondary" class="js-retry-job btn btn-inverted-secondary"
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
>{{ __('Retry') }}</gl-link
> >
{{ __('Retry') }}
</gl-link>
</div> </div>
<div :class="{ block: renderBlock }"> <div :class="{ block: renderBlock }">
<p v-if="job.merge_request" class="build-detail-row js-job-mr">
<span class="build-light-text"> {{ __('Merge Request:') }} </span>
<gl-link :href="job.merge_request.path"> !{{ job.merge_request.iid }} </gl-link>
</p>
<detail-row <detail-row
v-if="job.duration" v-if="job.duration"
:value="duration" :value="duration"
...@@ -198,10 +188,10 @@ export default { ...@@ -198,10 +188,10 @@ export default {
title="Coverage" title="Coverage"
/> />
<p v-if="job.tags.length" class="build-detail-row js-job-tags"> <p v-if="job.tags.length" class="build-detail-row js-job-tags">
<span class="build-light-text"> {{ __('Tags:') }} </span> <span class="font-weight-bold">{{ __('Tags:') }}</span>
<span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary"> <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{
{{ tag }} tag
</span> }}</span>
</p> </p>
<div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group"> <div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group">
...@@ -210,9 +200,8 @@ export default { ...@@ -210,9 +200,8 @@ export default {
class="js-cancel-job btn btn-sm btn-default" class="js-cancel-job btn btn-sm btn-default"
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
>{{ __('Cancel') }}</gl-link
> >
{{ __('Cancel') }}
</gl-link>
</div> </div>
</div> </div>
......
...@@ -34,8 +34,7 @@ export default { ...@@ -34,8 +34,7 @@ export default {
</script> </script>
<template> <template>
<p class="build-detail-row"> <p class="build-detail-row">
<span v-if="hasTitle" class="build-light-text"> {{ title }}: </span> {{ value }} <span v-if="hasTitle" class="font-weight-bold">{{ title }}:</span> {{ value }}
<span v-if="hasHelpURL" class="help-button float-right"> <span v-if="hasHelpURL" class="help-button float-right">
<gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow"> <gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow">
<i class="fa fa-question-circle" aria-hidden="true"></i> <i class="fa fa-question-circle" aria-hidden="true"></i>
......
...@@ -38,11 +38,11 @@ export default { ...@@ -38,11 +38,11 @@ export default {
<div class="block-last dropdown"> <div class="block-last dropdown">
<ci-icon :status="pipeline.details.status" class="vertical-align-middle" /> <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
{{ __('Pipeline') }} <span class="font-weight-bold">{{ __('Pipeline') }}</span>
<a :href="pipeline.path" class="js-pipeline-path link-commit"> #{{ pipeline.id }} </a> <a :href="pipeline.path" class="js-pipeline-path link-commit">#{{ pipeline.id }}</a>
<template v-if="hasRef"> <template v-if="hasRef">
{{ __('from') }} {{ __('from') }}
<a :href="pipeline.ref.path" class="link-commit ref-name"> {{ pipeline.ref.name }} </a> <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a>
</template> </template>
<button <button
......
...@@ -43,23 +43,24 @@ export default { ...@@ -43,23 +43,24 @@ export default {
<template> <template>
<div class="build-widget block"> <div class="build-widget block">
<h4 class="title">{{ __('Trigger') }}</h4>
<p <p
v-if="trigger.short_token" v-if="trigger.short_token"
class="js-short-token" class="js-short-token"
:class="{ 'append-bottom-0': !hasVariables }" :class="{ 'append-bottom-5': hasVariables, 'append-bottom-0': !hasVariables }"
> >
<span class="build-light-text"> {{ __('Token') }} </span> {{ trigger.short_token }} <span class="font-weight-bold">{{ __('Trigger token:') }}</span> {{ trigger.short_token }}
</p> </p>
<template v-if="hasVariables"> <template v-if="hasVariables">
<p class="trigger-variables-btn-container"> <p class="trigger-variables-btn-container">
<span class="build-light-text"> {{ __('Variables:') }} </span> <span class="font-weight-bold">{{ __('Trigger variables:') }}</span>
<gl-button v-if="hasValues" class="group js-reveal-variables" @click="toggleValues"> <gl-button
{{ getToggleButtonText }} v-if="hasValues"
</gl-button> class="btn-sm group js-reveal-variables trigger-variables-btn"
@click="toggleValues"
>{{ getToggleButtonText }}</gl-button
>
</p> </p>
<table class="js-build-variables trigger-build-variables"> <table class="js-build-variables trigger-build-variables">
......
...@@ -82,7 +82,7 @@ export function insertMarkdownText({ ...@@ -82,7 +82,7 @@ export function insertMarkdownText({
tag, tag,
cursorOffset, cursorOffset,
blockTag, blockTag,
selected, selected = '',
wrap, wrap,
select, select,
}) { }) {
...@@ -212,7 +212,7 @@ export function addMarkdownListeners(form) { ...@@ -212,7 +212,7 @@ export function addMarkdownListeners(form) {
blockTag: $this.data('mdBlock'), blockTag: $this.data('mdBlock'),
wrap: !$this.data('mdPrepend'), wrap: !$this.data('mdPrepend'),
select: $this.data('mdSelect'), select: $this.data('mdSelect'),
tagContent: $this.data('mdTagContent').toString(), tagContent: $this.data('mdTagContent'),
}); });
}); });
} }
......
...@@ -178,31 +178,32 @@ export default { ...@@ -178,31 +178,32 @@ export default {
commitId = `<span class="commit-sha">${truncateSha(commitId)}</span>`; commitId = `<span class="commit-sha">${truncateSha(commitId)}</span>`;
} }
let text = s__('MergeRequests|started a discussion'); const {
for_commit: isForCommit,
diff_discussion: isDiffDiscussion,
active: isActive,
} = this.discussion;
if (this.discussion.for_commit) { let text = s__('MergeRequests|started a discussion');
if (isForCommit) {
text = s__( text = s__(
'MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}', 'MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}',
); );
} else if (this.discussion.diff_discussion) { } else if (isDiffDiscussion && commitId) {
if (this.discussion.active) { text = isActive
text = s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}'); ? s__('MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}')
} else { : s__(
text = s__( 'MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}',
'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}', );
); } else if (isDiffDiscussion) {
} text = isActive
? s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}')
: s__(
'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}',
);
} }
return sprintf( return sprintf(text, { commitId, linkStart, linkEnd }, false);
text,
{
commitId,
linkStart,
linkEnd,
},
false,
);
}, },
diffLine() { diffLine() {
if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) { if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) {
......
import initReleases from '~/releases';
document.addEventListener('DOMContentLoaded', initReleases);
<script>
import { mapState, mapActions } from 'vuex';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import ReleaseBlock from './release_block.vue';
export default {
name: 'ReleasesApp',
components: {
GlLoadingIcon,
GlEmptyState,
ReleaseBlock,
},
props: {
projectId: {
type: String,
required: true,
},
documentationLink: {
type: String,
required: true,
},
illustrationPath: {
type: String,
required: true,
},
},
computed: {
...mapState(['isLoading', 'releases', 'hasError']),
shouldRenderEmptyState() {
return !this.releases.length && !this.hasError && !this.isLoading;
},
shouldRenderSuccessState() {
return this.releases.length && !this.isLoading && !this.hasError;
},
},
created() {
this.fetchReleases(this.projectId);
},
methods: {
...mapActions(['fetchReleases']),
},
};
</script>
<template>
<div class="prepend-top-default">
<gl-loading-icon v-if="isLoading" :size="2" class="js-loading prepend-top-20" />
<gl-empty-state
v-else-if="shouldRenderEmptyState"
class="js-empty-state"
:title="__('Getting started with releases')"
:svg-path="illustrationPath"
:description="
__(
'Releases mark specific points in a project\'s development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API.',
)
"
:primary-button-link="documentationLink"
:primary-button-text="__('Open Documentation')"
/>
<div v-else-if="shouldRenderSuccessState" class="js-success-state">
<release-block
v-for="(release, index) in releases"
:key="release.tag_name"
:release="release"
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
/>
</div>
</div>
</template>
<style>
.linked-card::after {
width: 1px;
content: ' ';
border: 1px solid #e5e5e5;
height: 17px;
top: 100%;
position: absolute;
left: 32px;
}
</style>
<script> <script>
import _ from 'underscore';
import { GlTooltipDirective, GlLink } from '@gitlab/ui'; import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
...@@ -17,52 +18,16 @@ export default { ...@@ -17,52 +18,16 @@ export default {
}, },
mixins: [timeagoMixin], mixins: [timeagoMixin],
props: { props: {
name: { release: {
type: String,
required: true,
},
tag: {
type: String,
required: true,
},
commit: {
type: Object,
required: true,
},
description: {
type: String,
required: false,
default: '',
},
author: {
type: Object, type: Object,
required: true, required: true,
}, default: () => ({}),
createdAt: {
type: String,
required: false,
default: '',
},
assetsCount: {
type: Number,
required: false,
default: 0,
},
sources: {
type: Array,
required: false,
default: () => [],
},
links: {
type: Array,
required: false,
default: () => [],
}, },
}, },
computed: { computed: {
releasedTimeAgo() { releasedTimeAgo() {
return sprintf('released %{time}', { return sprintf('released %{time}', {
time: this.timeFormated(this.createdAt), time: this.timeFormated(this.release.created_at),
}); });
}, },
userImageAltDescription() { userImageAltDescription() {
...@@ -70,13 +35,25 @@ export default { ...@@ -70,13 +35,25 @@ export default {
? sprintf("%{username}'s avatar", { username: this.author.username }) ? sprintf("%{username}'s avatar", { username: this.author.username })
: null; : null;
}, },
commit() {
return this.release.commit || {};
},
assets() {
return this.release.assets || {};
},
author() {
return this.release.author || {};
},
hasAuthor() {
return _.isEmpty(this.author);
},
}, },
}; };
</script> </script>
<template> <template>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h2 class="card-title mt-0">{{ name }}</h2> <h2 class="card-title mt-0">{{ release.name }}</h2>
<div class="card-subtitle d-flex flex-wrap text-secondary"> <div class="card-subtitle d-flex flex-wrap text-secondary">
<div class="append-right-8"> <div class="append-right-8">
...@@ -86,15 +63,17 @@ export default { ...@@ -86,15 +63,17 @@ export default {
<div class="append-right-8"> <div class="append-right-8">
<icon name="tag" class="align-middle" /> <icon name="tag" class="align-middle" />
<span v-gl-tooltip.bottom :title="__('Tag')">{{ tag }}</span> <span v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span>
</div> </div>
<div class="append-right-4"> <div class="append-right-4">
&bull; &bull;
<span v-gl-tooltip.bottom :title="tooltipTitle(createdAt)">{{ releasedTimeAgo }}</span> <span v-gl-tooltip.bottom :title="tooltipTitle(release.created_at)">{{
releasedTimeAgo
}}</span>
</div> </div>
<div class="d-flex"> <div v-if="hasAuthor" class="d-flex">
by by
<user-avatar-link <user-avatar-link
class="prepend-left-4" class="prepend-left-4"
...@@ -106,20 +85,25 @@ export default { ...@@ -106,20 +85,25 @@ export default {
</div> </div>
</div> </div>
<div class="card-text prepend-top-default"> <div
v-if="assets.links.length || assets.sources.length"
Sclass="card-text prepend-top-default"
>
<b> <b>
{{ __('Assets') }} <span class="js-assets-count badge badge-pill">{{ assetsCount }}</span> {{ __('Assets') }}
<span class="js-assets-count badge badge-pill">{{ assets.count }}</span>
</b> </b>
<ul class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list"> <ul v-if="assets.links.length" class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list">
<li v-for="link in links" :key="link.name" class="append-bottom-8"> <li v-for="link in assets.links" :key="link.name" class="append-bottom-8">
<gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url"> <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url">
<icon name="package" class="align-middle append-right-4" /> {{ link.name }} <icon name="package" class="align-middle append-right-4 align-text-bottom" />
{{ link.name }}
</gl-link> </gl-link>
</li> </li>
</ul> </ul>
<div class="dropdown"> <div v-if="assets.sources.length" class="dropdown">
<button <button
type="button" type="button"
class="btn btn-link" class="btn btn-link"
...@@ -132,14 +116,14 @@ export default { ...@@ -132,14 +116,14 @@ export default {
</button> </button>
<div class="js-sources-dropdown dropdown-menu"> <div class="js-sources-dropdown dropdown-menu">
<li v-for="asset in sources" :key="asset.url"> <li v-for="asset in assets.sources" :key="asset.url">
<gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link> <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link>
</li> </li>
</div> </div>
</div> </div>
</div> </div>
<div class="card-text prepend-top-default"><div v-html="description"></div></div> <div class="card-text prepend-top-default"><div v-html="release.description_html"></div></div>
</div> </div>
</div> </div>
</template> </template>
import Vue from 'vue';
import App from './components/app.vue';
import createStore from './store';
export default () => {
const element = document.getElementById('js-releases-page');
return new Vue({
el: element,
store: createStore(),
components: {
App,
},
render(createElement) {
return createElement('app', {
props: {
projectId: element.dataset.projectId,
documentationLink: element.dataset.documentationPath,
illustrationPath: element.dataset.illustrationPath,
},
});
},
});
};
import * as types from './mutation_types';
import createFlash from '~/flash';
import { __ } from '~/locale';
import api from '~/api';
/**
* Commits a mutation to update the state while the main endpoint is being requested.
*/
export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES);
/**
* Fetches the main endpoint.
* Will dispatch requestNamespace action before starting the request.
* Will dispatch receiveNamespaceSuccess if the request is successfull
* Will dispatch receiveNamesapceError if the request returns an error
*
* @param {String} projectId
*/
export const fetchReleases = ({ dispatch }, projectId) => {
dispatch('requestReleases');
api
.releases(projectId)
.then(({ data }) => dispatch('receiveReleasesSuccess', data))
.catch(() => dispatch('receiveReleasesError'));
};
export const receiveReleasesSuccess = ({ commit }, data) =>
commit(types.RECEIVE_RELEASES_SUCCESS, data);
export const receiveReleasesError = ({ commit }) => {
commit(types.RECEIVE_RELEASES_ERROR);
createFlash(__('An error occured while fetching the releases. Please try again.'));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import * as actions from './actions';
import mutations from './mutations';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
mutations,
state: state(),
});
export const REQUEST_RELEASES = 'REQUEST_RELEASES';
export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS';
export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR';
import * as types from './mutation_types';
export default {
/**
* Sets isLoading to true while the request is being made.
* @param {Object} state
*/
[types.REQUEST_RELEASES](state) {
state.isLoading = true;
},
/**
* Sets isLoading to false.
* Sets hasError to false.
* Sets the received data
* @param {Object} state
* @param {Object} data
*/
[types.RECEIVE_RELEASES_SUCCESS](state, data) {
state.hasError = false;
state.isLoading = false;
state.releases = data;
},
/**
* Sets isLoading to false.
* Sets hasError to true.
* Resets the data
* @param {Object} state
* @param {Object} data
*/
[types.RECEIVE_RELEASES_ERROR](state) {
state.isLoading = false;
state.releases = [];
state.hasError = true;
},
};
export default () => ({
isLoading: false,
hasError: false,
releases: [],
});
...@@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) { ...@@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url; var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget); $this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post'; ajaxType = $this.data('deletePath') ? 'delete' : 'post';
if ($this.attr('data-delete-path')) {
url = '' + $this.attr('data-delete-path'); if ($this.data('deletePath')) {
url = '' + $this.data('deletePath');
} else { } else {
url = '' + $this.data('url'); url = '' + $this.data('createPath');
} }
$this.tooltip('hide'); $this.tooltip('hide');
...@@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) { ...@@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) {
.removeClass('is-loading') .removeClass('is-loading')
.enable() .enable()
.attr('aria-label', $el.data(`${attrPrefix}Text`)) .attr('aria-label', $el.data(`${attrPrefix}Text`))
.attr('data-delete-path', deletePath) .attr('title', $el.data(`${attrPrefix}Text`))
.attr('title', $el.data(`${attrPrefix}Text`)); .data('deletePath', deletePath);
if ($el.hasClass('has-tooltip')) { if ($el.hasClass('has-tooltip')) {
$el.tooltip('_fixTitle'); $el.tooltip('_fixTitle');
} }
if ($el.data(`${attrPrefix}Icon`)) { if (typeof $el.data('isCollapsed') !== 'undefined') {
$elText.html($el.data(`${attrPrefix}Icon`)); $elText.html($el.data(`${attrPrefix}Icon`));
} else { } else {
$elText.text($el.data(`${attrPrefix}Text`)); $elText.text($el.data(`${attrPrefix}Text`));
......
...@@ -81,7 +81,7 @@ export default { ...@@ -81,7 +81,7 @@ export default {
</p> </p>
<ul> <ul>
<li>Your repository does not have a corresponding <code>serverless.yml</code> file.</li> <li>Your repository does not have a corresponding <code>serverless.yml</code> file.</li>
<li>Your <code>gitlab-ci.yml</code> file is not properly configured.</li> <li>Your <code>.gitlab-ci.yml</code> file is not properly configured.</li>
<li> <li>
The functions listed in the <code>serverless.yml</code> file don't match the namespace The functions listed in the <code>serverless.yml</code> file don't match the namespace
of your cluster. of your cluster.
......
...@@ -71,7 +71,7 @@ export default class SidebarStore { ...@@ -71,7 +71,7 @@ export default class SidebarStore {
} }
findAssignee(findAssignee) { findAssignee(findAssignee) {
return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0]; return this.assignees.find(assignee => assignee.id === findAssignee.id);
} }
removeAssignee(removeAssignee) { removeAssignee(removeAssignee) {
......
<script> <script>
import { GlPopover, GlSkeletonLoading } from '@gitlab/ui'; import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji'; import { glEmojiTag } from '../../../emoji';
...@@ -28,23 +27,6 @@ export default { ...@@ -28,23 +27,6 @@ export default {
}, },
}, },
computed: { computed: {
jobLine() {
if (this.user.bio && this.user.organization) {
return sprintf(
__('%{bio} at %{organization}'),
{
bio: this.user.bio,
organization: this.user.organization,
},
false,
);
} else if (this.user.bio) {
return this.user.bio;
} else if (this.user.organization) {
return this.user.organization;
}
return null;
},
statusHtml() { statusHtml() {
if (this.user.status.emoji && this.user.status.message) { if (this.user.status.emoji && this.user.status.message) {
return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message}`; return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message}`;
...@@ -86,7 +68,8 @@ export default { ...@@ -86,7 +68,8 @@ export default {
<gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" /> <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
</div> </div>
<div class="text-secondary"> <div class="text-secondary">
{{ jobLine }} <div v-if="user.bio" class="js-bio">{{ user.bio }}</div>
<div v-if="user.organization" class="js-organization">{{ user.organization }}</div>
<gl-skeleton-loading <gl-skeleton-loading
v-if="jobInfoIsLoading" v-if="jobInfoIsLoading"
:lines="1" :lines="1"
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
} }
} }
&:not(.use-csslab) table { table {
@extend .table; @extend .table;
} }
......
...@@ -42,7 +42,6 @@ ...@@ -42,7 +42,6 @@
padding: 10px; padding: 10px;
text-align: right; text-align: right;
float: left; float: left;
line-height: 1;
a { a {
font-family: $monospace-font; font-family: $monospace-font;
...@@ -69,3 +68,9 @@ ...@@ -69,3 +68,9 @@
} }
} }
} }
// Vertically aligns <table> line numbers (eg. blame view)
// see https://gitlab.com/gitlab-org/gitlab-ce/issues/54048
td.line-numbers {
line-height: 1;
}
...@@ -25,8 +25,8 @@ ...@@ -25,8 +25,8 @@
&.w-100 { &.w-100 {
// after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here // after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here
// https://github.com/twbs/bootstrap/pull/26976 // https://github.com/twbs/bootstrap/pull/26976
margin-right: -2rem; margin-right: -28px;
padding-right: 2rem; padding-right: 28px;
} }
} }
......
...@@ -198,7 +198,7 @@ $well-light-text-color: #5b6169; ...@@ -198,7 +198,7 @@ $well-light-text-color: #5b6169;
$gl-font-size: 14px; $gl-font-size: 14px;
$gl-font-size-xs: 11px; $gl-font-size-xs: 11px;
$gl-font-size-small: 12px; $gl-font-size-small: 12px;
$gl-font-size-medium: 1.43rem; $gl-font-size-medium: 20px;
$gl-font-size-large: 16px; $gl-font-size-large: 16px;
$gl-font-weight-normal: 400; $gl-font-weight-normal: 400;
$gl-font-weight-bold: 600; $gl-font-weight-bold: 600;
......
...@@ -28,3 +28,9 @@ $popover-border-width: 1px; ...@@ -28,3 +28,9 @@ $popover-border-width: 1px;
$popover-border-color: $border-color; $popover-border-color: $border-color;
$popover-box-shadow: 0 $border-radius-small $border-radius-default 0 $shadow-color; $popover-box-shadow: 0 $border-radius-small $border-radius-default 0 $shadow-color;
$popover-arrow-outer-color: $shadow-color; $popover-arrow-outer-color: $shadow-color;
$h1-font-size: 14px * 2.5;
$h2-font-size: 14px * 2;
$h3-font-size: 14px * 1.75;
$h4-font-size: 14px * 1.5;
$h5-font-size: 14px * 1.25;
$h6-font-size: 14px;
...@@ -135,6 +135,7 @@ ...@@ -135,6 +135,7 @@
.build-loader-animation { .build-loader-animation {
@include build-loader-animation; @include build-loader-animation;
float: left; float: left;
padding-left: $gl-padding-8;
} }
} }
...@@ -232,6 +233,11 @@ ...@@ -232,6 +233,11 @@
@extend .d-flex; @extend .d-flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.trigger-variables-btn {
margin-top: -5px;
margin-bottom: -5px;
}
} }
.trigger-build-variables { .trigger-build-variables {
......
...@@ -403,7 +403,7 @@ class ApplicationController < ActionController::Base ...@@ -403,7 +403,7 @@ class ApplicationController < ActionController::Base
end end
def manifest_import_enabled? def manifest_import_enabled?
Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest') Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
end end
# U2F (universal 2nd factor) devices need a unique identifier for the application # U2F (universal 2nd factor) devices need a unique identifier for the application
......
...@@ -32,14 +32,14 @@ module GroupTree ...@@ -32,14 +32,14 @@ module GroupTree
def filtered_groups_with_ancestors(groups) def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page]) filtered_groups = groups.search(params[:filter]).page(params[:page])
if Group.supports_nested_groups? if Group.supports_nested_objects?
# We find the ancestors by ID of the search results here. # We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied, # Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded. # which would cause them not to be preloaded.
# #
# Pagination needs to be applied before loading the ancestors to # Pagination needs to be applied before loading the ancestors to
# make sure ancestors are not cut off by pagination. # make sure ancestors are not cut off by pagination.
Gitlab::GroupHierarchy.new(Group.where(id: filtered_groups.select(:id))) Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
.base_and_ancestors .base_and_ancestors
else else
filtered_groups filtered_groups
......
...@@ -5,7 +5,6 @@ module IssuableActions ...@@ -5,7 +5,6 @@ module IssuableActions
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
included do included do
before_action :labels, only: [:show, :new, :edit]
before_action :authorize_destroy_issuable!, only: :destroy before_action :authorize_destroy_issuable!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update before_action :authorize_admin_issuable!, only: :bulk_update
end end
...@@ -25,7 +24,10 @@ module IssuableActions ...@@ -25,7 +24,10 @@ module IssuableActions
def show def show
respond_to do |format| respond_to do |format|
format.html format.html do
@issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
format.json do format.json do
render json: serializer.represent(issuable, serializer: params[:serializer]) render json: serializer.represent(issuable, serializer: params[:serializer])
end end
...@@ -168,10 +170,6 @@ module IssuableActions ...@@ -168,10 +170,6 @@ module IssuableActions
end end
end end
def labels
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def authorize_destroy_issuable! def authorize_destroy_issuable!
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
return access_denied! return access_denied!
......
...@@ -94,6 +94,7 @@ module LfsRequest ...@@ -94,6 +94,7 @@ module LfsRequest
def lfs_upload_access? def lfs_upload_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
return false unless has_authentication_ability?(:push_code) return false unless has_authentication_ability?(:push_code)
return false if limit_exceeded?
lfs_deploy_token? || can?(user, :push_code, project) lfs_deploy_token? || can?(user, :push_code, project)
end end
...@@ -121,4 +122,9 @@ module LfsRequest ...@@ -121,4 +122,9 @@ module LfsRequest
def has_authentication_ability?(capability) def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability) (authentication_abilities || []).include?(capability)
end end
# Overriden in EE
def limit_exceeded?
false
end
end end
...@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end end
def create def create
@key = DeployKeys::CreateService.new(current_user, create_params).execute @key = DeployKeys::CreateService.new(current_user, create_params).execute(project: @project)
unless @key.valid? unless @key.valid?
flash[:alert] = @key.errors.full_messages.join(', ').html_safe flash[:alert] = @key.errors.full_messages.join(', ').html_safe
......
...@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap ...@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
def show def show
respond_to do |format| respond_to do |format|
format.html do format.html do
labels @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
end end
format.json do format.json do
...@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap ...@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
end end
end end
private
def authorize_can_resolve_conflicts! def authorize_can_resolve_conflicts!
@conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request) @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
end end
def serializer
MergeRequestSerializer.new(current_user: current_user, project: project)
end
end end
...@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
format.html format.html
format.json do format.json do
render json: { render json: {
html: view_to_html_string("projects/merge_requests/_merge_requests"), html: view_to_html_string("projects/merge_requests/_merge_requests")
labels: @labels.as_json(methods: :text_color)
} }
end end
end end
...@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request @noteable = @merge_request
@commits_count = @merge_request.commits_count @commits_count = @merge_request.commits_count
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
labels
set_pipeline_variables set_pipeline_variables
......
...@@ -112,7 +112,7 @@ class GroupDescendantsFinder ...@@ -112,7 +112,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def ancestors_of_groups(base_for_ancestors) def ancestors_of_groups(base_for_ancestors)
group_ids = base_for_ancestors.except(:select, :sort).select(:id) group_ids = base_for_ancestors.except(:select, :sort).select(:id)
Gitlab::GroupHierarchy.new(Group.where(id: group_ids)) Gitlab::ObjectHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors(upto: parent_group.id) .base_and_ancestors(upto: parent_group.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -132,7 +132,7 @@ class GroupDescendantsFinder ...@@ -132,7 +132,7 @@ class GroupDescendantsFinder
end end
def subgroups def subgroups
return Group.none unless Group.supports_nested_groups? return Group.none unless Group.supports_nested_objects?
# When filtering subgroups, we want to find all matches withing the tree of # When filtering subgroups, we want to find all matches withing the tree of
# descendants to show to the user # descendants to show to the user
...@@ -183,7 +183,7 @@ class GroupDescendantsFinder ...@@ -183,7 +183,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def hierarchy_for_parent def hierarchy_for_parent
@hierarchy ||= Gitlab::GroupHierarchy.new(Group.where(id: parent_group.id)) @hierarchy ||= Gitlab::ObjectHierarchy.new(Group.where(id: parent_group.id))
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
...@@ -46,7 +46,7 @@ class GroupsFinder < UnionFinder ...@@ -46,7 +46,7 @@ class GroupsFinder < UnionFinder
return [Group.all] if current_user&.full_private_access? && all_available? return [Group.all] if current_user&.full_private_access? && all_available?
groups = [] groups = []
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user groups << Gitlab::ObjectHierarchy.new(groups_for_ancestors, groups_for_descendants).all_objects if current_user
groups << Group.unscoped.public_to_user(current_user) if include_public_groups? groups << Group.unscoped.public_to_user(current_user) if include_public_groups?
groups << Group.none if groups.empty? groups << Group.none if groups.empty?
groups groups
...@@ -66,7 +66,7 @@ class GroupsFinder < UnionFinder ...@@ -66,7 +66,7 @@ class GroupsFinder < UnionFinder
.groups .groups
.where('members.access_level >= ?', params[:min_access_level]) .where('members.access_level >= ?', params[:min_access_level])
Gitlab::GroupHierarchy Gitlab::ObjectHierarchy
.new(groups) .new(groups)
.base_and_descendants .base_and_descendants
end end
......
...@@ -26,6 +26,18 @@ module ApplicationSettingsHelper ...@@ -26,6 +26,18 @@ module ApplicationSettingsHelper
end end
end end
def all_protocols_enabled?
Gitlab::CurrentSettings.enabled_git_access_protocol.blank?
end
def ssh_enabled?
all_protocols_enabled? || enabled_protocol == 'ssh'
end
def http_enabled?
all_protocols_enabled? || enabled_protocol == 'http'
end
def enabled_project_button(project, protocol) def enabled_project_button(project, protocol)
case protocol case protocol
when 'ssh' when 'ssh'
...@@ -218,7 +230,8 @@ module ApplicationSettingsHelper ...@@ -218,7 +230,8 @@ module ApplicationSettingsHelper
:version_check_enabled, :version_check_enabled,
:web_ide_clientside_preview_enabled, :web_ide_clientside_preview_enabled,
:diff_max_patch_bytes, :diff_max_patch_bytes,
:commit_email_hostname :commit_email_hostname,
:protected_ci_variables
] ]
end end
......
# frozen_string_literal: true
module CiVariablesHelper
def ci_variable_protected_by_default?
Gitlab::CurrentSettings.current_application_settings.protected_ci_variables
end
def ci_variable_protected?(variable, only_key_value)
if variable && !only_key_value
variable.protected
else
ci_variable_protected_by_default?
end
end
end
...@@ -126,7 +126,7 @@ module GroupsHelper ...@@ -126,7 +126,7 @@ module GroupsHelper
end end
def supports_nested_groups? def supports_nested_groups?
Group.supports_nested_groups? Group.supports_nested_objects?
end end
private private
......
...@@ -23,30 +23,41 @@ module IssuablesHelper ...@@ -23,30 +23,41 @@ module IssuablesHelper
end end
end end
def sidebar_due_date_tooltip_label(issuable) def sidebar_milestone_tooltip_label(milestone)
if issuable.due_date return _('Milestone') unless milestone.present?
"#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
else [milestone[:title], sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>')
_('Due date') end
end
def sidebar_milestone_remaining_days(milestone)
due_date_with_remaining_days(milestone[:due_date], milestone[:start_date])
end
def sidebar_due_date_tooltip_label(due_date)
[_('Due date'), due_date_with_remaining_days(due_date)].compact.join('<br/>')
end end
def due_date_remaining_days(issuable) def due_date_with_remaining_days(due_date, start_date = nil)
remaining_days_in_words = remaining_days_in_words(issuable) return unless due_date
"#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})" "#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
end
def sidebar_label_filter_path(base_path, label_name)
query_params = { label_name: [label_name] }.to_query
"#{base_path}?#{query_params}"
end end
def multi_label_name(current_labels, default_label) def multi_label_name(current_labels, default_label)
if current_labels && current_labels.any? return default_label if current_labels.blank?
title = current_labels.first.try(:title)
if current_labels.size > 1 title = current_labels.first.try(:title) || current_labels.first[:title]
"#{title} +#{current_labels.size - 1} more"
else if current_labels.size > 1
title "#{title} +#{current_labels.size - 1} more"
end
else else
default_label title
end end
end end
...@@ -197,19 +208,11 @@ module IssuablesHelper ...@@ -197,19 +208,11 @@ module IssuablesHelper
output.join.html_safe output.join.html_safe
end end
# rubocop: disable CodeReuse/ActiveRecord
def issuable_todo(issuable)
if current_user
current_user.todos.find_by(target: issuable, state: :pending)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def issuable_labels_tooltip(labels, limit: 5) def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index { |_, i| i < limit } first, last = labels.partition.with_index { |_, i| i < limit }
if labels && labels.any? if labels && labels.any?
label_names = first.collect(&:name) label_names = first.collect { |label| label.fetch(:title) }
label_names << "and #{last.size} more" unless last.empty? label_names << "and #{last.size} more" unless last.empty?
label_names.join(', ') label_names.join(', ')
...@@ -356,12 +359,6 @@ module IssuablesHelper ...@@ -356,12 +359,6 @@ module IssuablesHelper
issuable.model_name.human.downcase issuable.model_name.human.downcase
end end
def selected_labels
Array(params[:label_name]).map do |label_name|
Label.new(title: label_name)
end
end
def has_filter_bar_param? def has_filter_bar_param?
finder.class.scalar_params.any? { |p| params[p].present? } finder.class.scalar_params.any? { |p| params[p].present? }
end end
...@@ -386,19 +383,20 @@ module IssuablesHelper ...@@ -386,19 +383,20 @@ module IssuablesHelper
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end end
def issuable_todo_button_data(issuable, todo, is_collapsed) def issuable_todo_button_data(issuable, is_collapsed)
{ {
todo_text: "Add todo", todo_text: _('Add todo'),
mark_text: "Mark todo as done", mark_text: _('Mark todo as done'),
todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil), todo_icon: sprite_icon('todo-add'),
mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil), mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'),
issuable_id: issuable.id, issuable_id: issuable[:id],
issuable_type: issuable.class.name.underscore, issuable_type: issuable[:type],
url: project_todos_path(@project), create_path: issuable[:create_todo_path],
delete_path: (dashboard_todo_path(todo) if todo), delete_path: issuable.dig(:current_user, :todo, :delete_path),
placement: (is_collapsed ? 'left' : nil), placement: is_collapsed ? 'left' : nil,
container: (is_collapsed ? 'body' : nil), container: is_collapsed ? 'body' : nil,
boundary: 'viewport' boundary: 'viewport',
is_collapsed: is_collapsed
} }
end end
...@@ -418,27 +416,20 @@ module IssuablesHelper ...@@ -418,27 +416,20 @@ module IssuablesHelper
end end
end end
def issuable_sidebar_options(issuable, can_edit_issuable) def issuable_sidebar_options(issuable)
{ {
endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar", endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras",
toggleSubscriptionEndpoint: toggle_subscription_path(issuable), toggleSubscriptionEndpoint: issuable[:toggle_subscription_path],
moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable), moveIssueEndpoint: issuable[:move_issue_path],
projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id), projectsAutocompleteEndpoint: issuable[:projects_autocomplete_path],
editable: can_edit_issuable, editable: issuable.dig(:current_user, :can_edit),
currentUser: UserSerializer.new.represent(current_user), currentUser: issuable[:current_user],
rootPath: root_path, rootPath: root_path,
fullPath: @project.full_path fullPath: issuable[:project_full_path]
} }
end end
def parent def parent
@project || @group @project || @group
end end
def issuable_milestone_tooltip_title(issuable)
if issuable.milestone
milestone_tooltip = milestone_tooltip_title(issuable.milestone)
_('Milestone') + (milestone_tooltip ? ': ' + milestone_tooltip : '')
end
end
end end
...@@ -114,12 +114,6 @@ module MilestonesHelper ...@@ -114,12 +114,6 @@ module MilestonesHelper
end end
end end
def milestone_tooltip_title(milestone)
if milestone
"#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}"
end
end
def milestone_time_for(date, date_type) def milestone_time_for(date, date_type)
title = date_type == :start ? "Start date" : "End date" title = date_type == :start ? "Start date" : "End date"
...@@ -173,7 +167,7 @@ module MilestonesHelper ...@@ -173,7 +167,7 @@ module MilestonesHelper
def milestone_tooltip_due_date(milestone) def milestone_tooltip_due_date(milestone)
if milestone.due_date if milestone.due_date
"#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})" "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})"
else else
_('Milestone') _('Milestone')
end end
......
...@@ -271,6 +271,20 @@ module ProjectsHelper ...@@ -271,6 +271,20 @@ module ProjectsHelper
params[:legacy_render] ? { markdown_engine: :redcarpet } : {} params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
end end
def explore_projects_tab?
current_page?(explore_projects_path) ||
current_page?(trending_explore_projects_path) ||
current_page?(starred_explore_projects_path)
end
def show_merge_request_count?(disabled: false, compact_mode: false)
!disabled && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true)
end
def show_issue_count?(disabled: false, compact_mode: false)
!disabled && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true)
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
...@@ -515,20 +529,6 @@ module ProjectsHelper ...@@ -515,20 +529,6 @@ module ProjectsHelper
end end
end end
def explore_projects_tab?
current_page?(explore_projects_path) ||
current_page?(trending_explore_projects_path) ||
current_page?(starred_explore_projects_path)
end
def show_merge_request_count?(merge_requests, compact_mode)
merge_requests && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true)
end
def show_issue_count?(issues, compact_mode)
issues && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true)
end
def sidebar_projects_paths def sidebar_projects_paths
%w[ %w[
projects#show projects#show
......
...@@ -56,7 +56,9 @@ module Emails ...@@ -56,7 +56,9 @@ module Emails
@milestone = milestone @milestone = milestone
@milestone_url = milestone_url(@milestone) @milestone_url = milestone_url(@milestone)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason).merge({
template_name: 'changed_milestone_email'
}))
end end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil)
......
...@@ -51,7 +51,9 @@ module Emails ...@@ -51,7 +51,9 @@ module Emails
@milestone = milestone @milestone = milestone
@milestone_url = milestone_url(@milestone) @milestone_url = milestone_url(@milestone)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason).merge({
template_name: 'changed_milestone_email'
}))
end end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
......
...@@ -16,6 +16,7 @@ class Notify < BaseMailer ...@@ -16,6 +16,7 @@ class Notify < BaseMailer
include Emails::AutoDevops include Emails::AutoDevops
include Emails::RemoteMirrors include Emails::RemoteMirrors
helper MilestonesHelper
helper MergeRequestsHelper helper MergeRequestsHelper
helper DiffHelper helper DiffHelper
helper BlobHelper helper BlobHelper
......
...@@ -302,7 +302,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -302,7 +302,8 @@ class ApplicationSetting < ActiveRecord::Base
user_show_add_ssh_key_message: true, user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil, usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname commit_email_hostname: default_commit_email_hostname,
protected_ci_variables: false
} }
end end
......
...@@ -22,49 +22,30 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -22,49 +22,30 @@ class BroadcastMessage < ActiveRecord::Base
after_commit :flush_redis_cache after_commit :flush_redis_cache
def self.current def self.current
raw_messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) do messages = cache.fetch(CACHE_KEY, as: BroadcastMessage, expires_in: cache_expires_in) do
remove_legacy_cache_key remove_legacy_cache_key
current_and_future_messages.to_json current_and_future_messages
end end
messages = decode_messages(raw_messages)
return [] unless messages&.present? return [] unless messages&.present?
now_or_future = messages.select(&:now_or_future?) now_or_future = messages.select(&:now_or_future?)
# If there are cached entries but none are to be displayed we'll purge the # If there are cached entries but none are to be displayed we'll purge the
# cache so we don't keep running this code all the time. # cache so we don't keep running this code all the time.
Rails.cache.delete(CACHE_KEY) if now_or_future.empty? cache.expire(CACHE_KEY) if now_or_future.empty?
now_or_future.select(&:now?) now_or_future.select(&:now?)
end end
def self.decode_messages(raw_messages)
return unless raw_messages&.present?
message_list = ActiveSupport::JSON.decode(raw_messages)
return unless message_list.is_a?(Array)
valid_attr = BroadcastMessage.attribute_names
message_list.map do |raw|
BroadcastMessage.new(raw) if valid_cache_entry?(raw, valid_attr)
end.compact
rescue ActiveSupport::JSON.parse_error
end
def self.valid_cache_entry?(raw, valid_attr)
return false unless raw.is_a?(Hash)
(raw.keys - valid_attr).empty?
end
def self.current_and_future_messages def self.current_and_future_messages
where('ends_at > :now', now: Time.zone.now).order_id_asc where('ends_at > :now', now: Time.zone.now).order_id_asc
end end
def self.cache
Gitlab::JsonCache.new(cache_key_with_version: false)
end
def self.cache_expires_in def self.cache_expires_in
nil nil
end end
...@@ -74,7 +55,7 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -74,7 +55,7 @@ class BroadcastMessage < ActiveRecord::Base
# environment a one-shot migration would not work because the cache # environment a one-shot migration would not work because the cache
# would be repopulated by a node that has not been upgraded. # would be repopulated by a node that has not been upgraded.
def self.remove_legacy_cache_key def self.remove_legacy_cache_key
Rails.cache.delete(LEGACY_CACHE_KEY) cache.expire(LEGACY_CACHE_KEY)
end end
def active? def active?
...@@ -102,7 +83,7 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -102,7 +83,7 @@ class BroadcastMessage < ActiveRecord::Base
end end
def flush_redis_cache def flush_redis_cache
Rails.cache.delete(CACHE_KEY) self.class.cache.expire(CACHE_KEY)
self.class.remove_legacy_cache_key self.class.remove_legacy_cache_key
end end
end end
...@@ -66,7 +66,7 @@ module Ci ...@@ -66,7 +66,7 @@ module Ci
scope :belonging_to_parent_group_of_project, -> (project_id) { scope :belonging_to_parent_group_of_project, -> (project_id) {
project_groups = ::Group.joins(:projects).where(projects: { id: project_id }) project_groups = ::Group.joins(:projects).where(projects: { id: project_id })
hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups).base_and_ancestors hierarchy_groups = Gitlab::ObjectHierarchy.new(project_groups).base_and_ancestors
joins(:groups).where(namespaces: { id: hierarchy_groups }) joins(:groups).where(namespaces: { id: hierarchy_groups })
} }
......
...@@ -228,7 +228,7 @@ module Clusters ...@@ -228,7 +228,7 @@ module Clusters
return unless namespace_changed? return unless namespace_changed?
run_after_commit do run_after_commit do
ClusterPlatformConfigureWorker.perform_async(cluster_id) ClusterConfigureWorker.perform_async(cluster_id)
end end
end end
end end
......
...@@ -43,7 +43,18 @@ module Avatarable ...@@ -43,7 +43,18 @@ module Avatarable
end end
def avatar_path(only_path: true, size: nil) def avatar_path(only_path: true, size: nil)
return unless self[:avatar].present? unless self.try(:id)
return uncached_avatar_path(only_path: only_path, size: size)
end
# Cache this avatar path only within the request because avatars in
# object storage may be generated with time-limited, signed URLs.
key = "#{self.class.name}:#{self.id}:#{only_path}:#{size}"
Gitlab::SafeRequestStore[key] ||= uncached_avatar_path(only_path: only_path, size: size)
end
def uncached_avatar_path(only_path: true, size: nil)
return unless self.try(:avatar).present?
asset_host = ActionController::Base.asset_host asset_host = ActionController::Base.asset_host
use_asset_host = asset_host.present? use_asset_host = asset_host.present?
......
# frozen_string_literal: true
module Descendant
extend ActiveSupport::Concern
class_methods do
def supports_nested_objects?
Gitlab::Database.postgresql?
end
end
end
...@@ -39,6 +39,7 @@ module DiscussionOnDiff ...@@ -39,6 +39,7 @@ module DiscussionOnDiff
# Returns an array of at most 16 highlighted lines above a diff note # Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true, diff_limit: nil) def truncated_diff_lines(highlight: true, diff_limit: nil)
return [] unless on_text?
return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote) return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote)
diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min
......
...@@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone ...@@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone
attr_reader :group_name attr_reader :group_name
override :initialize
def initialize(milestone) def initialize(milestone)
super(milestone.title, Array(milestone)) super(milestone.title, Array(milestone))
......
# frozen_string_literal: true # frozen_string_literal: true
class Environment < ActiveRecord::Base class Environment < ActiveRecord::Base
include Gitlab::Utils::StrongMemoize
# Used to generate random suffixes for the slug # Used to generate random suffixes for the slug
LETTERS = 'a'..'z' LETTERS = 'a'..'z'
NUMBERS = '0'..'9' NUMBERS = '0'..'9'
...@@ -231,7 +232,9 @@ class Environment < ActiveRecord::Base ...@@ -231,7 +232,9 @@ class Environment < ActiveRecord::Base
end end
def deployment_platform def deployment_platform
project.deployment_platform(environment: self.name) strong_memoize(:deployment_platform) do
project.deployment_platform(environment: self.name)
end
end end
private private
......
...@@ -10,6 +10,7 @@ class Group < Namespace ...@@ -10,6 +10,7 @@ class Group < Namespace
include Referable include Referable
include SelectForProjectAuthorization include SelectForProjectAuthorization
include LoadedInGroupList include LoadedInGroupList
include Descendant
include GroupDescendant include GroupDescendant
include TokenAuthenticatable include TokenAuthenticatable
include WithUploads include WithUploads
...@@ -63,10 +64,6 @@ class Group < Namespace ...@@ -63,10 +64,6 @@ class Group < Namespace
after_update :path_changed_hook, if: :path_changed? after_update :path_changed_hook, if: :path_changed?
class << self class << self
def supports_nested_groups?
Gitlab::Database.postgresql?
end
def sort_by_attribute(method) def sort_by_attribute(method)
if method == 'storage_size_desc' if method == 'storage_size_desc'
# storage_size is a virtual column so we need to # storage_size is a virtual column so we need to
......
...@@ -175,16 +175,16 @@ class Namespace < ActiveRecord::Base ...@@ -175,16 +175,16 @@ class Namespace < ActiveRecord::Base
# Returns all ancestors, self, and descendants of the current namespace. # Returns all ancestors, self, and descendants of the current namespace.
def self_and_hierarchy def self_and_hierarchy
Gitlab::GroupHierarchy Gitlab::ObjectHierarchy
.new(self.class.where(id: id)) .new(self.class.where(id: id))
.all_groups .all_objects
end end
# Returns all the ancestors of the current namespaces. # Returns all the ancestors of the current namespaces.
def ancestors def ancestors
return self.class.none unless parent_id return self.class.none unless parent_id
Gitlab::GroupHierarchy Gitlab::ObjectHierarchy
.new(self.class.where(id: parent_id)) .new(self.class.where(id: parent_id))
.base_and_ancestors .base_and_ancestors
end end
...@@ -192,27 +192,27 @@ class Namespace < ActiveRecord::Base ...@@ -192,27 +192,27 @@ class Namespace < ActiveRecord::Base
# returns all ancestors upto but excluding the given namespace # returns all ancestors upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned # when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil) def ancestors_upto(top = nil, hierarchy_order: nil)
Gitlab::GroupHierarchy.new(self.class.where(id: id)) Gitlab::ObjectHierarchy.new(self.class.where(id: id))
.ancestors(upto: top, hierarchy_order: hierarchy_order) .ancestors(upto: top, hierarchy_order: hierarchy_order)
end end
def self_and_ancestors def self_and_ancestors
return self.class.where(id: id) unless parent_id return self.class.where(id: id) unless parent_id
Gitlab::GroupHierarchy Gitlab::ObjectHierarchy
.new(self.class.where(id: id)) .new(self.class.where(id: id))
.base_and_ancestors .base_and_ancestors
end end
# Returns all the descendants of the current namespace. # Returns all the descendants of the current namespace.
def descendants def descendants
Gitlab::GroupHierarchy Gitlab::ObjectHierarchy
.new(self.class.where(parent_id: id)) .new(self.class.where(parent_id: id))
.base_and_descendants .base_and_descendants
end end
def self_and_descendants def self_and_descendants
Gitlab::GroupHierarchy Gitlab::ObjectHierarchy
.new(self.class.where(id: id)) .new(self.class.where(id: id))
.base_and_descendants .base_and_descendants
end end
...@@ -293,7 +293,7 @@ class Namespace < ActiveRecord::Base ...@@ -293,7 +293,7 @@ class Namespace < ActiveRecord::Base
end end
def force_share_with_group_lock_on_descendants def force_share_with_group_lock_on_descendants
return unless Group.supports_nested_groups? return unless Group.supports_nested_objects?
# We can't use `descendants.update_all` since Rails will throw away the WITH # We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use # RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
...@@ -306,6 +306,7 @@ class Namespace < ActiveRecord::Base ...@@ -306,6 +306,7 @@ class Namespace < ActiveRecord::Base
def write_projects_repository_config def write_projects_repository_config
all_projects.find_each do |project| all_projects.find_each do |project|
project.write_repository_config project.write_repository_config
project.track_project_repository
end end
end end
end end
...@@ -18,6 +18,7 @@ class PoolRepository < ActiveRecord::Base ...@@ -18,6 +18,7 @@ class PoolRepository < ActiveRecord::Base
state :scheduled state :scheduled
state :ready state :ready
state :failed state :failed
state :obsolete
event :schedule do event :schedule do
transition none: :scheduled transition none: :scheduled
...@@ -31,6 +32,10 @@ class PoolRepository < ActiveRecord::Base ...@@ -31,6 +32,10 @@ class PoolRepository < ActiveRecord::Base
transition all => :failed transition all => :failed
end end
event :mark_obsolete do
transition all => :obsolete
end
state all - [:ready] do state all - [:ready] do
def joinable? def joinable?
false false
...@@ -54,6 +59,12 @@ class PoolRepository < ActiveRecord::Base ...@@ -54,6 +59,12 @@ class PoolRepository < ActiveRecord::Base
::ObjectPool::ScheduleJoinWorker.perform_async(pool.id) ::ObjectPool::ScheduleJoinWorker.perform_async(pool.id)
end end
end end
after_transition any => :obsolete do |pool, _|
pool.run_after_commit do
::ObjectPool::DestroyWorker.perform_async(pool.id)
end
end
end end
def create_object_pool def create_object_pool
...@@ -71,10 +82,10 @@ class PoolRepository < ActiveRecord::Base ...@@ -71,10 +82,10 @@ class PoolRepository < ActiveRecord::Base
end end
# This RPC can cause data loss, as not all objects are present the local repository # This RPC can cause data loss, as not all objects are present the local repository
# No execution path yet, will be added through: def unlink_repository(repository)
# https://gitlab.com/gitlab-org/gitaly/issues/1415
def delete_repository_alternate(repository)
object_pool.unlink_repository(repository.raw) object_pool.unlink_repository(repository.raw)
mark_obsolete unless member_projects.where.not(id: repository.project.id).exists?
end end
def object_pool def object_pool
......
...@@ -256,7 +256,7 @@ class Project < ActiveRecord::Base ...@@ -256,7 +256,7 @@ class Project < ActiveRecord::Base
# other pipelines, like webide ones, that we won't retrieve # other pipelines, like webide ones, that we won't retrieve
# if we use this relation. # if we use this relation.
has_many :ci_pipelines, has_many :ci_pipelines,
-> { Feature.enabled?(:pipeline_ci_sources_only, default_enabled: true) ? ci_sources : all }, -> { ci_sources },
class_name: 'Ci::Pipeline', class_name: 'Ci::Pipeline',
inverse_of: :project inverse_of: :project
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
...@@ -570,7 +570,7 @@ class Project < ActiveRecord::Base ...@@ -570,7 +570,7 @@ class Project < ActiveRecord::Base
# returns all ancestor-groups upto but excluding the given namespace # returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned # when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil) def ancestors_upto(top = nil, hierarchy_order: nil)
Gitlab::GroupHierarchy.new(Group.where(id: namespace_id)) Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id))
.base_and_ancestors(upto: top, hierarchy_order: hierarchy_order) .base_and_ancestors(upto: top, hierarchy_order: hierarchy_order)
end end
...@@ -1244,10 +1244,8 @@ class Project < ActiveRecord::Base ...@@ -1244,10 +1244,8 @@ class Project < ActiveRecord::Base
end end
def track_project_repository def track_project_repository
return unless hashed_storage?(:repository) repository = project_repository || build_project_repository
repository.update!(shard_name: repository_storage, disk_path: disk_path)
project_repo = project_repository || build_project_repository
project_repo.update!(shard_name: repository_storage, disk_path: disk_path)
end end
def create_repository(force: false) def create_repository(force: false)
...@@ -2004,6 +2002,10 @@ class Project < ActiveRecord::Base ...@@ -2004,6 +2002,10 @@ class Project < ActiveRecord::Base
Feature.enabled?(:object_pools, self) Feature.enabled?(:object_pools, self)
end end
def leave_pool_repository
pool_repository&.unlink_repository(repository)
end
private private
def create_new_pool_repository def create_new_pool_repository
......
...@@ -709,13 +709,13 @@ class User < ActiveRecord::Base ...@@ -709,13 +709,13 @@ class User < ActiveRecord::Base
# Returns the groups a user is a member of, either directly or through a parent group # Returns the groups a user is a member of, either directly or through a parent group
def membership_groups def membership_groups
Gitlab::GroupHierarchy.new(groups).base_and_descendants Gitlab::ObjectHierarchy.new(groups).base_and_descendants
end end
# Returns a relation of groups the user has access to, including their parent # Returns a relation of groups the user has access to, including their parent
# and child groups (recursively). # and child groups (recursively).
def all_expanded_groups def all_expanded_groups
Gitlab::GroupHierarchy.new(groups).all_groups Gitlab::ObjectHierarchy.new(groups).all_objects
end end
def expanded_groups_requiring_two_factor_authentication def expanded_groups_requiring_two_factor_authentication
...@@ -1153,7 +1153,7 @@ class User < ActiveRecord::Base ...@@ -1153,7 +1153,7 @@ class User < ActiveRecord::Base
end end
def manageable_groups def manageable_groups
Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end end
def namespaces def namespaces
...@@ -1422,6 +1422,10 @@ class User < ActiveRecord::Base ...@@ -1422,6 +1422,10 @@ class User < ActiveRecord::Base
todos.where(id: ids) todos.where(id: ids)
end end
def pending_todo_for(target)
todos.find_by(target: target, state: :pending)
end
# @deprecated # @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
......
...@@ -16,7 +16,7 @@ class GroupPolicy < BasePolicy ...@@ -16,7 +16,7 @@ class GroupPolicy < BasePolicy
condition(:maintainer) { access_level >= GroupMember::MAINTAINER } condition(:maintainer) { access_level >= GroupMember::MAINTAINER }
condition(:reporter) { access_level >= GroupMember::REPORTER } condition(:reporter) { access_level >= GroupMember::REPORTER }
condition(:nested_groups_supported, scope: :global) { Group.supports_nested_groups? } condition(:nested_groups_supported, scope: :global) { Group.supports_nested_objects? }
condition(:has_parent, scope: :subject) { @subject.has_parent? } condition(:has_parent, scope: :subject) { @subject.has_parent? }
condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? } condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
......
...@@ -44,14 +44,14 @@ module EntityDateHelper ...@@ -44,14 +44,14 @@ module EntityDateHelper
# It returns "Upcoming" for upcoming entities # It returns "Upcoming" for upcoming entities
# If due date is provided, it returns "# days|weeks|months remaining|ago" # If due date is provided, it returns "# days|weeks|months remaining|ago"
# If start date is provided and elapsed, with no due date, it returns "# days elapsed" # If start date is provided and elapsed, with no due date, it returns "# days elapsed"
def remaining_days_in_words(entity) def remaining_days_in_words(due_date, start_date = nil)
if entity.try(:expired?) if due_date&.past?
content_tag(:strong, 'Past due') content_tag(:strong, 'Past due')
elsif entity.try(:upcoming?) elsif start_date&.future?
content_tag(:strong, 'Upcoming') content_tag(:strong, 'Upcoming')
elsif entity.due_date elsif due_date
is_upcoming = (entity.due_date - Date.today).to_i > 0 is_upcoming = (due_date - Date.today).to_i > 0
time_ago = time_ago_in_words(entity.due_date) time_ago = time_ago_in_words(due_date)
# https://gitlab.com/gitlab-org/gitlab-ce/issues/49440 # https://gitlab.com/gitlab-org/gitlab-ce/issues/49440
# #
...@@ -63,8 +63,8 @@ module EntityDateHelper ...@@ -63,8 +63,8 @@ module EntityDateHelper
remaining_or_ago = is_upcoming ? _("remaining") : _("ago") remaining_or_ago = is_upcoming ? _("remaining") : _("ago")
"#{content} #{remaining_or_ago}".html_safe "#{content} #{remaining_or_ago}".html_safe
elsif entity.start_date && entity.start_date.past? elsif start_date&.past?
days = entity.elapsed_days days = (Date.today - start_date).to_i
"#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe "#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe
end end
end end
......
...@@ -23,6 +23,10 @@ class EnvironmentEntity < Grape::Entity ...@@ -23,6 +23,10 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment) stop_project_environment_path(environment.project, environment)
end end
expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment|
cluster.cluster_type
end
expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment| expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment|
terminal_project_environment_path(environment.project, environment) terminal_project_environment_path(environment.project, environment)
end end
...@@ -48,4 +52,16 @@ class EnvironmentEntity < Grape::Entity ...@@ -48,4 +52,16 @@ class EnvironmentEntity < Grape::Entity
def can_access_terminal? def can_access_terminal?
can?(request.current_user, :create_environment_terminal, environment) can?(request.current_user, :create_environment_terminal, environment)
end end
def cluster_platform_kubernetes?
deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes)
end
def deployment_platform
environment.deployment_platform
end
def cluster
deployment_platform.cluster
end
end end
# frozen_string_literal: true
class IssuableSidebarBasicEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :type do |issuable|
issuable.to_ability_name
end
expose :author_id
expose :project_id do |issuable|
issuable.project.id
end
expose :discussion_locked
expose :reference do |issuable|
issuable.to_reference(issuable.project, full: true)
end
expose :milestone, using: ::API::Entities::Milestone
expose :labels, using: LabelEntity
expose :current_user, if: lambda { |_issuable| current_user } do
expose :current_user, merge: true, using: API::Entities::UserBasic
expose :todo, using: IssuableSidebarTodoEntity do |issuable|
current_user.pending_todo_for(issuable)
end
expose :can_edit do |issuable|
can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
end
expose :can_move do |issuable|
issuable.can_move?(current_user)
end
expose :can_admin_label do |issuable|
can?(current_user, :admin_label, issuable.project)
end
end
expose :issuable_json_path do |issuable|
if issuable.is_a?(MergeRequest)
project_merge_request_path(issuable.project, issuable.iid, :json)
else
project_issue_path(issuable.project, issuable.iid, :json)
end
end
expose :namespace_path do |issuable|
issuable.project.namespace.full_path
end
expose :project_path do |issuable|
issuable.project.path
end
expose :project_full_path do |issuable|
issuable.project.full_path
end
expose :project_issuables_path do |issuable|
project = issuable.project
namespace = project.namespace
if issuable.is_a?(MergeRequest)
namespace_project_merge_requests_path(namespace, project)
else
namespace_project_issues_path(namespace, project)
end
end
expose :create_todo_path do |issuable|
project_todos_path(issuable.project)
end
expose :project_milestones_path do |issuable|
project_milestones_path(issuable.project, :json)
end
expose :project_labels_path do |issuable|
project_labels_path(issuable.project, :json, include_ancestor_groups: true)
end
expose :toggle_subscription_path do |issuable|
toggle_subscription_path(issuable)
end
expose :move_issue_path do |issuable|
move_namespace_project_issue_path(
namespace_id: issuable.project.namespace.to_param,
project_id: issuable.project,
id: issuable
)
end
expose :projects_autocomplete_path do |issuable|
autocomplete_projects_path(project_id: issuable.project.id)
end
private
def current_user
request.current_user
end
end
# frozen_string_literal: true # frozen_string_literal: true
class IssuableSidebarEntity < Grape::Entity class IssuableSidebarExtrasEntity < Grape::Entity
include TimeTrackableEntity
include RequestAwareEntity include RequestAwareEntity
include TimeTrackableEntity
expose :participants, using: ::API::Entities::UserBasic do |issuable| expose :participants, using: ::API::Entities::UserBasic do |issuable|
issuable.participants(request.current_user) issuable.participants(request.current_user)
......
# frozen_string_literal: true
class IssuableSidebarTodoEntity < Grape::Entity
include Gitlab::Routing
expose :id
expose :delete_path do |todo|
dashboard_todo_path(todo) if todo
end
end
...@@ -37,7 +37,7 @@ class IssueBoardEntity < Grape::Entity ...@@ -37,7 +37,7 @@ class IssueBoardEntity < Grape::Entity
end end
expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue| expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue|
project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar') project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar_extras')
end end
expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue| expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue|
......
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
class IssueSerializer < BaseSerializer 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 `serializer` 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(issue, opts = {}) def represent(issue, opts = {})
entity = entity =
case opts[:serializer] case opts[:serializer]
when 'sidebar' when 'sidebar'
IssueSidebarEntity IssueSidebarBasicEntity
when 'sidebar_extras'
IssueSidebarExtrasEntity
when 'board' when 'board'
IssueBoardEntity IssueBoardEntity
else else
......
# frozen_string_literal: true
class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
expose :due_date
expose :confidential
end
# frozen_string_literal: true # frozen_string_literal: true
class IssueSidebarEntity < IssuableSidebarEntity class IssueSidebarExtrasEntity < IssuableSidebarExtrasEntity
expose :assignees, using: API::Entities::UserBasic expose :assignees, using: API::Entities::UserBasic
end end
# frozen_string_literal: true # frozen_string_literal: true
class MergeRequestBasicEntity < IssuableSidebarEntity class MergeRequestBasicEntity < Grape::Entity
expose :assignee_id expose :assignee_id
expose :merge_status expose :merge_status
expose :merge_error expose :merge_error
......
# frozen_string_literal: true
class MergeRequestBasicSerializer < BaseSerializer
entity MergeRequestBasicEntity
end
...@@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer ...@@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer
def represent(merge_request, opts = {}) def represent(merge_request, opts = {})
entity = entity =
case opts[:serializer] case opts[:serializer]
when 'basic', 'sidebar' when 'sidebar'
MergeRequestSidebarBasicEntity
when 'sidebar_extras'
IssuableSidebarExtrasEntity
when 'basic'
MergeRequestBasicEntity MergeRequestBasicEntity
else # It's 'widget' else
# fallback to widget for old poll requests without `serializer` set
MergeRequestWidgetEntity MergeRequestWidgetEntity
end end
......
# frozen_string_literal: true
class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
expose :assignee, if: lambda { |issuable| issuable.assignee } do
expose :assignee, merge: true, using: API::Entities::UserBasic
expose :can_merge do |issuable|
issuable.can_be_merged_by?(issuable.assignee)
end
end
end
...@@ -118,7 +118,7 @@ module Ci ...@@ -118,7 +118,7 @@ module Ci
# Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL` # Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces) groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
hierarchy_groups = Gitlab::GroupHierarchy.new(groups).base_and_descendants hierarchy_groups = Gitlab::ObjectHierarchy.new(groups).base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups) projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled .with_group_runners_enabled
.with_builds_enabled .with_builds_enabled
......
...@@ -13,7 +13,7 @@ module Clusters ...@@ -13,7 +13,7 @@ module Clusters
configure_kubernetes configure_kubernetes
cluster.save! cluster.save!
ClusterPlatformConfigureWorker.perform_async(cluster.id) ClusterConfigureWorker.perform_async(cluster.id)
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
log_service_error(e.class.name, provider.id, e.message) log_service_error(e.class.name, provider.id, e.message)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module DeployKeys module DeployKeys
class CreateService < Keys::BaseService class CreateService < Keys::BaseService
def execute def execute(project: nil)
DeployKey.create(params.merge(user: user)) DeployKey.create(params.merge(user: user))
end end
end end
......
...@@ -18,7 +18,7 @@ module Groups ...@@ -18,7 +18,7 @@ module Groups
return namespace return namespace
end end
if group_path.include?('/') && !Group.supports_nested_groups? if group_path.include?('/') && !Group.supports_nested_objects?
raise 'Nested groups are not supported on MySQL' raise 'Nested groups are not supported on MySQL'
end end
......
...@@ -40,7 +40,7 @@ module Groups ...@@ -40,7 +40,7 @@ module Groups
def ensure_allowed_transfer def ensure_allowed_transfer
raise_transfer_error(:group_is_already_root) if group_is_already_root? raise_transfer_error(:group_is_already_root) if group_is_already_root?
raise_transfer_error(:database_not_supported) unless Group.supports_nested_groups? raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects?
raise_transfer_error(:same_parent_as_current) if same_parent? raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies? raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path? raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
......
...@@ -81,6 +81,7 @@ module Projects ...@@ -81,6 +81,7 @@ module Projects
def update_repository_configuration def update_repository_configuration
project.reload_repository! project.reload_repository!
project.write_repository_config project.write_repository_config
project.track_project_repository
end end
def rename_transferred_documents def rename_transferred_documents
......
...@@ -137,6 +137,8 @@ module Projects ...@@ -137,6 +137,8 @@ module Projects
raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.') raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
end end
project.leave_pool_repository
Project.transaction do Project.transaction do
log_destroy_event log_destroy_event
trash_repositories! trash_repositories!
......
...@@ -81,7 +81,7 @@ module Projects ...@@ -81,7 +81,7 @@ module Projects
project.old_path_with_namespace = @old_path project.old_path_with_namespace = @old_path
write_repository_config(@new_path) update_repository_configuration(@new_path)
execute_system_hooks execute_system_hooks
end end
...@@ -106,8 +106,9 @@ module Projects ...@@ -106,8 +106,9 @@ module Projects
project.save! project.save!
end end
def write_repository_config(full_path) def update_repository_configuration(full_path)
project.write_repository_config(gl_full_path: full_path) project.write_repository_config(gl_full_path: full_path)
project.track_project_repository
end end
def refresh_permissions def refresh_permissions
...@@ -123,7 +124,7 @@ module Projects ...@@ -123,7 +124,7 @@ module Projects
rollback_folder_move rollback_folder_move
project.reload project.reload
update_namespace_and_visibility(@old_namespace) update_namespace_and_visibility(@old_namespace)
write_repository_config(@old_path) update_repository_configuration(@old_path)
end end
def rollback_folder_move def rollback_folder_move
......
...@@ -102,7 +102,7 @@ module Users ...@@ -102,7 +102,7 @@ module Users
end end
def fresh_authorizations def fresh_authorizations
klass = if Group.supports_nested_groups? klass = if Group.supports_nested_objects?
Gitlab::ProjectAuthorizations::WithNestedGroups Gitlab::ProjectAuthorizations::WithNestedGroups
else else
Gitlab::ProjectAuthorizations::WithoutNestedGroups Gitlab::ProjectAuthorizations::WithoutNestedGroups
......
...@@ -49,5 +49,12 @@ ...@@ -49,5 +49,12 @@
Once that time passes, the jobs will be archived and no longer able to be Once that time passes, the jobs will be archived and no longer able to be
retried. Make it empty to never expire jobs. It has to be no less than 1 day, retried. Make it empty to never expire jobs. It has to be no less than 1 day,
for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>. for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
.form-group
.form-check
= f.check_box :protected_ci_variables, class: 'form-check-input'
= f.label :protected_ci_variables, class: 'form-check-label' do
= s_('AdminSettings|Environment variables are protected by default')
.form-text.text-muted
= s_('AdminSettings|When creating a new environment variable it will be protected by default.')
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
= _('Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want.') = _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want.')
- expanded = local_assigns.fetch(:expanded)
%h4
= _('Environment variables')
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p.append-bottom-0
= render "ci/variables/content"
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.
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.
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