...@@ -70,7 +70,7 @@ export default { ...@@ -70,7 +70,7 @@ export default {
:title="$options.currentBranchPermissionsTooltip" :title="$options.currentBranchPermissionsTooltip"
> >
<span <span
class="ide-radio-label" class="ide-option-label"
data-qa-selector="commit_to_current_branch_radio" data-qa-selector="commit_to_current_branch_radio"
v-html="commitToCurrentBranchText" v-html="commitToCurrentBranchText"
></span> ></span>
... ...
......
<script> <script>
import { createNamespacedHelpers } from 'vuex'; import { createNamespacedHelpers } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
const { const { mapActions: mapCommitActions, mapGetters: mapCommitGetters } = createNamespacedHelpers(
mapState: mapCommitState, 'commit',
mapActions: mapCommitActions, );
mapGetters: mapCommitGetters,
} = createNamespacedHelpers('commit');
export default { export default {
directives: {
GlTooltip: GlTooltipDirective,
},
computed: { computed: {
...mapCommitState(['shouldCreateMR']), ...mapCommitGetters(['shouldHideNewMrOption', 'shouldDisableNewMrOption', 'shouldCreateMR']),
...mapCommitGetters(['shouldHideNewMrOption']), tooltipText() {
if (this.shouldDisableNewMrOption) {
return s__(
'IDE|This option is disabled because you are not allowed to create merge requests in this project.',
);
}
return '';
},
}, },
methods: { methods: {
...mapCommitActions(['toggleShouldCreateMR']), ...mapCommitActions(['toggleShouldCreateMR']),
...@@ -21,14 +32,19 @@ export default { ...@@ -21,14 +32,19 @@ export default {
<template> <template>
<fieldset v-if="!shouldHideNewMrOption"> <fieldset v-if="!shouldHideNewMrOption">
<hr class="my-2" /> <hr class="my-2" />
<label class="mb-0 js-ide-commit-new-mr"> <label
v-gl-tooltip="tooltipText"
class="mb-0 js-ide-commit-new-mr"
:class="{ 'is-disabled': shouldDisableNewMrOption }"
>
<input <input
:disabled="shouldDisableNewMrOption"
:checked="shouldCreateMR" :checked="shouldCreateMR"
type="checkbox" type="checkbox"
data-qa-selector="start_new_mr_checkbox" data-qa-selector="start_new_mr_checkbox"
@change="toggleShouldCreateMR" @change="toggleShouldCreateMR"
/> />
<span class="prepend-left-10"> <span class="prepend-left-10 ide-option-label">
{{ __('Start a new merge request') }} {{ __('Start a new merge request') }}
</span> </span>
</label> </label>
... ...
......
...@@ -67,7 +67,7 @@ export default { ...@@ -67,7 +67,7 @@ export default {
@change="updateCommitAction($event.target.value)" @change="updateCommitAction($event.target.value)"
/> />
<span class="prepend-left-10"> <span class="prepend-left-10">
<span v-if="label" class="ide-radio-label"> {{ label }} </span> <slot v-else></slot> <span v-if="label" class="ide-option-label"> {{ label }} </span> <slot v-else></slot>
</span> </span>
</label> </label>
<div v-if="commitAction === value && showInput" class="ide-commit-new-branch"> <div v-if="commitAction === value && showInput" class="ide-commit-new-branch">
... ...
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters } from 'vuex';
import NavForm from './nav_form.vue'; import NavForm from './nav_form.vue';
import NavDropdownButton from './nav_dropdown_button.vue'; import NavDropdownButton from './nav_dropdown_button.vue';
...@@ -13,6 +14,9 @@ export default { ...@@ -13,6 +14,9 @@ export default {
isVisibleDropdown: false, isVisibleDropdown: false,
}; };
}, },
computed: {
...mapGetters(['canReadMergeRequests']),
},
mounted() { mounted() {
this.addDropdownListeners(); this.addDropdownListeners();
}, },
...@@ -42,7 +46,9 @@ export default { ...@@ -42,7 +46,9 @@ export default {
<template> <template>
<div ref="dropdown" class="btn-group ide-nav-dropdown dropdown"> <div ref="dropdown" class="btn-group ide-nav-dropdown dropdown">
<nav-dropdown-button /> <nav-dropdown-button :show-merge-requests="canReadMergeRequests" />
<div class="dropdown-menu dropdown-menu-left p-0"><nav-form v-if="isVisibleDropdown" /></div> <div class="dropdown-menu dropdown-menu-left p-0">
<nav-form v-if="isVisibleDropdown" :show-merge-requests="canReadMergeRequests" />
</div>
</div> </div>
</template> </template>
...@@ -10,6 +10,13 @@ export default { ...@@ -10,6 +10,13 @@ export default {
Icon, Icon,
DropdownButton, DropdownButton,
}, },
props: {
showMergeRequests: {
type: Boolean,
required: false,
default: true,
},
},
computed: { computed: {
...mapState(['currentBranchId', 'currentMergeRequestId']), ...mapState(['currentBranchId', 'currentMergeRequestId']),
mergeRequestLabel() { mergeRequestLabel() {
...@@ -25,10 +32,10 @@ export default { ...@@ -25,10 +32,10 @@ export default {
<template> <template>
<dropdown-button> <dropdown-button>
<span class="row"> <span class="row">
<span class="col-7 text-truncate"> <span class="col-auto text-truncate" :class="{ 'col-7': showMergeRequests }">
<icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }} <icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }}
</span> </span>
<span class="col-5 pl-0 text-truncate"> <span v-if="showMergeRequests" class="col-5 pl-0 text-truncate">
<icon :size="16" :aria-label="__('Merge Request')" name="merge-request" /> <icon :size="16" :aria-label="__('Merge Request')" name="merge-request" />
{{ mergeRequestLabel }} {{ mergeRequestLabel }}
</span> </span>
... ...
......
...@@ -11,12 +11,19 @@ export default { ...@@ -11,12 +11,19 @@ export default {
BranchesSearchList, BranchesSearchList,
MergeRequestSearchList, MergeRequestSearchList,
}, },
props: {
showMergeRequests: {
type: Boolean,
required: false,
default: true,
},
},
}; };
</script> </script>
<template> <template>
<div class="ide-nav-form p-0"> <div class="ide-nav-form p-0">
<tabs stop-propagation> <tabs v-if="showMergeRequests" stop-propagation>
<tab active> <tab active>
<template slot="title"> <template slot="title">
{{ __('Branches') }} {{ __('Branches') }}
...@@ -30,5 +37,6 @@ export default { ...@@ -30,5 +37,6 @@ export default {
<merge-request-search-list /> <merge-request-search-list />
</tab> </tab>
</tabs> </tabs>
<branches-search-list v-else />
</div> </div>
</template> </template>
...@@ -8,6 +8,9 @@ export const MAX_BODY_LENGTH = 72; ...@@ -8,6 +8,9 @@ export const MAX_BODY_LENGTH = 72;
export const FILE_VIEW_MODE_EDITOR = 'editor'; export const FILE_VIEW_MODE_EDITOR = 'editor';
export const FILE_VIEW_MODE_PREVIEW = 'preview'; export const FILE_VIEW_MODE_PREVIEW = 'preview';
export const PERMISSION_CREATE_MR = 'createMergeRequestIn';
export const PERMISSION_READ_MR = 'readMergeRequest';
export const activityBarViews = { export const activityBarViews = {
edit: 'ide-tree', edit: 'ide-tree',
commit: 'commit-section', commit: 'commit-section',
... ...
......
query getUserPermissions($projectPath: ID!) {
project(fullPath: $projectPath) {
userPermissions {
createMergeRequestIn,
readMergeRequest
}
}
}
import createGqClient, { fetchPolicies } from '~/lib/graphql';
export default createGqClient(
{},
{
fetchPolicy: fetchPolicies.NO_CACHE,
},
);
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility'; import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import Api from '~/api'; import Api from '~/api';
import getUserPermissions from '../queries/getUserPermissions.query.graphql';
import gqClient from './gql';
const fetchApiProjectData = projectPath => Api.project(projectPath).then(({ data }) => data);
const fetchGqlProjectData = projectPath =>
gqClient
.query({
query: getUserPermissions,
variables: { projectPath },
})
.then(({ data }) => data.project);
export default { export default {
getFileData(endpoint) { getFileData(endpoint) {
...@@ -47,7 +59,16 @@ export default { ...@@ -47,7 +59,16 @@ export default {
.then(({ data }) => data); .then(({ data }) => data);
}, },
getProjectData(namespace, project) { getProjectData(namespace, project) {
return Api.project(`${namespace}/${project}`); const projectPath = `${namespace}/${project}`;
return Promise.all([fetchApiProjectData(projectPath), fetchGqlProjectData(projectPath)]).then(
([apiProjectData, gqlProjectData]) => ({
data: {
...apiProjectData,
...gqlProjectData,
},
}),
);
}, },
getProjectMergeRequests(projectId, params = {}) { getProjectMergeRequests(projectId, params = {}) {
return Api.projectMergeRequests(projectId, params); return Api.projectMergeRequests(projectId, params);
... ...
......
...@@ -2,10 +2,17 @@ import flash from '~/flash'; ...@@ -2,10 +2,17 @@ import flash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import service from '../../services'; import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import { activityBarViews } from '../../constants'; import { activityBarViews, PERMISSION_READ_MR } from '../../constants';
export const getMergeRequestsForBranch = ({ commit, state }, { projectId, branchId } = {}) => export const getMergeRequestsForBranch = (
service { commit, state, getters },
{ projectId, branchId } = {},
) => {
if (!getters.findProjectPermissions(projectId)[PERMISSION_READ_MR]) {
return Promise.resolve();
}
return service
.getProjectMergeRequests(`${projectId}`, { .getProjectMergeRequests(`${projectId}`, {
source_branch: branchId, source_branch: branchId,
source_project_id: state.projects[projectId].id, source_project_id: state.projects[projectId].id,
...@@ -36,6 +43,7 @@ export const getMergeRequestsForBranch = ({ commit, state }, { projectId, branch ...@@ -36,6 +43,7 @@ export const getMergeRequestsForBranch = ({ commit, state }, { projectId, branch
); );
throw e; throw e;
}); });
};
export const getMergeRequestData = ( export const getMergeRequestData = (
{ commit, dispatch, state }, { commit, dispatch, state },
... ...
......
import { getChangesCountForFiles, filePathMatches } from './utils'; import { getChangesCountForFiles, filePathMatches } from './utils';
import { activityBarViews, packageJsonPath } from '../constants'; import {
activityBarViews,
packageJsonPath,
PERMISSION_READ_MR,
PERMISSION_CREATE_MR,
} from '../constants';
export const activeFile = state => state.openFiles.find(file => file.active) || null; export const activeFile = state => state.openFiles.find(file => file.active) || null;
...@@ -141,5 +146,14 @@ export const getDiffInfo = (state, getters) => path => { ...@@ -141,5 +146,14 @@ export const getDiffInfo = (state, getters) => path => {
}; };
}; };
export const findProjectPermissions = (state, getters) => projectId =>
getters.findProject(projectId)?.userPermissions || {};
export const canReadMergeRequests = (state, getters) =>
Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_READ_MR]);
export const canCreateMergeRequests = (state, getters) =>
Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_CREATE_MR]);
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -158,7 +158,7 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo ...@@ -158,7 +158,7 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true }); commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true });
}, 5000); }, 5000);
if (state.shouldCreateMR) { if (getters.shouldCreateMR) {
const { currentProject } = rootGetters; const { currentProject } = rootGetters;
const targetBranch = getters.isCreatingNewBranch const targetBranch = getters.isCreatingNewBranch
? rootState.currentBranchId ? rootState.currentBranchId
... ...
......
...@@ -54,5 +54,11 @@ export const shouldHideNewMrOption = (_state, getters, _rootState, rootGetters) ...@@ -54,5 +54,11 @@ export const shouldHideNewMrOption = (_state, getters, _rootState, rootGetters)
(!rootGetters.hasMergeRequest && rootGetters.isOnDefaultBranch)) && (!rootGetters.hasMergeRequest && rootGetters.isOnDefaultBranch)) &&
rootGetters.canPushToBranch; rootGetters.canPushToBranch;
export const shouldDisableNewMrOption = (state, getters, rootState, rootGetters) =>
!rootGetters.canCreateMergeRequests;
export const shouldCreateMR = (state, getters) =>
state.shouldCreateMR && !getters.shouldDisableNewMrOption;
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -3,9 +3,7 @@ import projectSelect from '~/project_select'; ...@@ -3,9 +3,7 @@ import projectSelect from '~/project_select';
import selfMonitor from '~/self_monitor'; import selfMonitor from '~/self_monitor';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
if (gon.features && gon.features.selfMonitoringProject) {
selfMonitor(); selfMonitor();
}
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
projectSelect(); projectSelect();
... ...
......
...@@ -71,7 +71,12 @@ export default { ...@@ -71,7 +71,12 @@ export default {
<template> <template>
<div class="tree-content-holder"> <div class="tree-content-holder">
<div class="table-holder bordered-box"> <div class="table-holder bordered-box">
<table :aria-label="tableCaption" class="table tree-table qa-file-tree" aria-live="polite"> <table
:aria-label="tableCaption"
class="table tree-table"
aria-live="polite"
data-qa-selector="file_tree_table"
>
<table-header v-once /> <table-header v-once />
<tbody> <tbody>
<parent-row <parent-row
... ...
......
...@@ -139,7 +139,13 @@ export default { ...@@ -139,7 +139,13 @@ export default {
class="d-inline-block align-text-bottom fa-fw" class="d-inline-block align-text-bottom fa-fw"
/> />
<i v-else :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> <i v-else :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i>
<component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> <component
:is="linkComponent"
:to="routerLinkTo"
:href="url"
class="str-truncated"
data-qa-selector="file_name_link"
>
{{ fullPath }} {{ fullPath }}
</component> </component>
<!-- eslint-disable-next-line @gitlab/vue-i18n/no-bare-strings --> <!-- eslint-disable-next-line @gitlab/vue-i18n/no-bare-strings -->
... ...
......
...@@ -688,7 +688,7 @@ $ide-commit-header-height: 48px; ...@@ -688,7 +688,7 @@ $ide-commit-header-height: 48px;
font-weight: normal; font-weight: normal;
&.is-disabled { &.is-disabled {
.ide-radio-label { .ide-option-label {
text-decoration: line-through; text-decoration: line-through;
} }
} }
... ...
......
...@@ -11,16 +11,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -11,16 +11,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
before_action :set_application_setting before_action :set_application_setting
before_action :whitelist_query_limiting, only: [:usage_data] before_action :whitelist_query_limiting, only: [:usage_data]
before_action :validate_self_monitoring_feature_flag_enabled, only: [
:create_self_monitoring_project,
:status_create_self_monitoring_project,
:delete_self_monitoring_project,
:status_delete_self_monitoring_project
]
before_action do
push_frontend_feature_flag(:self_monitoring_project)
end
VALID_SETTING_PANELS = %w(general integrations repository VALID_SETTING_PANELS = %w(general integrations repository
ci_cd reporting metrics_and_profiling ci_cd reporting metrics_and_profiling
...@@ -163,10 +153,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -163,10 +153,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
private private
def validate_self_monitoring_feature_flag_enabled
self_monitoring_project_not_implemented unless Feature.enabled?(:self_monitoring_project)
end
def self_monitoring_data def self_monitoring_data
{ {
project_id: @application_setting.self_monitoring_project_id, project_id: @application_setting.self_monitoring_project_id,
...@@ -174,16 +160,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -174,16 +160,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
} }
end end
def self_monitoring_project_not_implemented
render(
status: :not_implemented,
json: {
message: _('Self-monitoring is not enabled on this GitLab server, contact your administrator.'),
documentation_url: help_page_path('administration/monitoring/gitlab_self_monitoring_project/index')
}
)
end
def set_application_setting def set_application_setting
@application_setting = ApplicationSetting.current_without_cache @application_setting = ApplicationSetting.current_without_cache
end end
... ...
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Projects module Projects
class LsifDataService class LsifDataService
attr_reader :file, :project, :path, :commit_id, attr_reader :file, :project, :path, :commit_id,
:docs, :doc_ranges, :ranges, :def_refs :docs, :doc_ranges, :ranges, :def_refs, :hover_refs
CACHE_EXPIRE_IN = 1.hour CACHE_EXPIRE_IN = 1.hour
...@@ -26,7 +26,8 @@ module Projects ...@@ -26,7 +26,8 @@ module Projects
end_line: line_data.last, end_line: line_data.last,
start_char: column_data.first, start_char: column_data.first,
end_char: column_data.last, end_char: column_data.last,
definition_url: definition_url_for(def_refs[ref_id]) definition_url: definition_url_for(def_refs[ref_id]),
hover: highlighted_hover(hover_refs[ref_id])
} }
end end
end end
...@@ -54,6 +55,7 @@ module Projects ...@@ -54,6 +55,7 @@ module Projects
@doc_ranges = data['doc_ranges'] @doc_ranges = data['doc_ranges']
@ranges = data['ranges'] @ranges = data['ranges']
@def_refs = data['def_refs'] @def_refs = data['def_refs']
@hover_refs = data['hover_refs']
end end
def doc_id def doc_id
...@@ -86,5 +88,16 @@ module Projects ...@@ -86,5 +88,16 @@ module Projects
Gitlab::Routing.url_helpers.project_blob_path(project, definition_ref_path, anchor: line_anchor) Gitlab::Routing.url_helpers.project_blob_path(project, definition_ref_path, anchor: line_anchor)
end end
def highlighted_hover(hovers)
hovers&.map do |hover|
# Documentation for a method which is added as comments on top of the method
# is stored as a raw string value in LSIF file
next { value: hover } unless hover.is_a?(Hash)
value = Gitlab::Highlight.highlight(nil, hover['value'], language: hover['language'])
{ language: hover['language'], value: value }
end
end
end end
end end