Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-21 21:10:20 +00:00
parent c742109766
commit 798e0b5920
35 changed files with 241 additions and 63 deletions

View File

@ -129,7 +129,7 @@ gem 'apollo_upload_server', '~> 2.1.0'
gem 'graphql-docs', '~> 2.1.0', group: [:development, :test]
gem 'graphlient', '~> 0.5.0' # Used by BulkImport feature (group::import)
gem 'hashie'
gem 'hashie', '~> 5.0.0'
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'

View File

@ -259,7 +259,7 @@
{"name":"hana","version":"1.3.7","platform":"ruby","checksum":"5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d"},
{"name":"hangouts-chat","version":"0.0.5","platform":"ruby","checksum":"bdbeb6c6e4abc98f395cb273f53b39911b3aa9e248fbbf063242b021ced8b6b6"},
{"name":"hashdiff","version":"1.0.1","platform":"ruby","checksum":"2cd4d04f5080314ecc8403c4e2e00dbaa282dff395e2d031bc16c8d501bdd6db"},
{"name":"hashie","version":"4.1.0","platform":"ruby","checksum":"7890dcb9ec18a4b66acec797018c73824b89cef5eb8cda36e8e8501845e87a09"},
{"name":"hashie","version":"5.0.0","platform":"ruby","checksum":"9d6c4e51f2a36d4616cbc8a322d619a162d8f42815a792596039fc95595603da"},
{"name":"hashie-forbidden_attributes","version":"0.1.1","platform":"ruby","checksum":"3a6ed37f3a314e4fb1dd1e2df6eb7721bcadd023a30bc0b951b2b5285a790fb2"},
{"name":"health_check","version":"3.1.0","platform":"ruby","checksum":"10146508237dc54ed7e24c292d8ba7fb8f9590cf26c66e325b947438c4103b57"},
{"name":"heapy","version":"0.2.0","platform":"ruby","checksum":"74141e845d61ffc7c1e8bf8b127c8cf94544ec7a1181aec613288682543585ea"},

View File

@ -709,7 +709,7 @@ GEM
hana (1.3.7)
hangouts-chat (0.0.5)
hashdiff (1.0.1)
hashie (4.1.0)
hashie (5.0.0)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (3.1.0)
@ -1661,7 +1661,7 @@ DEPENDENCIES
haml_lint (~> 0.40.0)
hamlit (~> 2.15.0)
hangouts-chat (~> 0.0.5)
hashie
hashie (~> 5.0.0)
hashie-forbidden_attributes
health_check (~> 3.0)
html-pipeline (~> 2.13.2)

View File

@ -109,10 +109,11 @@ export default {
writePackageRegistryHelp: s__(
'DeployTokens|Allows read and write access to the package registry.',
),
createTokenFailedAlert: s__('DeployTokens|Failed to create a new deployment token'),
},
computed: {
formattedExpiryDate() {
return formatDate(this.expiresAt, 'yyyy-mm-dd');
return this.expiresAt ? formatDate(this.expiresAt, 'yyyy-mm-dd') : '';
},
newTokenCreatedMessage() {
return this.tokenType === 'group'
@ -129,6 +130,9 @@ export default {
name: this.name,
read_repository: this.readRepository,
read_registry: this.readRegistry,
write_registry: this.writeRegistry,
read_package_registry: this.readPackageRegistry,
write_package_registry: this.writePackageRegistry,
username: this.username,
},
})
@ -142,7 +146,8 @@ export default {
})
.catch((error) => {
createAlert({
message: error.response.data.message,
message:
error?.response?.data?.message || this.$options.translations.createTokenFailedAlert,
});
});
},

View File

