Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-30 12:09:39 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-30 12:09:39 +0300
commitada214dc52b53bd9eb3a79c279506f91c547f721 (patch)
treef4266ef83f9be3a62a0f8942911058758655929a /app
parent27b43bd4d613cc7b8773ca0863b8d8f9b90f6d87 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/constants.js6
-rw-r--r--app/assets/javascripts/boards/index.js110
-rw-r--r--app/assets/javascripts/boards/models/assignee.js2
-rw-r--r--app/assets/javascripts/boards/models/issue.js2
-rw-r--r--app/assets/javascripts/boards/models/list.js12
-rw-r--r--app/assets/javascripts/boards/queries/board_list.fragment.graphql5
-rw-r--r--app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql15
-rw-r--r--app/assets/javascripts/boards/queries/group_board.query.graphql13
-rw-r--r--app/assets/javascripts/boards/queries/project_board.query.graphql13
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js10
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue33
-rw-r--r--app/assets/javascripts/environments/mixins/environments_app_mixin.js32
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js11
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue20
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/controllers/help_controller.rb4
-rw-r--r--app/controllers/projects/environments_controller.rb11
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/helpers/boards_helper.rb3
-rw-r--r--app/helpers/projects_helper.rb3
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/policies/project_policy.rb25
-rw-r--r--app/services/metrics/dashboard/base_service.rb2
23 files changed, 243 insertions, 93 deletions
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index 40f79a44b51..f577a168e75 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -1,3 +1,8 @@
+export const BoardType = {
+ project: 'project',
+ group: 'group',
+};
+
export const ListType = {
assignee: 'assignee',
milestone: 'milestone',
@@ -11,5 +16,6 @@ export const ListType = {
export const inactiveListId = 0;
export default {
+ BoardType,
ListType,
};
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index a12db7a5f1a..7c41182d554 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -16,10 +16,13 @@ import {
getBoardsModalData,
} from 'ee_else_ce/boards/ee_functions';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
import Flash from '~/flash';
import { __ } from '~/locale';
import './models/label';
import './models/assignee';
+import { BoardType } from './constants';
import FilteredSearchBoards from '~/boards/filtered_search_boards';
import eventHub from '~/boards/eventhub';
@@ -37,7 +40,16 @@ import {
convertObjectPropsToCamelCase,
parseBoolean,
} from '~/lib/utils/common_utils';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
+import projectBoardQuery from './queries/project_board.query.graphql';
+import groupQuery from './queries/group_board.query.graphql';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
let issueBoardsApp;
@@ -79,18 +91,22 @@ export default () => {
import('ee_component/boards/components/board_settings_sidebar.vue'),
},
store,
- data: {
- state: boardsStore.state,
- loading: true,
- boardsEndpoint: $boardApp.dataset.boardsEndpoint,
- recentBoardsEndpoint: $boardApp.dataset.recentBoardsEndpoint,
- listsEndpoint: $boardApp.dataset.listsEndpoint,
- boardId: $boardApp.dataset.boardId,
- disabled: parseBoolean($boardApp.dataset.disabled),
- issueLinkBase: $boardApp.dataset.issueLinkBase,
- rootPath: $boardApp.dataset.rootPath,
- bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
- detailIssue: boardsStore.detail,
+ apolloProvider,
+ data() {
+ return {
+ state: boardsStore.state,
+ loading: 0,
+ boardsEndpoint: $boardApp.dataset.boardsEndpoint,
+ recentBoardsEndpoint: $boardApp.dataset.recentBoardsEndpoint,
+ listsEndpoint: $boardApp.dataset.listsEndpoint,
+ boardId: $boardApp.dataset.boardId,
+ disabled: parseBoolean($boardApp.dataset.disabled),
+ issueLinkBase: $boardApp.dataset.issueLinkBase,
+ rootPath: $boardApp.dataset.rootPath,
+ bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
+ detailIssue: boardsStore.detail,
+ parent: $boardApp.dataset.parent,
+ };
},
computed: {
detailIssueVisible() {
@@ -124,31 +140,56 @@ export default () => {
this.filterManager.setup();
boardsStore.disabled = this.disabled;
- boardsStore
- .all()
- .then(res => res.data)
- .then(lists => {
- lists.forEach(listObj => {
- let { position } = listObj;
- if (listObj.list_type === 'closed') {
- position = Infinity;
- } else if (listObj.list_type === 'backlog') {
- position = -1;
+
+ if (gon.features.graphqlBoardLists) {
+ this.$apollo.addSmartQuery('lists', {
+ query() {
+ return this.parent === BoardType.group ? groupQuery : projectBoardQuery;
+ },
+ variables() {
+ return {
+ fullPath: this.state.endpoints.fullPath,
+ boardId: `gid://gitlab/Board/${this.boardId}`,
+ };
+ },
+ update(data) {
+ return this.getNodes(data);
+ },
+ result({ data, error }) {
+ if (error) {
+ throw error;
}
- boardsStore.addList({
- ...listObj,
- position,
- });
- });
+ const lists = this.getNodes(data);
+
+ lists.forEach(list =>
+ boardsStore.addList({
+ ...list,
+ id: getIdFromGraphQLId(list.id),
+ }),
+ );
- boardsStore.addBlankState();
- setPromotionState(boardsStore);
- this.loading = false;
- })
- .catch(() => {
- Flash(__('An error occurred while fetching the board lists. Please try again.'));
+ boardsStore.addBlankState();
+ setPromotionState(boardsStore);
+ },
+ error() {
+ Flash(__('An error occurred while fetching the board lists. Please try again.'));
+ },
});
+ } else {
+ boardsStore
+ .all()
+ .then(res => res.data)
+ .then(lists => {
+ lists.forEach(list => boardsStore.addList(list));
+ boardsStore.addBlankState();
+ setPromotionState(boardsStore);
+ this.loading = false;
+ })
+ .catch(() => {
+ Flash(__('An error occurred while fetching the board lists. Please try again.'));
+ });
+ }
},
methods: {
updateTokens() {
@@ -233,6 +274,9 @@ export default () => {
});
}
},
+ getNodes(data) {
+ return data[this.parent]?.board?.lists.nodes;
+ },
},
});
diff --git a/app/assets/javascripts/boards/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js
index 5f5758583bb..1e822d06bfd 100644
--- a/app/assets/javascripts/boards/models/assignee.js
+++ b/app/assets/javascripts/boards/models/assignee.js
@@ -3,7 +3,7 @@ export default class ListAssignee {
this.id = obj.id;
this.name = obj.name;
this.username = obj.username;
- this.avatar = obj.avatar_url || obj.avatar || gon.default_avatar_url;
+ this.avatar = obj.avatarUrl || obj.avatar_url || obj.avatar || gon.default_avatar_url;
this.path = obj.path;
this.state = obj.state;
this.webUrl = obj.web_url || obj.webUrl;
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index af1a910149e..878f49cc6be 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -15,7 +15,7 @@ class ListIssue {
this.labels = [];
this.assignees = [];
this.selected = false;
- this.position = obj.relative_position || Infinity;
+ this.position = obj.position || obj.relative_position || Infinity;
this.isFetching = {
subscriptions: true,
};
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index cd46f8cd1a4..31c372b7a75 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -39,8 +39,8 @@ class List {
this.id = obj.id;
this._uid = this.guid();
this.position = obj.position;
- this.title = obj.list_type === 'backlog' ? __('Open') : obj.title;
- this.type = obj.list_type;
+ this.title = (obj.list_type || obj.listType) === 'backlog' ? __('Open') : obj.title;
+ this.type = obj.list_type || obj.listType;
const typeInfo = this.getTypeInfo(this.type);
this.preset = Boolean(typeInfo.isPreset);
@@ -51,14 +51,12 @@ class List {
this.loadingMore = false;
this.issues = obj.issues || [];
this.issuesSize = obj.issuesSize ? obj.issuesSize : 0;
- this.maxIssueCount = Object.hasOwnProperty.call(obj, 'max_issue_count')
- ? obj.max_issue_count
- : 0;
+ this.maxIssueCount = obj.maxIssueCount || obj.max_issue_count || 0;
if (obj.label) {
this.label = new ListLabel(obj.label);
- } else if (obj.user) {
- this.assignee = new ListAssignee(obj.user);
+ } else if (obj.user || obj.assignee) {
+ this.assignee = new ListAssignee(obj.user || obj.assignee);
this.title = this.assignee.name;
} else if (IS_EE && obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
diff --git a/app/assets/javascripts/boards/queries/board_list.fragment.graphql b/app/assets/javascripts/boards/queries/board_list.fragment.graphql
new file mode 100644
index 00000000000..bbf3314377e
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/board_list.fragment.graphql
@@ -0,0 +1,5 @@
+#import "./board_list_shared.fragment.graphql"
+
+fragment BoardListFragment on BoardList {
+ ...BoardListShared
+}
diff --git a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
new file mode 100644
index 00000000000..6ba6c05d6d9
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
@@ -0,0 +1,15 @@
+fragment BoardListShared on BoardList {
+ id,
+ title,
+ position,
+ listType,
+ collapsed,
+ label {
+ id,
+ title,
+ color,
+ textColor,
+ description,
+ descriptionHtml
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/group_board.query.graphql b/app/assets/javascripts/boards/queries/group_board.query.graphql
new file mode 100644
index 00000000000..cb42cb3f73d
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/group_board.query.graphql
@@ -0,0 +1,13 @@
+#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
+
+query GroupBoard($fullPath: ID!, $boardId: ID!) {
+ group(fullPath: $fullPath) {
+ board(id: $boardId) {
+ lists {
+ nodes {
+ ...BoardListFragment
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/project_board.query.graphql b/app/assets/javascripts/boards/queries/project_board.query.graphql
new file mode 100644
index 00000000000..4620a7e0fd5
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/project_board.query.graphql
@@ -0,0 +1,13 @@
+#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
+
+query ProjectBoard($fullPath: ID!, $boardId: ID!) {
+ project(fullPath: $fullPath) {
+ board(id: $boardId) {
+ lists {
+ nodes {
+ ...BoardListFragment
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index d20b99ecfaa..b8ae6396475 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -80,7 +80,15 @@ const boardsStore = {
this.state.currentPage = page;
},
addList(listObj) {
- const list = new List(listObj);
+ const listType = listObj.listType || listObj.list_type;
+ let { position } = listObj;
+ if (listType === ListType.closed) {
+ position = Infinity;
+ } else if (listType === ListType.backlog) {
+ position = -1;
+ }
+
+ const list = new List({ ...listObj, position });
this.state.lists = sortBy([...this.state.lists, list], 'position');
return list;
},
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 0cc6f3df2d7..0a5538237f9 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -1,6 +1,5 @@
<script>
import { GlDeprecatedButton } from '@gitlab/ui';
-import envrionmentsAppMixin from 'ee_else_ce/environments/mixins/environments_app_mixin';
import Flash from '~/flash';
import { s__ } from '~/locale';
import emptyState from './empty_state.vue';
@@ -22,13 +21,18 @@ export default {
DeleteEnvironmentModal,
},
- mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin],
+ mixins: [CIPaginationMixin, environmentsMixin],
props: {
endpoint: {
type: String,
required: true,
},
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: '',
+ },
canCreateEnvironment: {
type: Boolean,
required: true,
@@ -41,6 +45,11 @@ export default {
type: String,
required: true,
},
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
helpPagePath: {
type: String,
required: true,
@@ -50,17 +59,37 @@ export default {
required: false,
default: '',
},
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
+ eventHub.$on('toggleDeployBoard', this.toggleDeployBoard);
},
beforeDestroy() {
eventHub.$off('toggleFolder');
+ eventHub.$off('toggleDeployBoard');
},
methods: {
+ toggleDeployBoard(model) {
+ this.store.toggleDeployBoard(model.id);
+ },
toggleFolder(folder) {
this.store.toggleFolder(folder);
diff --git a/app/assets/javascripts/environments/mixins/environments_app_mixin.js b/app/assets/javascripts/environments/mixins/environments_app_mixin.js
deleted file mode 100644
index fc805b9235a..00000000000
--- a/app/assets/javascripts/environments/mixins/environments_app_mixin.js
+++ /dev/null
@@ -1,32 +0,0 @@
-export default {
- props: {
- canaryDeploymentFeatureId: {
- type: String,
- required: false,
- default: '',
- },
- showCanaryDeploymentCallout: {
- type: Boolean,
- required: false,
- default: false,
- },
- userCalloutsPath: {
- type: String,
- required: false,
- default: '',
- },
- lockPromotionSvgPath: {
- type: String,
- required: false,
- default: '',
- },
- helpCanaryDeploymentsPath: {
- type: String,
- required: false,
- default: '',
- },
- },
- metods: {
- toggleDeployBoard() {},
- },
-};
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index 6b7c1ff627d..e07ec693948 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -133,6 +133,17 @@ export default class EnvironmentsStore {
}
/**
+ * Toggles deploy board visibility for the provided environment ID.
+ * Currently only works on EE.
+ *
+ * @param {Object} environment
+ * @return {Array}
+ */
+ toggleDeployBoard() {
+ return this.state.environments;
+ }
+
+ /**
* Toggles folder open property for the given folder.
*
* @param {Object} folder
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 6efddec1172..e4edcc2448c 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -12,6 +12,7 @@ import {
visibilityLevelDescriptions,
featureAccessLevelMembers,
featureAccessLevelEveryone,
+ featureAccessLevel,
} from '../constants';
import { toggleHiddenClassBySelector } from '../external';
@@ -127,7 +128,7 @@ export default {
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
pagesAccessLevel: 20,
- metricsAccessLevel: visibilityOptions.PRIVATE,
+ metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
containerRegistryEnabled: true,
lfsEnabled: true,
requestAccessEnabled: true,
@@ -174,6 +175,10 @@ export default {
return options;
},
+ metricsOptionsDropdownEnabled() {
+ return this.featureAccessLevelOptions.length < 2;
+ },
+
repositoryEnabled() {
return this.repositoryAccessLevel > 0;
},
@@ -211,6 +216,7 @@ export default {
this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
+ this.metricsDashboardAccessLevel = Math.min(10, this.metricsDashboardAccessLevel);
if (this.pagesAccessLevel === 20) {
// When from Internal->Private narrow access for only members
this.pagesAccessLevel = 10;
@@ -225,6 +231,7 @@ export default {
if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20;
+ if (this.metricsDashboardAccessLevel === 10) this.metricsDashboardAccessLevel = 20;
this.highlightChanges();
}
},
@@ -485,17 +492,18 @@ export default {
<div class="project-feature-controls">
<div class="select-wrapper">
<select
- v-model="metricsAccessLevel"
+ v-model="metricsDashboardAccessLevel"
+ :disabled="metricsOptionsDropdownEnabled"
name="project[project_feature_attributes][metrics_dashboard_access_level]"
- class="form-control select-control"
+ class="form-control project-repo-select select-control"
>
<option
- :value="visibilityOptions.PRIVATE"
- :disabled="!visibilityAllowed(visibilityOptions.PRIVATE)"
+ :value="featureAccessLevelMembers[0]"
+ :disabled="!visibilityAllowed(visibilityOptions.INTERNAL)"
>{{ featureAccessLevelMembers[1] }}</option
>
<option
- :value="visibilityOptions.PUBLIC"
+ :value="featureAccessLevelEveryone[0]"
:disabled="!visibilityAllowed(visibilityOptions.PUBLIC)"
>{{ featureAccessLevelEveryone[1] }}</option
>
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 320bd4adaaa..7f50c50145b 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -434,6 +434,7 @@ img.emoji {
.append-bottom-20 { margin-bottom: 20px; }
.append-bottom-default { margin-bottom: $gl-padding; }
.prepend-bottom-32 { margin-bottom: 32px; }
+.ml-10 { margin-left: 4.5rem; }
.inline { display: inline-block; }
.center { text-align: center; }
.block { display: block; }
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 91bba1eb617..a1bbcf34f69 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -26,7 +26,7 @@ class HelpController < ApplicationController
respond_to do |format|
format.any(:markdown, :md, :html) do
- # Note: We are purposefully NOT using `Rails.root.join`
+ # Note: We are purposefully NOT using `Rails.root.join` because of https://gitlab.com/gitlab-org/gitlab/-/issues/216028.
path = File.join(Rails.root, 'doc', "#{@path}.md")
if File.exist?(path)
@@ -42,7 +42,7 @@ class HelpController < ApplicationController
# Allow access to specific media files in the doc folder
format.any(:png, :gif, :jpeg, :mp4, :mp3) do
- # Note: We are purposefully NOT using `Rails.root.join`
+ # Note: We are purposefully NOT using `Rails.root.join` because of https://gitlab.com/gitlab-org/gitlab/-/issues/216028.
path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}")
if File.exist?(path)
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 1fa362eff03..70724394ef5 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -4,6 +4,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
include MetricsDashboard
layout 'project'
+
+ before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
+ authorize_metrics_dashboard!
+
+ push_frontend_feature_flag(:prometheus_computed_alerts)
+ push_frontend_feature_flag(:metrics_dashboard_annotations, project)
+ end
before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_stop_environment!, only: [:stop]
@@ -12,10 +19,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :cancel_auto_stop]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
- before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
- push_frontend_feature_flag(:prometheus_computed_alerts)
- push_frontend_feature_flag(:metrics_dashboard_annotations, project)
- end
after_action :expire_etag_cache, only: [:cancel_auto_stop]
def index
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index bb20ea1de49..92fc2d202f3 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -403,6 +403,7 @@ class ProjectsController < Projects::ApplicationController
snippets_access_level
wiki_access_level
pages_access_level
+ metrics_dashboard_access_level
]
]
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index c14bc454bb9..f8c00f3a4cd 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -16,7 +16,8 @@ module BoardsHelper
full_path: full_path,
bulk_update_path: @bulk_issues_path,
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
- recent_boards_endpoint: recent_boards_path
+ recent_boards_endpoint: recent_boards_path,
+ parent: current_board_parent.model_name.param_key
}
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index bd207615e7c..a8cde2b723e 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -589,7 +589,8 @@ module ProjectsHelper
pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled,
- emailsDisabled: project.emails_disabled?
+ emailsDisabled: project.emails_disabled?,
+ metricsDashboardAccessLevel: feature.metrics_dashboard_access_level
}
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 31a3fa12c00..9201cd24d66 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -23,7 +23,7 @@ class ProjectFeature < ApplicationRecord
PUBLIC = 30
FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard).freeze
- PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER }.freeze
+ PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER, metrics_dashboard: Gitlab::Access::REPORTER }.freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
STRING_OPTIONS = HashWithIndifferentAccess.new({
'disabled' => DISABLED,
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 0bb91782dc8..442d07dcb62 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -88,6 +88,11 @@ class ProjectPolicy < BasePolicy
@subject.feature_available?(:forking, @user)
end
+ with_scope :subject
+ condition(:metrics_dashboard_allowed) do
+ feature_available?(:metrics_dashboard)
+ end
+
with_scope :global
condition(:mirror_available, score: 0) do
::Gitlab::CurrentSettings.current_application_settings.mirror_available
@@ -134,6 +139,7 @@ class ProjectPolicy < BasePolicy
wiki
builds
pages
+ metrics_dashboard
]
features.each do |f|
@@ -227,6 +233,7 @@ class ProjectPolicy < BasePolicy
enable :read_prometheus
enable :read_metrics_dashboard_annotation
enable :read_alert_management_alerts
+ enable :metrics_dashboard
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
@@ -249,6 +256,16 @@ class ProjectPolicy < BasePolicy
enable :fork_project
end
+ rule { metrics_dashboard_disabled }.policy do
+ prevent(:metrics_dashboard)
+ end
+
+ rule { can?(:metrics_dashboard) }.policy do
+ enable :read_prometheus
+ enable :read_environment
+ enable :read_deployment
+ end
+
rule { owner | admin | guest | group_member }.prevent :request_access
rule { ~request_access_enabled }.prevent :request_access
@@ -327,6 +344,14 @@ class ProjectPolicy < BasePolicy
enable :admin_terraform_state
end
+ rule { public_project & metrics_dashboard_allowed }.policy do
+ enable :metrics_dashboard
+ end
+
+ rule { internal_access & metrics_dashboard_allowed }.policy do
+ enable :metrics_dashboard
+ end
+
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
rule { can?(:push_code) }.enable :admin_tag
diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb
index c112d75a9b5..514793694ba 100644
--- a/app/services/metrics/dashboard/base_service.rb
+++ b/app/services/metrics/dashboard/base_service.rb
@@ -42,7 +42,7 @@ module Metrics
def allowed?
return false unless params[:environment]
- Ability.allowed?(current_user, :read_environment, project)
+ project&.feature_available?(:metrics_dashboard, current_user)
end
# Returns a new dashboard Hash, supplemented with DB info