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
*.rb @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
*.rake @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
*.rb @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @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/`
/doc/ @axil @marcia
......
......@@ -4,7 +4,7 @@
### 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
......
This diff is collapsed.
......@@ -130,7 +130,7 @@ gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2'
gem 'nokogiri', '~> 1.8.4'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
......@@ -165,7 +165,7 @@ gem 'acts-as-taggable-on', '~> 5.0'
gem 'sidekiq', '~> 5.2.1'
gem 'sidekiq-cron', '~> 0.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
gem 'rufus-scheduler', '~> 3.4'
......@@ -419,7 +419,7 @@ group :ed25519 do
end
# 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 'google-protobuf', '~> 3.6'
......
......@@ -274,13 +274,13 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (1.3.0)
gitaly-proto (1.5.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
activerecord (>= 3.2.0, < 6.0)
gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.1.0)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
gitlab-styles (2.4.1)
rubocop (~> 0.54.0)
......@@ -1008,11 +1008,11 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.3.0)
gitaly-proto (~> 1.5.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher (~> 0.1.0)
gitlab-sidekiq-fetcher (~> 0.4.0)
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
......@@ -1059,7 +1059,7 @@ DEPENDENCIES
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
nokogiri (~> 1.8.2)
nokogiri (~> 1.8.4)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
......
......@@ -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/.
### 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
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 = {
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
releasesPath: '/api/:version/projects/:id/releases',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -307,6 +308,12 @@ const Api = {
});
},
releases(id) {
const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id));
return axios.get(url);
},
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {
......
......@@ -36,7 +36,9 @@ export default class VariableList {
},
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: {
// We can't use a `.js-` class here because
......
......@@ -49,7 +49,7 @@ export default {
:is-bottom="index + 1 === diffLinesLength"
/>
<inline-diff-comment-row
:key="`icr-${index}`"
:key="`icr-${line.line_code || index}`"
:diff-file-hash="diffFile.file_hash"
:line="line"
:help-page-path="helpPagePath"
......
......@@ -43,14 +43,14 @@ export default {
<tbody>
<template v-for="(line, index) in diffLines">
<parallel-diff-table-row
:key="index"
:key="line.line_code"
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
/>
<parallel-diff-comment-row
:key="`dcr-${index}`"
:key="`dcr-${line.line_code || index}`"
:line="line"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
......
......@@ -196,6 +196,15 @@ export function trimFirstCharOfLineContent(line = {}) {
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
// by setting up incremental rendering and removing unneeded data
export function prepareDiffData(diffData) {
......@@ -208,6 +217,8 @@ export function prepareDiffData(diffData) {
const linesLength = file.parallel_diff_lines.length;
for (let u = 0; u < linesLength; u += 1) {
const line = file.parallel_diff_lines[u];
line.line_code = getLineCode(line, u);
if (line.left) {
line.left = trimFirstCharOfLineContent(line.left);
line.left.hasForm = false;
......
......@@ -14,6 +14,7 @@ import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { CLUSTER_TYPE } from '~/clusters/constants';
/**
* Environment Item Component
......@@ -84,6 +85,15 @@ export default {
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.
*
......@@ -547,6 +557,7 @@ export default {
<terminal-button-component
v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"
:disabled="disableGroupClusterFeatures"
/>
<rollback-component
......
......@@ -19,6 +19,11 @@ export default {
required: false,
default: '',
},
disabled: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
title() {
......@@ -33,6 +38,7 @@ export default {
:title="title"
:aria-label="title"
:href="terminalPath"
:class="{ disabled: disabled }"
class="btn terminal-button d-none d-sm-none d-md-block"
>
<icon name="terminal" />
......
......@@ -28,27 +28,29 @@ export default {
</script>
<template>
<div class="block">
<div class="title">{{ s__('Job|Job artifacts') }}</div>
<p v-if="isExpired" class="js-artifacts-removed build-detail-row">
{{ s__('Job|The artifacts were removed') }}
</p>
<p v-else-if="willExpire" class="js-artifacts-will-be-removed build-detail-row">
{{ s__('Job|The artifacts will be removed in') }}
</p>
<div class="title font-weight-bold">{{ s__('Job|Job artifacts') }}</div>
<p
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>
<div class="btn-group d-flex" role="group">
<div class="btn-group d-flex prepend-top-10" role="group">
<gl-link
v-if="artifact.keep_path"
:href="artifact.keep_path"
class="js-keep-artifacts btn btn-sm btn-default"
data-method="post"
>{{ s__('Job|Keep') }}</gl-link
>
{{ s__('Job|Keep') }}
</gl-link>
<gl-link
v-if="artifact.download_path"
......@@ -56,17 +58,15 @@ export default {
class="js-download-artifacts btn btn-sm btn-default"
download
rel="nofollow"
>{{ s__('Job|Download') }}</gl-link
>
{{ s__('Job|Download') }}
</gl-link>
<gl-link
v-if="artifact.browse_path"
:href="artifact.browse_path"
class="js-browse-artifacts btn btn-sm btn-default"
>{{ s__('Job|Browse') }}</gl-link
>
{{ s__('Job|Browse') }}
</gl-link>
</div>
</div>
</template>
......@@ -31,12 +31,12 @@ export default {
block: !isLastBlock,
}"
>
<p>
{{ __('Commit') }}
<p class="append-bottom-5">
<span class="font-weight-bold">{{ __('Commit') }}</span>
<gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">{{
commit.short_id
}}</gl-link>
<gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">
{{ commit.short_id }}
</gl-link>
<clipboard-button
:text="commit.short_id"
......@@ -44,11 +44,14 @@ export default {
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">
{{ __('in') }}
<gl-link :href="mergeRequest.path" class="js-link-commit link-commit"
>!{{ mergeRequest.iid }}</gl-link
>
</span>
</p>
<p class="build-light-text append-bottom-0">{{ commit.title }}</p>
<p class="append-bottom-0">{{ commit.title }}</p>
</div>
</template>
......@@ -110,22 +110,20 @@ export default {
<aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix">
<div class="sidebar-container">
<div class="blocks-container">
<div class="block">
<strong class="inline prepend-top-8"> {{ job.name }} </strong>
<div class="block d-flex align-items-center">
<h4 class="flex-grow-1 prepend-top-8 m-0">{{ job.name }}</h4>
<gl-link
v-if="job.retry_path"
:class="retryButtonClass"
:href="job.retry_path"
data-method="post"
rel="nofollow"
>{{ __('Retry') }}</gl-link
>
{{ __('Retry') }}
</gl-link>
<gl-link
v-if="job.terminal_path"
:href="job.terminal_path"
class="js-terminal-link pull-right btn btn-primary
btn-inverted visible-md-block visible-lg-block"
class="js-terminal-link pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block"
target="_blank"
>
{{ __('Debug') }} <icon name="external-link" />
......@@ -133,8 +131,7 @@ export default {
<gl-button
:aria-label="__('Toggle Sidebar')"
type="button"
class="btn btn-blank gutter-toggle
float-right d-block d-md-none js-sidebar-build-toggle"
class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle"
@click="toggleSidebar"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
......@@ -145,25 +142,18 @@ export default {
v-if="job.new_issue_path"
:href="job.new_issue_path"
class="js-new-issue btn btn-success btn-inverted"
>{{ __('New issue') }}</gl-link
>
{{ __('New issue') }}
</gl-link>
<gl-link
v-if="job.retry_path"
:href="job.retry_path"
class="js-retry-job btn btn-inverted-secondary"
data-method="post"
rel="nofollow"
>{{ __('Retry') }}</gl-link
>
{{ __('Retry') }}
</gl-link>
</div>
<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
v-if="job.duration"
:value="duration"
......@@ -198,10 +188,10 @@ export default {
title="Coverage"
/>
<p v-if="job.tags.length" class="build-detail-row js-job-tags">
<span class="build-light-text"> {{ __('Tags:') }} </span>
<span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary">
{{ tag }}
</span>
<span class="font-weight-bold">{{ __('Tags:') }}</span>
<span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{
tag
}}</span>
</p>
<div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group">
......@@ -210,9 +200,8 @@ export default {
class="js-cancel-job btn btn-sm btn-default"
data-method="post"
rel="nofollow"
>{{ __('Cancel') }}</gl-link
>
{{ __('Cancel') }}
</gl-link>
</div>
</div>
......
......@@ -34,8 +34,7 @@ export default {
</script>
<template>
<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">
<gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow">
<i class="fa fa-question-circle" aria-hidden="true"></i>
......
......@@ -38,11 +38,11 @@ export default {
<div class="block-last dropdown">
<ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
{{ __('Pipeline') }}
<a :href="pipeline.path" class="js-pipeline-path link-commit"> #{{ pipeline.id }} </a>
<span class="font-weight-bold">{{ __('Pipeline') }}</span>
<a :href="pipeline.path" class="js-pipeline-path link-commit">#{{ pipeline.id }}</a>
<template v-if="hasRef">
{{ __('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>
<button
......
......@@ -43,23 +43,24 @@ export default {
<template>
<div class="build-widget block">
<h4 class="title">{{ __('Trigger') }}</h4>
<p
v-if="trigger.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>
<template v-if="hasVariables">
<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">
{{ getToggleButtonText }}
</gl-button>
<gl-button
v-if="hasValues"
class="btn-sm group js-reveal-variables trigger-variables-btn"
@click="toggleValues"
>{{ getToggleButtonText }}</gl-button
>
</p>
<table class="js-build-variables trigger-build-variables">
......
......@@ -82,7 +82,7 @@ export function insertMarkdownText({
tag,
cursorOffset,
blockTag,
selected,
selected = '',
wrap,
select,
}) {
......@@ -212,7 +212,7 @@ export function addMarkdownListeners(form) {
blockTag: $this.data('mdBlock'),
wrap: !$this.data('mdPrepend'),
select: $this.data('mdSelect'),
tagContent: $this.data('mdTagContent').toString(),
tagContent: $this.data('mdTagContent'),
});
});
}
......
......@@ -178,31 +178,32 @@ export default {
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__(
'MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}',
);
} else if (this.discussion.diff_discussion) {
if (this.discussion.active) {
text = s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}');
} else {
text = s__(
} else if (isDiffDiscussion && commitId) {
text = isActive
? s__('MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}')
: s__(
'MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{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(
text,
{
commitId,
linkStart,
linkEnd,
},
false,
);
return sprintf(text, { commitId, linkStart, linkEnd }, false);
},
diffLine() {
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>
import _ from 'underscore';
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
......@@ -17,52 +18,16 @@ export default {
},
mixins: [timeagoMixin],
props: {
name: {
type: String,
required: true,
},
tag: {
type: String,
required: true,
},
commit: {
release: {
type: Object,
required: true,
},
description: {
type: String,
required: false,
default: '',
},
author: {
type: Object,
required: true,
},
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: () => [],
default: () => ({}),
},
},
computed: {
releasedTimeAgo() {
return sprintf('released %{time}', {
time: this.timeFormated(this.createdAt),
time: this.timeFormated(this.release.created_at),
});
},
userImageAltDescription() {
......@@ -70,13 +35,25 @@ export default {
? sprintf("%{username}'s avatar", { username: this.author.username })
: null;
},
commit() {
return this.release.commit || {};
},
assets() {
return this.release.assets || {};
},
author() {
return this.release.author || {};
},
hasAuthor() {
return _.isEmpty(this.author);
},
},
};
</script>
<template>
<div class="card">
<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="append-right-8">
......@@ -86,15 +63,17 @@ export default {
<div class="append-right-8">
<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 class="append-right-4">
&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 class="d-flex">
<div v-if="hasAuthor" class="d-flex">
by
<user-avatar-link
class="prepend-left-4"
......@@ -106,20 +85,25 @@ export default {
</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>
{{ __('Assets') }} <span class="js-assets-count badge badge-pill">{{ assetsCount }}</span>
{{ __('Assets') }}
<span class="js-assets-count badge badge-pill">{{ assets.count }}</span>
</b>
<ul 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">
<ul v-if="assets.links.length" class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list">
<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">
<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>
</li>
</ul>
<div class="dropdown">
<div v-if="assets.sources.length" class="dropdown">
<button
type="button"
class="btn btn-link"
......@@ -132,14 +116,14 @@ export default {
</button>
<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>
</li>
</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>
</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) {
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post';
if ($this.attr('data-delete-path')) {
url = '' + $this.attr('data-delete-path');
ajaxType = $this.data('deletePath') ? 'delete' : 'post';
if ($this.data('deletePath')) {
url = '' + $this.data('deletePath');
} else {
url = '' + $this.data('url');
url = '' + $this.data('createPath');
}
$this.tooltip('hide');
......@@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) {
.removeClass('is-loading')
.enable()
.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')) {
$el.tooltip('_fixTitle');
}
if ($el.data(`${attrPrefix}Icon`)) {
if (typeof $el.data('isCollapsed') !== 'undefined') {
$elText.html($el.data(`${attrPrefix}Icon`));
} else {
$elText.text($el.data(`${attrPrefix}Text`));
......
......@@ -81,7 +81,7 @@ export default {
</p>
<ul>
<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>
The functions listed in the <code>serverless.yml</code> file don't match the namespace
of your cluster.
......
......@@ -71,7 +71,7 @@ export default class SidebarStore {
}
findAssignee(findAssignee) {
return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0];
return this.assignees.find(assignee => assignee.id === findAssignee.id);
}
removeAssignee(removeAssignee) {
......
<script>
import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
......@@ -28,23 +27,6 @@ export default {
},
},
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() {
if (this.user.status.emoji && this.user.status.message) {
return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message}`;
......@@ -86,7 +68,8 @@ export default {
<gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
</div>
<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
v-if="jobInfoIsLoading"
:lines="1"
......
......@@ -24,7 +24,7 @@
}
}
&:not(.use-csslab) table {
table {
@extend .table;
}
......
......@@ -42,7 +42,6 @@
padding: 10px;
text-align: right;
float: left;
line-height: 1;
a {
font-family: $monospace-font;
......@@ -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 @@
&.w-100 {
// after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here
// https://github.com/twbs/bootstrap/pull/26976
margin-right: -2rem;
padding-right: 2rem;
margin-right: -28px;
padding-right: 28px;
}
}
......
......@@ -198,7 +198,7 @@ $well-light-text-color: #5b6169;
$gl-font-size: 14px;
$gl-font-size-xs: 11px;
$gl-font-size-small: 12px;
$gl-font-size-medium: 1.43rem;
$gl-font-size-medium: 20px;
$gl-font-size-large: 16px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
......
......@@ -28,3 +28,9 @@ $popover-border-width: 1px;
$popover-border-color: $border-color;
$popover-box-shadow: 0 $border-radius-small $border-radius-default 0 $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 @@
.build-loader-animation {
@include build-loader-animation;
float: left;
padding-left: $gl-padding-8;
}
}
......@@ -232,6 +233,11 @@
@extend .d-flex;
justify-content: space-between;
align-items: center;
.trigger-variables-btn {
margin-top: -5px;
margin-bottom: -5px;
}
}
.trigger-build-variables {
......
......@@ -403,7 +403,7 @@ class ApplicationController < ActionController::Base
end
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
# U2F (universal 2nd factor) devices need a unique identifier for the application
......
......@@ -32,14 +32,14 @@ module GroupTree
def filtered_groups_with_ancestors(groups)
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.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
#
# Pagination needs to be applied before loading the ancestors to
# 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
else
filtered_groups
......
......@@ -5,7 +5,6 @@ module IssuableActions
include Gitlab::Utils::StrongMemoize
included do
before_action :labels, only: [:show, :new, :edit]
before_action :authorize_destroy_issuable!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
end
......@@ -25,7 +24,10 @@ module IssuableActions
def show
respond_to do |format|
format.html
format.html do
@issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
format.json do
render json: serializer.represent(issuable, serializer: params[:serializer])
end
......@@ -168,10 +170,6 @@ module IssuableActions
end
end
def labels
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def authorize_destroy_issuable!
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
......
......@@ -94,6 +94,7 @@ module LfsRequest
def lfs_upload_access?
return false unless project.lfs_enabled?
return false unless has_authentication_ability?(:push_code)
return false if limit_exceeded?
lfs_deploy_token? || can?(user, :push_code, project)
end
......@@ -121,4 +122,9 @@ module LfsRequest
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
# Overriden in EE
def limit_exceeded?
false
end
end
......@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
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?
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
......
......@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
def show
respond_to do |format|
format.html do
labels
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
end
format.json do
......@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
end
end
private
def authorize_can_resolve_conflicts!
@conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
end
def serializer
MergeRequestSerializer.new(current_user: current_user, project: project)
end
end
......@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
format.html
format.json do
render json: {
html: view_to_html_string("projects/merge_requests/_merge_requests"),
labels: @labels.as_json(methods: :text_color)
html: view_to_html_string("projects/merge_requests/_merge_requests")
}
end
end
......@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
labels
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
set_pipeline_variables
......
......@@ -112,7 +112,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
def ancestors_of_groups(base_for_ancestors)
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)
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -132,7 +132,7 @@ class GroupDescendantsFinder
end
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
# descendants to show to the user
......@@ -183,7 +183,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
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
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -46,7 +46,7 @@ class GroupsFinder < UnionFinder
return [Group.all] if current_user&.full_private_access? && all_available?
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.none if groups.empty?
groups
......@@ -66,7 +66,7 @@ class GroupsFinder < UnionFinder
.groups
.where('members.access_level >= ?', params[:min_access_level])
Gitlab::GroupHierarchy
Gitlab::ObjectHierarchy
.new(groups)
.base_and_descendants
end
......
......@@ -26,6 +26,18 @@ module ApplicationSettingsHelper
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)
case protocol
when 'ssh'
......@@ -218,7 +230,8 @@ module ApplicationSettingsHelper
:version_check_enabled,
:web_ide_clientside_preview_enabled,
:diff_max_patch_bytes,
:commit_email_hostname
:commit_email_hostname,
:protected_ci_variables
]
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
end
def supports_nested_groups?
Group.supports_nested_groups?
Group.supports_nested_objects?
end
private
......
......@@ -23,31 +23,42 @@ module IssuablesHelper
end
end
def sidebar_due_date_tooltip_label(issuable)
if issuable.due_date
"#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
else
_('Due date')
def sidebar_milestone_tooltip_label(milestone)
return _('Milestone') unless milestone.present?
[milestone[:title], sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>')
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
def due_date_with_remaining_days(due_date, start_date = nil)
return unless due_date
"#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
end
def due_date_remaining_days(issuable)
remaining_days_in_words = remaining_days_in_words(issuable)
def sidebar_label_filter_path(base_path, label_name)
query_params = { label_name: [label_name] }.to_query
"#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})"
"#{base_path}?#{query_params}"
end
def multi_label_name(current_labels, default_label)
if current_labels && current_labels.any?
title = current_labels.first.try(:title)
return default_label if current_labels.blank?
title = current_labels.first.try(:title) || current_labels.first[:title]
if current_labels.size > 1
"#{title} +#{current_labels.size - 1} more"
else
title
end
else
default_label
end
end
def issuable_json_path(issuable)
......@@ -197,19 +208,11 @@ module IssuablesHelper
output.join.html_safe
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)
first, last = labels.partition.with_index { |_, i| i < limit }
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.join(', ')
......@@ -356,12 +359,6 @@ module IssuablesHelper
issuable.model_name.human.downcase
end
def selected_labels
Array(params[:label_name]).map do |label_name|
Label.new(title: label_name)
end
end
def has_filter_bar_param?
finder.class.scalar_params.any? { |p| params[p].present? }
end
......@@ -386,19 +383,20 @@ module IssuablesHelper
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end
def issuable_todo_button_data(issuable, todo, is_collapsed)
def issuable_todo_button_data(issuable, is_collapsed)
{
todo_text: "Add todo",
mark_text: "Mark todo as done",
todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil),
mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil),
issuable_id: issuable.id,
issuable_type: issuable.class.name.underscore,
url: project_todos_path(@project),
delete_path: (dashboard_todo_path(todo) if todo),
placement: (is_collapsed ? 'left' : nil),
container: (is_collapsed ? 'body' : nil),
boundary: 'viewport'
todo_text: _('Add todo'),
mark_text: _('Mark todo as done'),
todo_icon: sprite_icon('todo-add'),
mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'),
issuable_id: issuable[:id],
issuable_type: issuable[:type],
create_path: issuable[:create_todo_path],
delete_path: issuable.dig(:current_user, :todo, :delete_path),
placement: is_collapsed ? 'left' : nil,
container: is_collapsed ? 'body' : nil,
boundary: 'viewport',
is_collapsed: is_collapsed
}
end
......@@ -418,27 +416,20 @@ module IssuablesHelper
end
end
def issuable_sidebar_options(issuable, can_edit_issuable)
def issuable_sidebar_options(issuable)
{
endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar",
toggleSubscriptionEndpoint: toggle_subscription_path(issuable),
moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable),
projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id),
editable: can_edit_issuable,
currentUser: UserSerializer.new.represent(current_user),
endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras",
toggleSubscriptionEndpoint: issuable[:toggle_subscription_path],
moveIssueEndpoint: issuable[:move_issue_path],
projectsAutocompleteEndpoint: issuable[:projects_autocomplete_path],
editable: issuable.dig(:current_user, :can_edit),
currentUser: issuable[:current_user],
rootPath: root_path,
fullPath: @project.full_path
fullPath: issuable[:project_full_path]
}
end
def parent
@project || @group
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
......@@ -114,12 +114,6 @@ module MilestonesHelper
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)
title = date_type == :start ? "Start date" : "End date"
......@@ -173,7 +167,7 @@ module MilestonesHelper
def milestone_tooltip_due_date(milestone)
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
_('Milestone')
end
......
......@@ -271,6 +271,20 @@ module ProjectsHelper
params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
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
def get_project_nav_tabs(project, current_user)
......@@ -515,20 +529,6 @@ module ProjectsHelper
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
%w[
projects#show
......
......@@ -56,7 +56,9 @@ module Emails
@milestone = 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
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil)
......
......@@ -51,7 +51,9 @@ module Emails
@milestone = 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
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
......
......@@ -16,6 +16,7 @@ class Notify < BaseMailer
include Emails::AutoDevops
include Emails::RemoteMirrors
helper MilestonesHelper
helper MergeRequestsHelper
helper DiffHelper
helper BlobHelper
......
......@@ -302,7 +302,8 @@ class ApplicationSetting < ActiveRecord::Base
user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil,
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
......
......@@ -22,49 +22,30 @@ class BroadcastMessage < ActiveRecord::Base
after_commit :flush_redis_cache
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
current_and_future_messages.to_json
current_and_future_messages
end
messages = decode_messages(raw_messages)
return [] unless messages&.present?
now_or_future = messages.select(&:now_or_future?)
# 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.
Rails.cache.delete(CACHE_KEY) if now_or_future.empty?
cache.expire(CACHE_KEY) if now_or_future.empty?
now_or_future.select(&:now?)
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
where('ends_at > :now', now: Time.zone.now).order_id_asc
end
def self.cache
Gitlab::JsonCache.new(cache_key_with_version: false)
end
def self.cache_expires_in
nil
end
......@@ -74,7 +55,7 @@ class BroadcastMessage < ActiveRecord::Base
# environment a one-shot migration would not work because the cache
# would be repopulated by a node that has not been upgraded.
def self.remove_legacy_cache_key
Rails.cache.delete(LEGACY_CACHE_KEY)
cache.expire(LEGACY_CACHE_KEY)
end
def active?
......@@ -102,7 +83,7 @@ class BroadcastMessage < ActiveRecord::Base
end
def flush_redis_cache
Rails.cache.delete(CACHE_KEY)
self.class.cache.expire(CACHE_KEY)
self.class.remove_legacy_cache_key
end
end
......@@ -66,7 +66,7 @@ module Ci
scope :belonging_to_parent_group_of_project, -> (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 })
}
......
......@@ -228,7 +228,7 @@ module Clusters
return unless namespace_changed?
run_after_commit do
ClusterPlatformConfigureWorker.perform_async(cluster_id)
ClusterConfigureWorker.perform_async(cluster_id)
end
end
end
......
......@@ -43,7 +43,18 @@ module Avatarable
end
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
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
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true, diff_limit: nil)
return [] unless on_text?
return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote)
diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min
......
......@@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone
attr_reader :group_name
override :initialize
def initialize(milestone)
super(milestone.title, Array(milestone))
......
# frozen_string_literal: true
class Environment < ActiveRecord::Base
include Gitlab::Utils::StrongMemoize
# Used to generate random suffixes for the slug
LETTERS = 'a'..'z'
NUMBERS = '0'..'9'
......@@ -231,8 +232,10 @@ class Environment < ActiveRecord::Base
end
def deployment_platform
strong_memoize(:deployment_platform) do
project.deployment_platform(environment: self.name)
end
end
private
......
......@@ -10,6 +10,7 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
include LoadedInGroupList
include Descendant
include GroupDescendant
include TokenAuthenticatable
include WithUploads
......@@ -63,10 +64,6 @@ class Group < Namespace
after_update :path_changed_hook, if: :path_changed?
class << self
def supports_nested_groups?
Gitlab::Database.postgresql?
end
def sort_by_attribute(method)
if method == 'storage_size_desc'
# storage_size is a virtual column so we need to
......
......@@ -175,16 +175,16 @@ class Namespace < ActiveRecord::Base
# Returns all ancestors, self, and descendants of the current namespace.
def self_and_hierarchy
Gitlab::GroupHierarchy
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.all_groups
.all_objects
end
# Returns all the ancestors of the current namespaces.
def ancestors
return self.class.none unless parent_id
Gitlab::GroupHierarchy
Gitlab::ObjectHierarchy
.new(self.class.where(id: parent_id))
.base_and_ancestors
end
......@@ -192,27 +192,27 @@ class Namespace < ActiveRecord::Base
# returns all ancestors upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
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)
end
def self_and_ancestors
return self.class.where(id: id) unless parent_id
Gitlab::GroupHierarchy
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_ancestors
end
# Returns all the descendants of the current namespace.
def descendants
Gitlab::GroupHierarchy
Gitlab::ObjectHierarchy
.new(self.class.where(parent_id: id))
.base_and_descendants
end
def self_and_descendants
Gitlab::GroupHierarchy
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_descendants
end
......@@ -293,7 +293,7 @@ class Namespace < ActiveRecord::Base
end
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
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
......@@ -306,6 +306,7 @@ class Namespace < ActiveRecord::Base
def write_projects_repository_config
all_projects.find_each do |project|
project.write_repository_config
project.track_project_repository
end
end
end
......@@ -18,6 +18,7 @@ class PoolRepository < ActiveRecord::Base
state :scheduled
state :ready
state :failed
state :obsolete
event :schedule do
transition none: :scheduled
......@@ -31,6 +32,10 @@ class PoolRepository < ActiveRecord::Base
transition all => :failed
end
event :mark_obsolete do
transition all => :obsolete
end
state all - [:ready] do
def joinable?
false
......@@ -54,6 +59,12 @@ class PoolRepository < ActiveRecord::Base
::ObjectPool::ScheduleJoinWorker.perform_async(pool.id)
end
end
after_transition any => :obsolete do |pool, _|
pool.run_after_commit do
::ObjectPool::DestroyWorker.perform_async(pool.id)
end
end
end
def create_object_pool
......@@ -71,10 +82,10 @@ class PoolRepository < ActiveRecord::Base
end
# This RPC can cause data loss, as not all objects are present the local repository
# No execution path yet, will be added through:
# https://gitlab.com/gitlab-org/gitaly/issues/1415
def delete_repository_alternate(repository)
def unlink_repository(repository)
object_pool.unlink_repository(repository.raw)
mark_obsolete unless member_projects.where.not(id: repository.project.id).exists?
end
def object_pool
......
......@@ -256,7 +256,7 @@ class Project < ActiveRecord::Base
# other pipelines, like webide ones, that we won't retrieve
# if we use this relation.
has_many :ci_pipelines,
-> { Feature.enabled?(:pipeline_ci_sources_only, default_enabled: true) ? ci_sources : all },
-> { ci_sources },
class_name: 'Ci::Pipeline',
inverse_of: :project
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
......@@ -570,7 +570,7 @@ class Project < ActiveRecord::Base
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
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)
end
......@@ -1244,10 +1244,8 @@ class Project < ActiveRecord::Base
end
def track_project_repository
return unless hashed_storage?(:repository)
project_repo = project_repository || build_project_repository
project_repo.update!(shard_name: repository_storage, disk_path: disk_path)
repository = project_repository || build_project_repository
repository.update!(shard_name: repository_storage, disk_path: disk_path)
end
def create_repository(force: false)
......@@ -2004,6 +2002,10 @@ class Project < ActiveRecord::Base
Feature.enabled?(:object_pools, self)
end
def leave_pool_repository
pool_repository&.unlink_repository(repository)
end
private
def create_new_pool_repository
......
......@@ -709,13 +709,13 @@ class User < ActiveRecord::Base
# Returns the groups a user is a member of, either directly or through a parent group
def membership_groups
Gitlab::GroupHierarchy.new(groups).base_and_descendants
Gitlab::ObjectHierarchy.new(groups).base_and_descendants
end
# Returns a relation of groups the user has access to, including their parent
# and child groups (recursively).
def all_expanded_groups
Gitlab::GroupHierarchy.new(groups).all_groups
Gitlab::ObjectHierarchy.new(groups).all_objects
end
def expanded_groups_requiring_two_factor_authentication
......@@ -1153,7 +1153,7 @@ class User < ActiveRecord::Base
end
def manageable_groups
Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants
Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end
def namespaces
......@@ -1422,6 +1422,10 @@ class User < ActiveRecord::Base
todos.where(id: ids)
end
def pending_todo_for(target)
todos.find_by(target: target, state: :pending)
end
# @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
......
......@@ -16,7 +16,7 @@ class GroupPolicy < BasePolicy
condition(:maintainer) { access_level >= GroupMember::MAINTAINER }
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(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
......
......@@ -44,14 +44,14 @@ module EntityDateHelper
# It returns "Upcoming" for upcoming entities
# 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"
def remaining_days_in_words(entity)
if entity.try(:expired?)
def remaining_days_in_words(due_date, start_date = nil)
if due_date&.past?
content_tag(:strong, 'Past due')
elsif entity.try(:upcoming?)
elsif start_date&.future?
content_tag(:strong, 'Upcoming')
elsif entity.due_date
is_upcoming = (entity.due_date - Date.today).to_i > 0
time_ago = time_ago_in_words(entity.due_date)
elsif due_date
is_upcoming = (due_date - Date.today).to_i > 0
time_ago = time_ago_in_words(due_date)
# https://gitlab.com/gitlab-org/gitlab-ce/issues/49440
#
......@@ -63,8 +63,8 @@ module EntityDateHelper
remaining_or_ago = is_upcoming ? _("remaining") : _("ago")
"#{content} #{remaining_or_ago}".html_safe
elsif entity.start_date && entity.start_date.past?
days = entity.elapsed_days
elsif start_date&.past?
days = (Date.today - start_date).to_i
"#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe
end
end
......
......@@ -23,6 +23,10 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
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|
terminal_project_environment_path(environment.project, environment)
end
......@@ -48,4 +52,16 @@ class EnvironmentEntity < Grape::Entity
def can_access_terminal?
can?(request.current_user, :create_environment_terminal, environment)
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
# 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
class IssuableSidebarEntity < Grape::Entity
include TimeTrackableEntity
class IssuableSidebarExtrasEntity < Grape::Entity
include RequestAwareEntity
include TimeTrackableEntity
expose :participants, using: ::API::Entities::UserBasic do |issuable|
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
end
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
expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue|
......
......@@ -2,13 +2,15 @@
class IssueSerializer < BaseSerializer
# This overrided method takes care of which entity should be used
# to serialize the `issue` based on `basic` key in `opts` param.
# to serialize the `issue` based on `serializer` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
def represent(issue, opts = {})
entity =
case opts[:serializer]
when 'sidebar'
IssueSidebarEntity
IssueSidebarBasicEntity
when 'sidebar_extras'
IssueSidebarExtrasEntity
when 'board'
IssueBoardEntity
else
......
# frozen_string_literal: true
class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
expose :due_date
expose :confidential
end
# frozen_string_literal: true
class IssueSidebarEntity < IssuableSidebarEntity
class IssueSidebarExtrasEntity < IssuableSidebarExtrasEntity
expose :assignees, using: API::Entities::UserBasic
end
# frozen_string_literal: true
class MergeRequestBasicEntity < IssuableSidebarEntity
class MergeRequestBasicEntity < Grape::Entity
expose :assignee_id
expose :merge_status
expose :merge_error
......
# frozen_string_literal: true
class MergeRequestBasicSerializer < BaseSerializer
entity MergeRequestBasicEntity
end
......@@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer
def represent(merge_request, opts = {})
entity =
case opts[:serializer]
when 'basic', 'sidebar'
when 'sidebar'
MergeRequestSidebarBasicEntity
when 'sidebar_extras'
IssuableSidebarExtrasEntity
when 'basic'
MergeRequestBasicEntity
else # It's 'widget'
else
# fallback to widget for old poll requests without `serializer` set
MergeRequestWidgetEntity
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
# 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)
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)
.with_group_runners_enabled
.with_builds_enabled
......
......@@ -13,7 +13,7 @@ module Clusters
configure_kubernetes
cluster.save!
ClusterPlatformConfigureWorker.perform_async(cluster.id)
ClusterConfigureWorker.perform_async(cluster.id)
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
log_service_error(e.class.name, provider.id, e.message)
......
......@@ -2,7 +2,7 @@
module DeployKeys
class CreateService < Keys::BaseService
def execute
def execute(project: nil)
DeployKey.create(params.merge(user: user))
end
end
......
......@@ -18,7 +18,7 @@ module Groups
return namespace
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'
end
......
......@@ -40,7 +40,7 @@ module Groups
def ensure_allowed_transfer
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(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
......
......@@ -81,6 +81,7 @@ module Projects
def update_repository_configuration
project.reload_repository!
project.write_repository_config
project.track_project_repository
end
def rename_transferred_documents
......
......@@ -137,6 +137,8 @@ module Projects
raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
end
project.leave_pool_repository
Project.transaction do
log_destroy_event
trash_repositories!
......
......@@ -81,7 +81,7 @@ module Projects
project.old_path_with_namespace = @old_path
write_repository_config(@new_path)
update_repository_configuration(@new_path)
execute_system_hooks
end
......@@ -106,8 +106,9 @@ module Projects
project.save!
end
def write_repository_config(full_path)
def update_repository_configuration(full_path)
project.write_repository_config(gl_full_path: full_path)
project.track_project_repository
end
def refresh_permissions
......@@ -123,7 +124,7 @@ module Projects
rollback_folder_move
project.reload
update_namespace_and_visibility(@old_namespace)
write_repository_config(@old_path)
update_repository_configuration(@old_path)
end
def rollback_folder_move
......
......@@ -102,7 +102,7 @@ module Users
end
def fresh_authorizations
klass = if Group.supports_nested_groups?
klass = if Group.supports_nested_objects?
Gitlab::ProjectAuthorizations::WithNestedGroups
else
Gitlab::ProjectAuthorizations::WithoutNestedGroups
......
......@@ -49,5 +49,12 @@
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,
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"
= _('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