@ -1,7 +1,6 @@
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { GlLink, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
import { __ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import {
MANUAL_DEPLOY,
@ -18,7 +17,7 @@ export default {
components: {
GlLink,
MemoryUsage: () => import('./memory_usage.vue'),
TooltipOnTruncate,
GlTruncate,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -74,16 +73,19 @@ export default {
<div class="js-deployment-info deployment-info">
<template v-if="hasDeploymentMeta">
<span>{{ deployedText }}</span>
<tooltip-on-truncate :title="deployment.name" truncate-target="child" class="label-truncate">
<gl-link
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-meta gl-font-sm gl-pb-1"
>
{{ deployment.name }}
</gl-link>
</tooltip-on-truncate>
<gl-link
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-meta gl-font-sm gl-pb-1"
>
<gl-truncate
class="js-deploy-env-name"
:text="deployment.name"
position="middle"
with-tooltip
/>
</gl-link>
</template>
<span
v-if="hasDeploymentTime"

View File

@ -72,7 +72,7 @@ export default {
:display="appButtonText"
:link="deploymentExternalUrl"
size="small"
css-class="deploy-link js-deploy-url inline gl-ml-3"
css-class="deploy-link js-deploy-url inline"
/>
<modal-copy-button
v-else
@ -116,7 +116,7 @@ export default {
:display="appButtonText"
:link="deploymentExternalUrl"
size="small"
css-class="deploy-link js-deploy-url inline gl-ml-3"
css-class="deploy-link js-deploy-url inline"
/>
<modal-copy-button
v-else

View File

@ -391,6 +391,10 @@ $tabs-holder-z-index: 250;
text-overflow: ellipsis;
min-width: 100px;
display: grid;
grid-template-columns: max-content minmax(0, max-content) max-content;
grid-gap: $gl-spacing-scale-2;
@include media-breakpoint-up(xs) {
min-width: 0;
max-width: 100%;
@ -404,6 +408,7 @@ $tabs-holder-z-index: 250;
.deploy-heading,
.merge-train-position-indicator {
padding: $gl-padding-8;
@include media-breakpoint-up(md) {
padding: $gl-padding-8 $gl-padding;
}
@ -637,7 +642,6 @@ $tabs-holder-z-index: 250;
word-break: break-all;
}
.deploy-link,
.label-branch {
&.label-truncate {
// NOTE: This selector targets its children because some of the HTML comes from

View File

@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Troubleshooting topic type
Troubleshooting topics should be the last topics on a page.
Troubleshooting topics should be the final topics on a page.
If a page has more than five troubleshooting topics, put the content on a separate page that has troubleshooting information exclusively. Name the page `Troubleshooting <feature>`
and in the left nav, use the word `Troubleshooting` only.
@ -54,3 +54,13 @@ For the heading of a **Troubleshooting reference** topic:
- Use fewer than 70 characters.
If you do not put the full error in the title, include it in the body text.
## Rails console write functions
If the troubleshooting suggestion includes a function that changes data on the GitLab instance,
add the following warning:
```markdown
WARNING:
Commands that change data can cause damage if not run correctly or under the right conditions. Always run commands in a test environment first and have a backup instance ready to restore.
```

View File

@ -32,7 +32,7 @@ For error tracking to work, you need two pieces:
### Deploying Sentry
You can sign up to the cloud hosted [Sentry](https://sentry.io), deploy your own [on-premise instance](https://github.com/getsentry/onpremise/), or use GitLab to [install Sentry to a Kubernetes cluster](../user/infrastructure/clusters/manage/management_project_applications/sentry.md).
You can sign up to the cloud hosted [Sentry](https://sentry.io) or deploy your own [on-premise instance](https://github.com/getsentry/onpremise/).
### Enabling Sentry
@ -156,11 +156,11 @@ You can find the feature configuration at **Settings > Monitor > Error Tracking*
1. Select **GitLab** as the error tracking backend for your project:
![Error Tracking Settings](img/error_tracking_setting_v14_3.png)
![Error Tracking Settings](img/error_tracking_setting_v14_3.png)
1. Select **Save changes**. After page reload you should see a text field with the DSN string. Copy it.
![Error Tracking Settings DSN](img/error_tracking_setting_dsn_v14_4.png)
![Error Tracking Settings DSN](img/error_tracking_setting_dsn_v14_4.png)
1. Take the DSN from the previous step and configure your Sentry SDK with it. Errors are now
reported to the GitLab collector and are visible in the [GitLab UI](#error-tracking-list).

View File

@ -103,7 +103,6 @@ The [built-in supported applications](https://gitlab.com/gitlab-org/project-temp
- [GitLab Runner](../infrastructure/clusters/manage/management_project_applications/runner.md)
- [Ingress](../infrastructure/clusters/manage/management_project_applications/ingress.md)
- [Prometheus](../infrastructure/clusters/manage/management_project_applications/prometheus.md)
- [Sentry](../infrastructure/clusters/manage/management_project_applications/sentry.md)
- [Vault](../infrastructure/clusters/manage/management_project_applications/vault.md)
Each application has an `applications/{app}/values.yaml` file.

View File

@ -13309,6 +13309,9 @@ msgstr ""
msgid "DeployTokens|Expires"
msgstr ""
msgid "DeployTokens|Failed to create a new deployment token"
msgstr ""
msgid "DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group."
msgstr ""

View File

@ -6,7 +6,7 @@ module QA
RSpec.describe 'Plan' do
include Support::API
describe 'Issue' do
describe 'Issue', product_group: :project_management do
let(:issue) do
Resource::Issue.fabricate_via_api!
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :orchestrated, :smtp, :reliable do
RSpec.describe 'Plan', :orchestrated, :smtp, :reliable, product_group: :project_management do
describe 'Email Notification' do
include Support::API

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
let!(:user) do
Resource::User.fabricate_via_api! do |user|
user.name = "QA User <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;"

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'collapse comments in issue discussions' do
let(:my_first_reply) { 'My first reply' }
let(:one_reply) { '1 reply' }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Issue comments' do
before do
Flow::Login.sign_in

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :smoke do
RSpec.describe 'Plan', :smoke, product_group: :project_management do
describe 'Issue creation' do
let(:project) { Resource::Project.fabricate_via_api! }
let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Custom issue templates' do
let(:template_name) { 'custom_issue_template' }
let(:template_content) { 'This is a custom issue template test' }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Issues list' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'filter issue comments activities' do
before do
Flow::Login.sign_in

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'issue suggestions' do
let(:issue_title) { 'Issue Lists are awesome' }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :smoke do
RSpec.describe 'Plan', :smoke, product_group: :project_management do
describe 'mention' do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:project) do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan' do
RSpec.describe 'Plan', product_group: :project_management do
describe 'Assignees' do
let(:user1) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:user2) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Issue board focus mode' do
let(:project) do
QA::Resource::Project.fabricate_via_api! do |project|

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Milestones' do
include Support::Dates

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Group milestone' do
include Support::Dates

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Project milestone' do
include Support::Dates

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :reliable do
RSpec.describe 'Plan', :reliable, product_group: :project_management do
describe 'Related issues' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :transient do
RSpec.describe 'Plan', :transient, product_group: :project_management do
describe 'Discussion comments transient bugs' do
let(:user1) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)

View File

@ -1,4 +1,5 @@
#!/bin/sh
#!/usr/bin/env bash
set -euo pipefail
#
# This script runs the LicenseFinder gem to verify that all licenses are
# compliant. However, bundler v2.2+ and LicenseFinder do not play well

View File

@ -18,6 +18,11 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
let(:build) { create(:ci_build, :with_deployment, environment: environment.name, pipeline: pipeline) }
let!(:deployment) { build.deployment }
def assert_env_widget(text, env_name)
expect(find('.js-deploy-env-name')[:title]).to have_text(env_name)
expect(page).to have_content(text)
end
before do
merge_request.update!(merge_commit_sha: sha)
project.add_member(user, role)
@ -33,7 +38,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).to have_content("Deployed to #{environment.name}")
assert_env_widget("Deployed to", environment.name)
expect(find('.js-deploy-time')['title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
end
@ -47,8 +52,8 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
wait_for_requests
expect(page).to have_selector('.js-deployment-info', count: 1)
expect(page).to have_content("#{environment.name}")
expect(page).not_to have_content("#{environment2.name}")
expect(find('.js-deploy-env-name')[:title]).to have_text(environment.name)
expect(find('.js-deploy-env-name')[:title]).not_to have_text(environment2.name)
end
end
end
@ -62,7 +67,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).to have_content("Failed to deploy to #{environment.name}")
assert_env_widget("Failed to deploy to", environment.name)
expect(page).not_to have_css('.js-deploy-time')
end
end
@ -76,7 +81,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).to have_content("Deploying to #{environment.name}")
assert_env_widget("Deploying to", environment.name)
expect(page).not_to have_css('.js-deploy-time')
end
end
@ -89,7 +94,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).to have_content("Will deploy to #{environment.name}")
assert_env_widget("Will deploy to", environment.name)
expect(page).not_to have_css('.js-deploy-time')
end
end
@ -103,7 +108,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).to have_content("Canceled deployment to #{environment.name}")
assert_env_widget("Canceled deployment to", environment.name)
expect(page).not_to have_css('.js-deploy-time')
end
end

View File

@ -64,7 +64,8 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
wait_for_requests
page.within('.js-pre-deployment') do
expect(page).to have_content("Deployed to #{environment.name}")
expect(find('.js-deploy-env-name')[:title]).to have_text(environment.name)
expect(page).to have_content("Deployed to")
expect(find('.js-deploy-url')[:href]).to include(environment.formatted_external_url)
end
end

View File

@ -59,6 +59,19 @@ describe('New Deploy Token', () => {
expect(checkbox.text()).toBe('read_registry');
});
function submitTokenThenCheck() {
wrapper.findAllComponents(GlButton).at(0).vm.$emit('click');
return waitForPromises()
.then(() => nextTick())
.then(() => {
const [tokenUsername, tokenValue] = wrapper.findAllComponents(GlFormInputGroup).wrappers;
expect(tokenUsername.props('value')).toBe('test token username');
expect(tokenValue.props('value')).toBe('test token');
});
}
it('should make a request to create a token on submit', () => {
const mockAxios = new MockAdapter(axios);
@ -72,9 +85,18 @@ describe('New Deploy Token', () => {
const datepicker = wrapper.findAllComponents(GlDatepicker).at(0);
datepicker.vm.$emit('input', date);
const [readRepo, readRegistry] = wrapper.findAllComponents(GlFormCheckbox).wrappers;
const [
readRepo,
readRegistry,
writeRegistry,
readPackageRegistry,
writePackageRegistry,
] = wrapper.findAllComponents(GlFormCheckbox).wrappers;
readRepo.vm.$emit('input', true);
readRegistry.vm.$emit('input', true);
writeRegistry.vm.$emit('input', true);
readPackageRegistry.vm.$emit('input', true);
writePackageRegistry.vm.$emit('input', true);
mockAxios
.onPost(createNewTokenPath, {
@ -84,20 +106,47 @@ describe('New Deploy Token', () => {
username: 'test username',
read_repository: true,
read_registry: true,
write_registry: true,
read_package_registry: true,
write_package_registry: true,
},
})
.replyOnce(200, { username: 'test token username', token: 'test token' });
wrapper.findAllComponents(GlButton).at(0).vm.$emit('click');
return submitTokenThenCheck();
});
return waitForPromises()
.then(() => nextTick())
.then(() => {
const [tokenUsername, tokenValue] = wrapper.findAllComponents(GlFormInputGroup).wrappers;
it('should request a token without an expiration date', () => {
const mockAxios = new MockAdapter(axios);
expect(tokenUsername.props('value')).toBe('test token username');
expect(tokenValue.props('value')).toBe('test token');
});
const formInputs = wrapper.findAllComponents(GlFormInput);
const name = formInputs.at(0);
const username = formInputs.at(2);
name.vm.$emit('input', 'test never expire name');
username.vm.$emit('input', 'test never expire username');
const [, , , readPackageRegistry, writePackageRegistry] = wrapper.findAllComponents(
GlFormCheckbox,
).wrappers;
readPackageRegistry.vm.$emit('input', true);
writePackageRegistry.vm.$emit('input', true);
mockAxios
.onPost(createNewTokenPath, {
deploy_token: {
name: 'test never expire name',
expires_at: null,
username: 'test never expire username',
read_repository: false,
read_registry: false,
write_registry: false,
read_package_registry: true,
write_package_registry: true,
},
})
.replyOnce(200, { username: 'test token username', token: 'test token' });
return submitTokenThenCheck();
});
});
});

View File

@ -0,0 +1,42 @@
import { mount } from '@vue/test-utils';
import { GlTruncate, GlLink } from '@gitlab/ui';
import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
import { deploymentMockData } from './deployment_mock_data';
// This component is well covered in ./deployment_spec.js
// more component-specific tests are added below
describe('Deployment Info component', () => {
let wrapper;
const defaultDeploymentInfoOptions = {
computedDeploymentStatus: 'computed deployment status',
deployment: deploymentMockData,
showMetrics: false,
};
const factory = (options = {}) => {
const componentProps = { ...defaultDeploymentInfoOptions, ...options };
const componentOptions = { propsData: componentProps };
wrapper = mount(DeploymentInfo, componentOptions);
};
beforeEach(() => {
factory();
});
it('should render gl-truncate for environment name', () => {
const envNameComponent = wrapper.findComponent(GlTruncate);
expect(envNameComponent.exists()).toBe(true, 'We should use gl-truncate for environment name');
expect(envNameComponent.props()).toEqual({
text: deploymentMockData.name,
withTooltip: true,
position: 'middle',
});
});
it('should have a link with a correct href to deployed environment', () => {
const envLink = wrapper.findComponent(GlLink);
expect(envLink.exists()).toBe(true, 'We should have gl-link pointing to deployed environment');
expect(envLink.attributes().href).toBe(deploymentMockData.url);
});
});

View File

@ -125,6 +125,63 @@ RSpec.shared_context 'ProjectPolicyTable context' do
:private | :disabled | :anonymous | nil | 0
end
# This table is based on permission_table_for_guest_feature_access,
# but takes into account note confidentiality. It is required on the context
# to have one regular note and one confidential note.
#
# project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_notes_feature_access
:public | :enabled | :admin | true | 2
:public | :enabled | :admin | false | 1
:public | :enabled | :reporter | nil | 2
:public | :enabled | :guest | nil | 1
:public | :enabled | :non_member | nil | 1
:public | :enabled | :anonymous | nil | 1
:public | :private | :admin | true | 2
:public | :private | :admin | false | 0
:public | :private | :reporter | nil | 2
:public | :private | :guest | nil | 1
:public | :private | :non_member | nil | 0
:public | :private | :anonymous | nil | 0
:public | :disabled | :reporter | nil | 0
:public | :disabled | :guest | nil | 0
:public | :disabled | :non_member | nil | 0
:public | :disabled | :anonymous | nil | 0
:internal | :enabled | :admin | true | 2
:internal | :enabled | :admin | false | 1
:internal | :enabled | :reporter | nil | 2
:internal | :enabled | :guest | nil | 1
:internal | :enabled | :non_member | nil | 1
:internal | :enabled | :anonymous | nil | 0
:internal | :private | :admin | true | 2
:internal | :private | :admin | false | 0
:internal | :private | :reporter | nil | 2
:internal | :private | :guest | nil | 1
:internal | :private | :non_member | nil | 0
:internal | :private | :anonymous | nil | 0
:internal | :disabled | :reporter | nil | 0
:internal | :disabled | :guest | nil | 0
:internal | :disabled | :non_member | nil | 0
:internal | :disabled | :anonymous | nil | 0
:private | :private | :admin | true | 2
:private | :private | :admin | false | 0
:private | :private | :reporter | nil | 2
:private | :private | :guest | nil | 1
:private | :private | :non_member | nil | 0
:private | :private | :anonymous | nil | 0
:private | :disabled | :reporter | nil | 0
:private | :disabled | :guest | nil | 0
:private | :disabled | :non_member | nil | 0
:private | :disabled | :anonymous | nil | 0
end
# This table is based on permission_table_for_guest_feature_access,
# but with a slight twist.
# Some features can be hidden away to GUEST, when project is private.