diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-24 18:07:55 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-24 18:07:55 +0000 |
commit | 603c7d4cac5e28bc1c75e50c23ed2cbe56f1aafc (patch) | |
tree | 907f5b8ee1b6f5aad396e95e3327a08400b9e8ea /app | |
parent | 120f4aaedc8fe830a3f572491d240d8ee6addefb (diff) | |
download | gitlab-ce-603c7d4cac5e28bc1c75e50c23ed2cbe56f1aafc.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
36 files changed, 412 insertions, 258 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 022d79ecf49..14381f63e4b 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -44,7 +44,6 @@ const Api = { mergeRequestsPipeline: '/api/:version/projects/:id/merge_requests/:merge_request_iid/pipelines', adminStatisticsPath: '/api/:version/application/statistics', pipelineSinglePath: '/api/:version/projects/:id/pipelines/:pipeline_id', - lsifPath: '/api/:version/projects/:id/commits/:commit_id/lsif/info', environmentsPath: '/api/:version/projects/:id/environments', group(groupId, callback) { @@ -474,14 +473,6 @@ const Api = { return axios.get(url); }, - lsifData(projectPath, commitId, paths) { - const url = Api.buildUrl(this.lsifPath) - .replace(':id', encodeURIComponent(projectPath)) - .replace(':commit_id', commitId); - - return axios.get(url, { params: { paths } }); - }, - environments(id) { const url = Api.buildUrl(this.environmentsPath).replace(':id', encodeURIComponent(id)); return axios.get(url); diff --git a/app/assets/javascripts/code_navigation/components/app.vue b/app/assets/javascripts/code_navigation/components/app.vue index 0e5f1f0485d..0e0160b9832 100644 --- a/app/assets/javascripts/code_navigation/components/app.vue +++ b/app/assets/javascripts/code_navigation/components/app.vue @@ -7,7 +7,7 @@ export default { Popover, }, computed: { - ...mapState(['currentDefinition', 'currentDefinitionPosition']), + ...mapState(['currentDefinition', 'currentDefinitionPosition', 'definitionPathPrefix']), }, mounted() { this.blobViewer = document.querySelector('.blob-viewer'); @@ -39,5 +39,6 @@ export default { v-if="currentDefinition" :position="currentDefinitionPosition" :data="currentDefinition" + :definition-path-prefix="definitionPathPrefix" /> </template> diff --git a/app/assets/javascripts/code_navigation/components/popover.vue b/app/assets/javascripts/code_navigation/components/popover.vue index d5bbe430fcd..f216a4c6e6f 100644 --- a/app/assets/javascripts/code_navigation/components/popover.vue +++ b/app/assets/javascripts/code_navigation/components/popover.vue @@ -14,6 +14,10 @@ export default { type: Object, required: true, }, + definitionPathPrefix: { + type: String, + required: true, + }, }, data() { return { @@ -27,6 +31,11 @@ export default { top: `${this.position.y + this.position.height}px`, }; }, + definitionPath() { + return ( + this.data.definition_path && `${this.definitionPathPrefix}/${this.data.definition_path}` + ); + }, }, watch: { position: { @@ -67,8 +76,8 @@ export default { {{ hover.value }} </p> </div> - <div v-if="data.definition_url" class="popover-body"> - <gl-button :href="data.definition_url" target="_blank" class="w-100" variant="default"> + <div v-if="definitionPath" class="popover-body"> + <gl-button :href="definitionPath" target="_blank" class="w-100" variant="default"> {{ __('Go to definition') }} </gl-button> </div> diff --git a/app/assets/javascripts/code_navigation/store/actions.js b/app/assets/javascripts/code_navigation/store/actions.js index 5220b1215b8..9b607023f39 100644 --- a/app/assets/javascripts/code_navigation/store/actions.js +++ b/app/assets/javascripts/code_navigation/store/actions.js @@ -1,4 +1,4 @@ -import api from '~/api'; +import axios from '~/lib/utils/axios_utils'; import * as types from './mutation_types'; import { getCurrentHoverElement, setCurrentHoverElement, addInteractionClass } from '../utils'; @@ -12,11 +12,10 @@ export default { fetchData({ commit, dispatch, state }) { commit(types.REQUEST_DATA); - api - .lsifData(state.projectPath, state.commitId, [state.blobPath]) + axios + .get(state.codeNavUrl) .then(({ data }) => { - const dataForPath = data[state.blobPath]; - const normalizedData = dataForPath.reduce((acc, d) => { + const normalizedData = data.reduce((acc, d) => { if (d.hover) { acc[`${d.start_line}:${d.start_char}`] = d; addInteractionClass(d); diff --git a/app/assets/javascripts/code_navigation/store/mutations.js b/app/assets/javascripts/code_navigation/store/mutations.js index bb833a5adbc..febb7afe2f8 100644 --- a/app/assets/javascripts/code_navigation/store/mutations.js +++ b/app/assets/javascripts/code_navigation/store/mutations.js @@ -1,10 +1,9 @@ import * as types from './mutation_types'; export default { - [types.SET_INITIAL_DATA](state, { projectPath, commitId, blobPath }) { - state.projectPath = projectPath; - state.commitId = commitId; - state.blobPath = blobPath; + [types.SET_INITIAL_DATA](state, { codeNavUrl, definitionPathPrefix }) { + state.codeNavUrl = codeNavUrl; + state.definitionPathPrefix = definitionPathPrefix; }, [types.REQUEST_DATA](state) { state.loading = true; diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue index 31f1dec43ad..76821bcd986 100644 --- a/app/assets/javascripts/ide/components/branches/search_list.vue +++ b/app/assets/javascripts/ide/components/branches/search_list.vue @@ -1,6 +1,6 @@ <script> import { mapActions, mapState } from 'vuex'; -import _ from 'underscore'; +import { debounce } from 'lodash'; import { GlLoadingIcon } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; import Item from './item.vue'; @@ -39,7 +39,7 @@ export default { loadBranches() { this.fetchBranches({ search: this.search }); }, - searchBranches: _.debounce(function debounceSearch() { + searchBranches: debounce(function debounceSearch() { this.loadBranches(); }, 250), focusSearch() { diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue index 2581c3e9928..beff95eb47b 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue @@ -1,5 +1,5 @@ <script> -import _ from 'underscore'; +import { escape as esc } from 'lodash'; import { mapState, mapGetters, createNamespacedHelpers } from 'vuex'; import { sprintf, s__ } from '~/locale'; import consts from '../../stores/modules/commit/constants'; @@ -22,7 +22,7 @@ export default { commitToCurrentBranchText() { return sprintf( s__('IDE|Commit to %{branchName} branch'), - { branchName: `<strong class="monospace">${_.escape(this.currentBranchId)}</strong>` }, + { branchName: `<strong class="monospace">${esc(this.currentBranchId)}</strong>` }, false, ); }, diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue index 7710bfb49ec..504391ffdc7 100644 --- a/app/assets/javascripts/ide/components/jobs/detail.vue +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -1,6 +1,6 @@ <script> import { mapActions, mapState } from 'vuex'; -import _ from 'underscore'; +import { throttle } from 'lodash'; import { __ } from '../../../locale'; import tooltip from '../../../vue_shared/directives/tooltip'; import Icon from '../../../vue_shared/components/icon.vue'; @@ -53,7 +53,7 @@ export default { this.$refs.buildTrace.scrollTo(0, 0); } }, - scrollBuildLog: _.throttle(function buildLogScrollDebounce() { + scrollBuildLog: throttle(function buildLogScrollDebounce() { const { scrollTop } = this.$refs.buildTrace; const { offsetHeight, scrollHeight } = this.$refs.buildTrace; diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue index 5a8face062b..15c08988977 100644 --- a/app/assets/javascripts/ide/components/merge_requests/list.vue +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -1,6 +1,6 @@ <script> import { mapActions, mapState } from 'vuex'; -import _ from 'underscore'; +import { debounce } from 'lodash'; import { GlLoadingIcon } from '@gitlab/ui'; import { __ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; @@ -59,7 +59,7 @@ export default { loadMergeRequests() { this.fetchMergeRequests({ type: this.type, search: this.search }); }, - searchMergeRequests: _.debounce(function debounceSearch() { + searchMergeRequests: debounce(function debounceSearch() { this.loadMergeRequests(); }, 250), onSearchFocus() { diff --git a/app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue b/app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue index 7f65d089148..8adf0122fb4 100644 --- a/app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue +++ b/app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue @@ -1,6 +1,5 @@ <script> import { mapActions, mapState } from 'vuex'; -import _ from 'underscore'; import tooltip from '~/vue_shared/directives/tooltip'; import Icon from '~/vue_shared/components/icon.vue'; import ResizablePanel from '../resizable_panel.vue'; @@ -55,7 +54,7 @@ export default { return this.extensionTabs.filter(tab => tab.show); }, tabViews() { - return _.flatten(this.tabs.map(tab => tab.views)); + return this.tabs.map(tab => tab.views).flat(); }, aliveTabViews() { return this.tabViews.filter(view => this.isAliveView(view.name)); diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 3a63fc32639..343b0b6e90c 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -1,6 +1,6 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; -import _ from 'underscore'; +import { escape as esc } from 'lodash'; import { GlLoadingIcon } from '@gitlab/ui'; import { sprintf, __ } from '../../../locale'; import Icon from '../../../vue_shared/components/icon.vue'; @@ -35,7 +35,7 @@ export default { return sprintf( __('You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}.'), { - linkStart: `<a href="${_.escape(this.currentProject.web_url)}/-/ci/lint">`, + linkStart: `<a href="${esc(this.currentProject.web_url)}/-/ci/lint">`, linkEnd: '</a>', }, false, diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue index aa8d932da6e..86a773499bc 100644 --- a/app/assets/javascripts/ide/components/preview/clientside.vue +++ b/app/assets/javascripts/ide/components/preview/clientside.vue @@ -1,6 +1,6 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; -import _ from 'underscore'; +import { isEmpty } from 'lodash'; import { Manager } from 'smooshpack'; import { listen } from 'codesandbox-api'; import { GlLoadingIcon } from '@gitlab/ui'; @@ -78,7 +78,7 @@ export default { .then(() => this.initPreview()); }, beforeDestroy() { - if (!_.isEmpty(this.manager)) { + if (!isEmpty(this.manager)) { this.manager.listener(); } this.manager = {}; @@ -125,7 +125,7 @@ export default { clearTimeout(this.timeout); this.timeout = setTimeout(() => { - if (_.isEmpty(this.manager)) { + if (isEmpty(this.manager)) { this.initPreview(); return; diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 9e9d9df8f82..55a0dd848c8 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import { mapActions } from 'vuex'; -import _ from 'underscore'; import Translate from '~/vue_shared/translate'; +import { identity } from 'lodash'; import ide from './components/ide.vue'; import store from './stores'; import router from './ide_router'; @@ -31,7 +31,7 @@ Vue.use(Translate); export function initIde(el, options = {}) { if (!el) return null; - const { rootComponent = ide, extendStore = _.identity } = options; + const { rootComponent = ide, extendStore = identity } = options; return new Vue({ el, diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js index 046e562ba2b..234a7f903a1 100644 --- a/app/assets/javascripts/ide/lib/diff/controller.js +++ b/app/assets/javascripts/ide/lib/diff/controller.js @@ -1,5 +1,5 @@ import { Range } from 'monaco-editor'; -import { throttle } from 'underscore'; +import { throttle } from 'lodash'; import DirtyDiffWorker from './diff_worker'; import Disposable from '../common/disposable'; diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js index 3d729463cb4..3aff4d30d81 100644 --- a/app/assets/javascripts/ide/lib/editor.js +++ b/app/assets/javascripts/ide/lib/editor.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import { debounce } from 'lodash'; import { editor as monacoEditor, KeyCode, KeyMod } from 'monaco-editor'; import store from '../stores'; import DecorationsController from './decorations/controller'; @@ -38,7 +38,7 @@ export default class Editor { setupThemes(); - this.debouncedUpdate = _.debounce(() => { + this.debouncedUpdate = debounce(() => { this.updateDimensions(); }, 200); } diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index ddc0925efb9..04cf0ad53d5 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import Vue from 'vue'; -import _ from 'underscore'; +import { escape as esc } from 'lodash'; import { __, sprintf } from '~/locale'; import { visitUrl } from '~/lib/utils/url_utility'; import flash from '~/flash'; @@ -296,7 +296,7 @@ export const getBranchData = ({ commit, state }, { projectId, branchId, force = sprintf( __('Branch not loaded - %{branchId}'), { - branchId: `<strong>${_.escape(projectId)}/${_.escape(branchId)}</strong>`, + branchId: `<strong>${esc(projectId)}/${esc(branchId)}</strong>`, }, false, ), diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index 62084892d13..0b168009847 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import { escape as esc } from 'lodash'; import flash from '~/flash'; import { __, sprintf } from '~/locale'; import service from '../../services'; @@ -73,7 +73,7 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => { text: sprintf( __("Branch %{branchName} was not found in this project's repository."), { - branchName: `<strong>${_.escape(branchId)}</strong>`, + branchName: `<strong>${esc(branchId)}</strong>`, }, false, ), @@ -154,7 +154,7 @@ export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, sprintf( __('An error occurred while getting files for - %{branchId}'), { - branchId: `<strong>${_.escape(projectId)}/${_.escape(branchId)}</strong>`, + branchId: `<strong>${esc(projectId)}/${esc(branchId)}</strong>`, }, false, ), diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js index 828e4ed5eb9..7d48f0adc4c 100644 --- a/app/assets/javascripts/ide/stores/actions/tree.js +++ b/app/assets/javascripts/ide/stores/actions/tree.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import { defer } from 'lodash'; import { __ } from '../../../locale'; import service from '../../services'; import * as types from '../mutation_types'; @@ -71,7 +71,7 @@ export const getFiles = ({ state, commit, dispatch }, payload = {}) => // Defer setting the directory data because this triggers some intense rendering. // The entries is all we need to load the file editor. - _.defer(() => dispatch('setDirectoryData', { projectId, branchId, treeList })); + defer(() => dispatch('setDirectoryData', { projectId, branchId, treeList })); resolve(); }) diff --git a/app/assets/javascripts/logs/components/environment_logs.vue b/app/assets/javascripts/logs/components/environment_logs.vue index 92d4be81c75..70b3af8dc75 100644 --- a/app/assets/javascripts/logs/components/environment_logs.vue +++ b/app/assets/javascripts/logs/components/environment_logs.vue @@ -7,17 +7,15 @@ import { GlAlert, GlDropdown, GlDropdownHeader, - GlDropdownDivider, GlDropdownItem, - GlFormGroup, - GlSearchBoxByClick, GlInfiniteScroll, } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; + +import LogSimpleFilters from './log_simple_filters.vue'; +import LogAdvancedFilters from './log_advanced_filters.vue'; import LogControlButtons from './log_control_buttons.vue'; -import { timeRanges, defaultTimeRange } from '~/vue_shared/constants'; +import { defaultTimeRange } from '~/vue_shared/constants'; import { timeRangeFromUrl } from '~/monitoring/utils'; import { formatDate } from '../utils'; @@ -28,12 +26,10 @@ export default { GlAlert, GlDropdown, GlDropdownHeader, - GlDropdownDivider, GlDropdownItem, - GlFormGroup, - GlSearchBoxByClick, GlInfiniteScroll, - DateTimePicker, + LogSimpleFilters, + LogAdvancedFilters, LogControlButtons, }, filters: { @@ -63,49 +59,22 @@ export default { traceHeight: 600, data() { return { - searchQuery: '', - timeRanges, isElasticStackCalloutDismissed: false, scrollDownButtonDisabled: true, }; }, computed: { ...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods']), - ...mapGetters('environmentLogs', ['trace']), - - timeRangeModel: { - get() { - return this.timeRange.selected; - }, - set(val) { - this.setTimeRange(val); - }, - }, + ...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']), showLoader() { return this.logs.isLoading; }, - advancedFeaturesEnabled() { - const environment = this.environments.options.find( - ({ name }) => name === this.environments.current, - ); - return environment && environment.enable_advanced_logs_querying; - }, - disableAdvancedControls() { - return this.environments.isLoading || !this.advancedFeaturesEnabled; - }, shouldShowElasticStackCallout() { - return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls; - }, - - podDropdownText() { - if (this.pods.current) { - return this.pods.current; - } else if (this.advancedFeaturesEnabled) { - // "All pods" is a valid option when advanced querying is available - return s__('Environments|All pods'); - } - return s__('Environments|No pod selected'); + return ( + !this.isElasticStackCalloutDismissed && + (this.environments.isLoading || !this.showAdvancedFilters) + ); }, }, mounted() { @@ -121,7 +90,6 @@ export default { ...mapActions('environmentLogs', [ 'setInitData', 'setSearch', - 'setTimeRange', 'showPodLogs', 'showEnvironment', 'fetchEnvironments', @@ -131,9 +99,6 @@ export default { isCurrentEnvironment(envName) { return envName === this.environments.current; }, - isCurrentPod(podName) { - return podName === this.pods.current; - }, topReached() { if (!this.logs.isLoading) { this.fetchMoreLogsPrepend(); @@ -167,123 +132,49 @@ export default { </strong> </a> </gl-alert> - <div class="top-bar js-top-bar d-flex"> - <div class="row mx-n1"> - <gl-form-group - id="environments-dropdown-fg" - label-size="sm" - label-for="environments-dropdown" - class="col-3 px-1" + <div class="top-bar d-md-flex border bg-secondary-50 pt-2 pr-1 pb-0 pl-2"> + <div class="flex-grow-0"> + <gl-dropdown + id="environments-dropdown" + :text="environments.current" + :disabled="environments.isLoading" + class="mb-2 gl-h-32 pr-2 d-flex d-md-block js-environments-dropdown" > - <gl-dropdown - id="environments-dropdown" - :text="environments.current" - :disabled="environments.isLoading" - class="d-flex gl-h-32 js-environments-dropdown" - toggle-class="dropdown-menu-toggle" + <gl-dropdown-header class="text-center"> + {{ s__('Environments|Select environment') }} + </gl-dropdown-header> + <gl-dropdown-item + v-for="env in environments.options" + :key="env.id" + @click="showEnvironment(env.name)" > - <gl-dropdown-header class="text-center"> - {{ s__('Environments|Select environment') }} - </gl-dropdown-header> - <gl-dropdown-item - v-for="env in environments.options" - :key="env.id" - @click="showEnvironment(env.name)" - > - <div class="d-flex"> - <gl-icon - :class="{ invisible: !isCurrentEnvironment(env.name) }" - name="status_success_borderless" - /> - <div class="flex-grow-1">{{ env.name }}</div> - </div> - </gl-dropdown-item> - </gl-dropdown> - </gl-form-group> - - <gl-form-group - id="pods-dropdown-fg" - label-size="sm" - label-for="pods-dropdown" - class="col-3 px-1" - > - <gl-dropdown - id="pods-dropdown" - :text="podDropdownText" - :disabled="environments.isLoading" - class="d-flex gl-h-32 js-pods-dropdown" - toggle-class="dropdown-menu-toggle" - > - <gl-dropdown-header class="text-center"> - {{ s__('Environments|Filter by pod') }} - </gl-dropdown-header> - - <template v-if="advancedFeaturesEnabled"> - <gl-dropdown-item key="all-pods" @click="showPodLogs(null)"> - <div class="d-flex"> - <gl-icon - :class="{ invisible: !isCurrentPod(null) }" - name="status_success_borderless" - /> - <div class="flex-grow-1">{{ s__('Environments|All pods') }}</div> - </div> - </gl-dropdown-item> - <gl-dropdown-divider /> - </template> - - <gl-dropdown-item v-if="!pods.options.length" :disabled="true"> - <span class="text-muted"> - {{ s__('Environments|No pods to display') }} - </span> - </gl-dropdown-item> - <gl-dropdown-item - v-for="podName in pods.options" - :key="podName" - class="text-nowrap" - @click="showPodLogs(podName)" - > - <div class="d-flex"> - <gl-icon - :class="{ invisible: !isCurrentPod(podName) }" - name="status_success_borderless" - /> - <div class="flex-grow-1">{{ podName }}</div> - </div> - </gl-dropdown-item> - </gl-dropdown> - </gl-form-group> - <gl-form-group id="search-fg" label-size="sm" label-for="search" class="col-3 px-1"> - <gl-search-box-by-click - v-model.trim="searchQuery" - :disabled="disableAdvancedControls" - :placeholder="s__('Environments|Search')" - class="js-logs-search" - type="search" - autofocus - @submit="setSearch(searchQuery)" - /> - </gl-form-group> - - <gl-form-group - id="dates-fg" - label-size="sm" - label-for="time-window-dropdown" - class="col-3 px-1" - > - <date-time-picker - ref="dateTimePicker" - v-model="timeRangeModel" - class="w-100 gl-h-32" - right - :disabled="disableAdvancedControls" - :options="timeRanges" - /> - </gl-form-group> + <div class="d-flex"> + <gl-icon + :class="{ invisible: !isCurrentEnvironment(env.name) }" + name="status_success_borderless" + /> + <div class="flex-grow-1">{{ env.name }}</div> + </div> + </gl-dropdown-item> + </gl-dropdown> </div> + <log-advanced-filters + v-if="showAdvancedFilters" + ref="log-advanced-filters" + class="d-md-flex flex-grow-1" + :disabled="environments.isLoading" + /> + <log-simple-filters + v-else + ref="log-simple-filters" + class="d-md-flex flex-grow-1" + :disabled="environments.isLoading" + /> + <log-control-buttons ref="scrollButtons" - class="controllers" + class="flex-grow-0 pr-2 mb-2 controllers" :scroll-down-button-disabled="scrollDownButtonDisabled" @refresh="showPodLogs(pods.current)" @scrollDown="scrollDown" diff --git a/app/assets/javascripts/logs/components/log_advanced_filters.vue b/app/assets/javascripts/logs/components/log_advanced_filters.vue new file mode 100644 index 00000000000..dfbd858bf18 --- /dev/null +++ b/app/assets/javascripts/logs/components/log_advanced_filters.vue @@ -0,0 +1,128 @@ +<script> +import { s__ } from '~/locale'; +import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; +import { mapActions, mapState } from 'vuex'; +import { + GlIcon, + GlDropdown, + GlDropdownHeader, + GlDropdownDivider, + GlDropdownItem, + GlSearchBoxByClick, +} from '@gitlab/ui'; +import { timeRanges } from '~/vue_shared/constants'; + +export default { + components: { + GlIcon, + GlDropdown, + GlDropdownHeader, + GlDropdownDivider, + GlDropdownItem, + GlSearchBoxByClick, + DateTimePicker, + }, + props: { + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + timeRanges, + searchQuery: '', + }; + }, + computed: { + ...mapState('environmentLogs', ['timeRange', 'pods']), + + timeRangeModel: { + get() { + return this.timeRange.selected; + }, + set(val) { + this.setTimeRange(val); + }, + }, + + podDropdownText() { + return this.pods.current || s__('Environments|All pods'); + }, + }, + methods: { + ...mapActions('environmentLogs', ['setSearch', 'showPodLogs', 'setTimeRange']), + isCurrentPod(podName) { + return podName === this.pods.current; + }, + }, +}; +</script> +<template> + <div> + <gl-dropdown + ref="podsDropdown" + :text="podDropdownText" + :disabled="disabled" + class="mb-2 gl-h-32 pr-2 d-flex d-md-block flex-grow-0 qa-pods-dropdown" + > + <gl-dropdown-header class="text-center"> + {{ s__('Environments|Filter by pod') }} + </gl-dropdown-header> + + <gl-dropdown-item v-if="!pods.options.length" disabled> + <span ref="noPodsMsg" class="text-muted"> + {{ s__('Environments|No pods to display') }} + </span> + </gl-dropdown-item> + + <template v-else> + <gl-dropdown-item ref="allPodsOption" key="all-pods" @click="showPodLogs(null)"> + <div class="d-flex"> + <gl-icon + :class="{ invisible: pods.current !== null }" + name="status_success_borderless" + /> + <div class="flex-grow-1">{{ s__('Environments|All pods') }}</div> + </div> + </gl-dropdown-item> + <gl-dropdown-divider /> + <gl-dropdown-item + v-for="podName in pods.options" + :key="podName" + class="text-nowrap" + @click="showPodLogs(podName)" + > + <div class="d-flex"> + <gl-icon + :class="{ invisible: !isCurrentPod(podName) }" + name="status_success_borderless" + /> + <div class="flex-grow-1">{{ podName }}</div> + </div> + </gl-dropdown-item> + </template> + </gl-dropdown> + + <gl-search-box-by-click + ref="searchBox" + v-model.trim="searchQuery" + :disabled="disabled" + :placeholder="s__('Environments|Search')" + class="mb-2 pr-2 flex-grow-1" + type="search" + autofocus + @submit="setSearch(searchQuery)" + /> + + <date-time-picker + ref="dateTimePicker" + v-model="timeRangeModel" + :disabled="disabled" + :options="timeRanges" + class="mb-2 gl-h-32 pr-2 d-block date-time-picker-wrapper" + right + /> + </div> +</template> diff --git a/app/assets/javascripts/logs/components/log_simple_filters.vue b/app/assets/javascripts/logs/components/log_simple_filters.vue new file mode 100644 index 00000000000..21fe1695624 --- /dev/null +++ b/app/assets/javascripts/logs/components/log_simple_filters.vue @@ -0,0 +1,73 @@ +<script> +import { s__ } from '~/locale'; +import { mapActions, mapState } from 'vuex'; +import { GlIcon, GlDropdown, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui'; + +export default { + components: { + GlIcon, + GlDropdown, + GlDropdownHeader, + GlDropdownItem, + }, + props: { + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + searchQuery: '', + }; + }, + computed: { + ...mapState('environmentLogs', ['pods']), + + podDropdownText() { + return this.pods.current || s__('Environments|No pod selected'); + }, + }, + methods: { + ...mapActions('environmentLogs', ['showPodLogs']), + isCurrentPod(podName) { + return podName === this.pods.current; + }, + }, +}; +</script> +<template> + <div> + <gl-dropdown + ref="podsDropdown" + :text="podDropdownText" + :disabled="disabled" + class="mb-2 gl-h-32 pr-2 d-flex d-md-block flex-grow-0 qa-pods-dropdown" + > + <gl-dropdown-header class="text-center"> + {{ s__('Environments|Select pod') }} + </gl-dropdown-header> + + <gl-dropdown-item v-if="!pods.options.length" disabled> + <span ref="noPodsMsg" class="text-muted"> + {{ s__('Environments|No pods to display') }} + </span> + </gl-dropdown-item> + <gl-dropdown-item + v-for="podName in pods.options" + :key="podName" + class="text-nowrap" + @click="showPodLogs(podName)" + > + <div class="d-flex"> + <gl-icon + :class="{ invisible: !isCurrentPod(podName) }" + name="status_success_borderless" + /> + <div class="flex-grow-1">{{ podName }}</div> + </div> + </gl-dropdown-item> + </gl-dropdown> + </div> +</template> diff --git a/app/assets/javascripts/logs/stores/getters.js b/app/assets/javascripts/logs/stores/getters.js index 8770306fdd6..d92969c5389 100644 --- a/app/assets/javascripts/logs/stores/getters.js +++ b/app/assets/javascripts/logs/stores/getters.js @@ -5,5 +5,9 @@ const mapTrace = ({ timestamp = null, pod = '', message = '' }) => export const trace = state => state.logs.lines.map(mapTrace).join('\n'); -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; +export const showAdvancedFilters = state => { + const environment = state.environments.options.find( + ({ name }) => name === state.environments.current, + ); + return Boolean(environment?.enable_advanced_logs_querying); +}; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 11687378e20..0ecb38a1ea7 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -379,25 +379,19 @@ } .top-bar { - @include build-trace-top-bar($gl-line-height * 3); - position: relative; - top: 0; - - .dropdown-menu-toggle { - width: 200px; + .date-time-picker-wrapper, + .dropdown-toggle { + @include media-breakpoint-up(md) { + width: 140px; + } - @include media-breakpoint-up(sm) { - width: 300px; + @include media-breakpoint-up(lg) { + width: 160px; } } .controllers { - @include build-controllers(16px, flex-end, true, 2); - } - - .refresh-control { - @include build-controllers(16px, flex-end, true, 0); - margin-left: 2px; + @include build-controllers(16px, flex-end, false, 2); } } diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 00d738a50be..f161d76c623 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -43,6 +43,7 @@ .border-color-blue-300 { border-color: $blue-300; } .border-color-default { border-color: $border-color; } .border-bottom-color-default { border-bottom-color: $border-color; } +.border-radius-default { border-radius: $border-radius-default; } .box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; } .gl-children-ml-sm-3 > * { diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 2f554519632..55817550b4b 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -3,11 +3,10 @@ class Admin::ServicesController < Admin::ApplicationController include ServiceParams - before_action :whitelist_query_limiting, only: [:index] before_action :service, only: [:edit, :update] def index - @services = services_templates + @services = Service.find_or_create_templates end def edit @@ -31,21 +30,8 @@ class Admin::ServicesController < Admin::ApplicationController private # rubocop: disable CodeReuse/ActiveRecord - def services_templates - Service.available_services_names.map do |service_name| - service_template = "#{service_name}_service".camelize.constantize - service_template.where(template: true).first_or_create - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord def service @service ||= Service.find_by(id: params[:id], template: true) end # rubocop: enable CodeReuse/ActiveRecord - - def whitelist_query_limiting - Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42430') - end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 5788fc17a9b..8c8824ae47f 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -208,11 +208,24 @@ class Projects::BlobController < Projects::ApplicationController .last_for_path(@repository, @ref, @path).sha end + def set_code_navigation_build + return if Feature.disabled?(:code_navigation, @project) + + artifact = + Ci::JobArtifact + .for_sha(@blob.commit_id, @project.id) + .for_job_name(Ci::Build::CODE_NAVIGATION_JOB_NAME) + .last + + @code_navigation_build = artifact&.job + end + def show_html environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } environment_params[:find_latest] = true @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) + set_code_navigation_build render 'show' end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index da6a0e38c44..2f5aac892ab 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -26,7 +26,7 @@ module MilestonesHelper end end - def milestones_label_path(opts = {}) + def milestones_issues_path(opts = {}) if @project project_issues_path(@project, opts) elsif @group @@ -283,6 +283,27 @@ module MilestonesHelper can?(current_user, :admin_milestone, @project.group) end end + + def display_issues_count_warning?(milestone) + milestone_visible_issues_count(milestone) > Milestone::DISPLAY_ISSUES_LIMIT + end + + def milestone_issues_count_message(milestone) + total_count = milestone_visible_issues_count(milestone) + limit = Milestone::DISPLAY_ISSUES_LIMIT + link_options = { milestone_title: @milestone.title } + + message = _('Showing %{limit} of %{total_count} issues. ') % { limit: limit, total_count: total_count } + message += link_to(_('View all issues'), milestones_issues_path(link_options)) + + message.html_safe + end + + private + + def milestone_visible_issues_count(milestone) + @milestone_visible_issues_count ||= milestone.issues_visible_to_user(current_user).size + end end MilestonesHelper.prepend_if_ee('EE::MilestonesHelper') diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b555b78cda6..d0ea7439556 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -33,6 +33,8 @@ module Ci scheduler_failure: 2 }.freeze + CODE_NAVIGATION_JOB_NAME = 'code_navigation' + has_one :deployment, as: :deployable, class_name: 'Deployment' has_one :resource, class_name: 'Ci::Resource', inverse_of: :build has_many :trace_sections, class_name: 'Ci::BuildTraceSection' diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index ae57da9c546..ef0701b3874 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -31,7 +31,8 @@ module Ci metrics: 'metrics.txt', lsif: 'lsif.json', dotenv: '.env', - cobertura: 'cobertura-coverage.xml' + cobertura: 'cobertura-coverage.xml', + terraform: 'tfplan.json' }.freeze INTERNAL_TYPES = { @@ -59,7 +60,8 @@ module Ci dast: :raw, license_management: :raw, license_scanning: :raw, - performance: :raw + performance: :raw, + terraform: :raw }.freeze TYPE_AND_FORMAT_PAIRS = INTERNAL_TYPES.merge(REPORT_TYPES).freeze @@ -80,6 +82,7 @@ module Ci scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) } scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) } scope :for_sha, ->(sha, project_id) { joins(job: :pipeline).where(ci_pipelines: { sha: sha, project_id: project_id }) } + scope :for_job_name, ->(name) { joins(:job).where(ci_builds: { name: name }) } scope :with_file_types, -> (file_types) do types = self.file_types.select { |file_type| file_types.include?(file_type) }.values @@ -129,7 +132,8 @@ module Ci network_referee: 14, ## runner referees lsif: 15, # LSIF data for code navigation dotenv: 16, - cobertura: 17 + cobertura: 17, + terraform: 18 # Transformed json } enum file_format: { diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index 6dbb9649b9f..fac058e5a46 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module Milestoneish + DISPLAY_ISSUES_LIMIT = 3000 + def total_issues_count @total_issues_count ||= Milestones::IssuesCountService.new(self).count end @@ -55,7 +57,15 @@ module Milestoneish end def sorted_issues(user) - issues_visible_to_user(user).preload_associated_models.sort_by_attribute('label_priority') + # This method is used on milestone view to filter opened assigned, opened unassigned and closed issues columns. + # We want a limit of DISPLAY_ISSUES_LIMIT for total issues present on all columns. + limited_ids = + issues_visible_to_user(user).sort_by_attribute('label_priority').limit(DISPLAY_ISSUES_LIMIT) + + Issue + .where(id: Issue.select(:id).from(limited_ids)) + .preload_associated_models + .sort_by_attribute('label_priority') end def sorted_merge_requests(user) diff --git a/app/models/issue.rb b/app/models/issue.rb index bdcebb4b942..0f00a78c728 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -70,7 +70,7 @@ class Issue < ApplicationRecord scope :order_closed_date_desc, -> { reorder(closed_at: :desc) } scope :order_created_at_desc, -> { reorder(created_at: :desc) } - scope :preload_associated_models, -> { preload(:labels, project: :namespace) } + scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) } scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) } scope :public_only, -> { where(confidential: false) } diff --git a/app/models/service.rb b/app/models/service.rb index 5782fab3266..138da0c546e 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -46,6 +46,7 @@ class Service < ApplicationRecord scope :active, -> { where(active: true) } scope :without_defaults, -> { where(default: false) } scope :by_type, -> (type) { where(type: type) } + scope :templates, -> { where(template: true, type: available_services_types) } scope :push_hooks, -> { where(push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } @@ -259,14 +260,32 @@ class Service < ApplicationRecord self.category == :issue_tracker end + # Find all service templates; if some of them do not exist, create them + # within a transaction to perform the lowest possible SQL queries. + def self.find_or_create_templates + create_nonexistent_templates + templates + end + + private_class_method def self.create_nonexistent_templates + nonexistent_services = available_services_types - templates.map(&:type) + return if nonexistent_services.empty? + + transaction do + nonexistent_services.each do |service_type| + service_type.constantize.create(template: true) + end + end + end + def self.available_services_names service_names = %w[ alerts asana assembla bamboo - buildkite bugzilla + buildkite campfire custom_issue_tracker discord @@ -278,20 +297,20 @@ class Service < ApplicationRecord hipchat irker jira - mattermost_slash_commands mattermost + mattermost_slash_commands + microsoft_teams packagist pipelines_email pivotaltracker prometheus pushover redmine - youtrack - slack_slash_commands slack + slack_slash_commands teamcity - microsoft_teams unify_circuit + youtrack ] if Rails.env.development? @@ -301,6 +320,10 @@ class Service < ApplicationRecord service_names.sort_by(&:downcase) end + def self.available_services_types + available_services_names.map { |service_name| "#{service_name}_service".camelize } + end + def self.build_from_template(project_id, template) service = template.dup diff --git a/app/uploaders/content_type_whitelist.rb b/app/uploaders/content_type_whitelist.rb index b3975d7e2e0..3210d57b00c 100644 --- a/app/uploaders/content_type_whitelist.rb +++ b/app/uploaders/content_type_whitelist.rb @@ -26,14 +26,14 @@ module ContentTypeWhitelist # Here we override and extend CarrierWave's method that does not parse the # magic headers. def check_content_type_whitelist!(new_file) - new_file.content_type = mime_magic_content_type(new_file.path) + if content_type_whitelist + content_type = mime_magic_content_type(new_file.path) - if content_type_whitelist && !whitelisted_content_type?(new_file.content_type) - message = I18n.translate(:"errors.messages.content_type_whitelist_error", allowed_types: Array(content_type_whitelist).join(", ")) - raise CarrierWave::IntegrityError, message + unless whitelisted_content_type?(content_type) + message = I18n.translate(:"errors.messages.content_type_whitelist_error", allowed_types: Array(content_type_whitelist).join(", ")) + raise CarrierWave::IntegrityError, message + end end - - super(new_file) end def whitelisted_content_type?(content_type) diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 91d1fc06a41..02a327c5a49 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -9,8 +9,9 @@ = render "projects/blob/auxiliary_viewer", blob: blob #blob-content-holder.blob-content-holder - - if native_code_navigation_enabled?(@project) - #js-code-navigation{ data: { commit_id: blob.commit_id, blob_path: blob.path, project_path: @project.full_path } } + - if @code_navigation_build + - code_nav_url = raw_project_job_artifacts_url(@project, @code_navigation_build, path: "lsif/#{blob.path}") + #js-code-navigation{ data: { code_nav_url: "#{code_nav_url}.json", definition_path_prefix: project_blob_path(@project, @ref) } } %article.file-holder = render 'projects/blob/header', blob: blob = render 'projects/blob/content', blob: blob diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml index a8db7f8a556..d7e4f2ed5a0 100644 --- a/app/views/shared/milestones/_issues_tab.html.haml +++ b/app/views/shared/milestones/_issues_tab.html.haml @@ -1,6 +1,11 @@ - args = { show_project_name: local_assigns.fetch(:show_project_name, false), show_full_project_name: local_assigns.fetch(:show_full_project_name, false) } +- if display_issues_count_warning?(@milestone) + .flash-container + .flash-warning#milestone-issue-count-warning + = milestone_issues_count_message(@milestone) + .row.prepend-top-default .col-md-4 = render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true) diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index 4c930b90ce7..6d79b0d31b2 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -3,12 +3,12 @@ - options = { milestone_title: @milestone.title, label_name: label.title } %li.no-border - = render_label(label, tooltip: false, link: milestones_label_path(options)) + = render_label(label, tooltip: false, link: milestones_issues_path(options)) %span.prepend-description-left = markdown_field(label, :description) .float-right.d-none.d-lg-block.d-xl-block - = link_to milestones_label_path(options.merge(state: 'opened')), class: 'btn btn-transparent btn-action' do + = link_to milestones_issues_path(options.merge(state: 'opened')), class: 'btn btn-transparent btn-action' do - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' - = link_to milestones_label_path(options.merge(state: 'closed')), class: 'btn btn-transparent btn-action' do + = link_to milestones_issues_path(options.merge(state: 'closed')), class: 'btn btn-transparent btn-action' do - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue' |