diff options
Diffstat (limited to 'app')
35 files changed, 332 insertions, 105 deletions
diff --git a/app/assets/javascripts/lib/utils/axios_startup_calls.js b/app/assets/javascripts/lib/utils/axios_startup_calls.js index cb2e8a76c08..a047cebc8ab 100644 --- a/app/assets/javascripts/lib/utils/axios_startup_calls.js +++ b/app/assets/javascripts/lib/utils/axios_startup_calls.js @@ -34,14 +34,17 @@ const setupAxiosStartupCalls = axios => { }); // eslint-disable-next-line promise/no-nesting - return res.json().then(data => ({ - data, - status: res.status, - statusText: res.statusText, - headers: fetchHeaders, - config: req, - request: req, - })); + return res + .clone() + .json() + .then(data => ({ + data, + status: res.status, + statusText: res.statusText, + headers: fetchHeaders, + config: req, + request: req, + })); }); } diff --git a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue index 54586c67fef..d82b8e0992e 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue @@ -11,6 +11,8 @@ import { GlTooltipDirective, } from '@gitlab/ui'; import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { PANEL_NEW_PAGE } from '../router/constants'; import DuplicateDashboardModal from './duplicate_dashboard_modal.vue'; import CreateDashboardModal from './create_dashboard_modal.vue'; import { s__ } from '~/locale'; @@ -36,6 +38,7 @@ export default { GlTooltip: GlTooltipDirective, TrackEvent: TrackEventDirective, }, + mixins: [glFeatureFlagsMixin()], props: { addingMetricsAvailable: { type: Boolean, @@ -56,6 +59,10 @@ export default { type: String, required: true, }, + isOotbDashboard: { + type: Boolean, + required: true, + }, }, data() { return { customMetricsFormIsValid: null }; @@ -72,15 +79,22 @@ export default { }, isMenuItemEnabled() { return { + addPanel: !this.isOotbDashboard, createDashboard: Boolean(this.projectPath), editDashboard: this.selectedDashboard?.can_edit, }; }, isMenuItemShown() { return { + addPanel: this.glFeatures.metricsDashboardNewPanelPage, duplicateDashboard: this.isOutOfTheBoxDashboard, }; }, + newPanelPageLocation() { + // Retains params/query if any + const { params, query } = this.$route ?? {}; + return { name: PANEL_NEW_PAGE, params, query }; + }, }, methods: { ...mapActions('monitoringDashboard', ['toggleStarredValue']), @@ -117,7 +131,9 @@ export default { starDashboard: s__('Metrics|Star dashboard'), unstarDashboard: s__('Metrics|Unstar dashboard'), addMetric: s__('Metrics|Add metric'), - editDashboardInfo: s__('Metrics|Duplicate this dashboard to edit dashboard YAML'), + addPanel: s__('Metrics|Add panel'), + addPanelInfo: s__('Metrics|Duplicate this dashboard to add panel or edit dashboard YAML.'), + editDashboardInfo: s__('Metrics|Duplicate this dashboard to add panel or edit dashboard YAML.'), editDashboard: s__('Metrics|Edit dashboard YAML'), createDashboard: s__('Metrics|Create new dashboard'), }, @@ -176,6 +192,32 @@ export default { </gl-modal> </template> + <template v-if="isMenuItemShown.addPanel"> + <gl-new-dropdown-item + v-if="isMenuItemEnabled.addPanel" + data-testid="add-panel-item-enabled" + :to="newPanelPageLocation" + > + {{ $options.i18n.addPanel }} + </gl-new-dropdown-item> + + <!-- + wrapper for tooltip as button can be `disabled` + https://bootstrap-vue.org/docs/components/tooltip#disabled-elements + --> + <div v-else v-gl-tooltip :title="$options.i18n.addPanelInfo"> + <gl-new-dropdown-item + :alt="$options.i18n.addPanelInfo" + :to="newPanelPageLocation" + data-testid="add-panel-item-disabled" + disabled + class="gl-cursor-not-allowed" + > + <span class="gl-text-gray-400">{{ $options.i18n.addPanel }}</span> + </gl-new-dropdown-item> + </div> + </template> + <gl-new-dropdown-item v-if="isMenuItemEnabled.editDashboard" :href="selectedDashboard ? selectedDashboard.project_blob_path : null" diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue index 1c921548ce7..1048e72a431 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_header.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue @@ -118,6 +118,9 @@ export default { shouldShowSettingsButton() { return this.canAccessOperationsSettings && this.operationsSettingsPath; }, + isOOTBDashboard() { + return this.selectedDashboard?.out_of_the_box_dashboard ?? false; + }, }, methods: { ...mapActions('monitoringDashboard', ['filterEnvironments']), @@ -266,6 +269,7 @@ export default { :custom-metrics-path="customMetricsPath" :validate-query-path="validateQueryPath" :default-branch="defaultBranch" + :is-ootb-dashboard="isOOTBDashboard" /> </div> diff --git a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql index 27b49860b8a..32b982ff195 100644 --- a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql +++ b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql @@ -5,6 +5,7 @@ query getAnnotations( $startingFrom: Time! ) { project(fullPath: $projectPath) { + id environments(name: $environmentName) { nodes { id diff --git a/app/assets/javascripts/monitoring/queries/getEnvironments.query.graphql b/app/assets/javascripts/monitoring/queries/getEnvironments.query.graphql index 17cd1b2c342..48d0a780fc7 100644 --- a/app/assets/javascripts/monitoring/queries/getEnvironments.query.graphql +++ b/app/assets/javascripts/monitoring/queries/getEnvironments.query.graphql @@ -1,5 +1,6 @@ query getEnvironments($projectPath: ID!, $search: String, $states: [String!]) { project(fullPath: $projectPath) { + id data: environments(search: $search, states: $states) { environments: nodes { name diff --git a/app/assets/javascripts/monitoring/router/routes.js b/app/assets/javascripts/monitoring/router/routes.js index 8092a5b7c0b..cc43fd8622a 100644 --- a/app/assets/javascripts/monitoring/router/routes.js +++ b/app/assets/javascripts/monitoring/router/routes.js @@ -13,12 +13,12 @@ import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from './constants'; export default [ { name: PANEL_NEW_PAGE, - path: '/:dashboard(.*)?/panel/new', + path: '/:dashboard(.+)?/panel/new', component: PanelNewPage, }, { name: DASHBOARD_PAGE, - path: '/:dashboard(.*)?', + path: '/:dashboard(.+)?', component: DashboardPage, }, ]; diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index faa6006945d..262c0b53e79 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -136,6 +136,8 @@ export default { } window.addEventListener('hashchange', this.handleHashChanged); + + eventHub.$on('notesApp.updateIssuableConfidentiality', this.setConfidentiality); }, updated() { this.$nextTick(() => { @@ -146,6 +148,7 @@ export default { beforeDestroy() { this.stopPolling(); window.removeEventListener('hashchange', this.handleHashChanged); + eventHub.$off('notesApp.updateIssuableConfidentiality', this.setConfidentiality); }, methods: { ...mapActions([ @@ -164,6 +167,7 @@ export default { 'startTaskList', 'convertToDiscussion', 'stopPolling', + 'setConfidentiality', ]), discussionIsIndividualNoteAndNotConverted(discussion) { return discussion.individual_note && !this.convertedDisscussionIds.includes(discussion.id); diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index ac57fcf2ea5..3f4da32ec35 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -21,29 +21,6 @@ import Api from '~/api'; let eTagPoll; -export const updateConfidentialityOnIssue = ({ commit, getters }, { confidential, fullPath }) => { - const { iid } = getters.getNoteableData; - - return utils.gqClient - .mutate({ - mutation: updateIssueConfidentialMutation, - variables: { - input: { - projectPath: fullPath, - iid: String(iid), - confidential, - }, - }, - }) - .then(({ data }) => { - const { - issueSetConfidential: { issue }, - } = data; - - commit(types.SET_ISSUE_CONFIDENTIAL, issue.confidential); - }); -}; - export const updateLockedAttribute = ({ commit, getters }, { locked, fullPath }) => { const { iid, targetType } = getters.getNoteableData; @@ -712,3 +689,29 @@ export const updateAssignees = ({ commit }, assignees) => { export const updateDiscussionPosition = ({ commit }, updatedPosition) => { commit(types.UPDATE_DISCUSSION_POSITION, updatedPosition); }; + +export const updateConfidentialityOnIssuable = ( + { getters, commit }, + { confidential, fullPath }, +) => { + const { iid } = getters.getNoteableData; + + return utils.gqClient + .mutate({ + mutation: updateIssueConfidentialMutation, + variables: { + input: { + projectPath: fullPath, + iid: String(iid), + confidential, + }, + }, + }) + .then(({ data }) => { + const { + issueSetConfidential: { issue }, + } = data; + + setConfidentiality({ commit }, issue.confidential); + }); +}; diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js index 0eb6f231839..a245af72d93 100644 --- a/app/assets/javascripts/pages/projects/commit/show/index.js +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -7,23 +7,47 @@ import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import initNotes from '~/init_notes'; import initChangesDropdown from '~/init_changes_dropdown'; -import initDiffNotes from '~/diff_notes/diff_notes_bundle'; import { fetchCommitMergeRequests } from '~/commit_merge_requests'; import '~/sourcegraph/load'; +import { handleLocationHash } from '~/lib/utils/common_utils'; +import axios from '~/lib/utils/axios_utils'; +import syntaxHighlight from '~/syntax_highlight'; +import flash from '~/flash'; +import { __ } from '~/locale'; document.addEventListener('DOMContentLoaded', () => { const hasPerfBar = document.querySelector('.with-performance-bar'); const performanceHeight = hasPerfBar ? 35 : 0; - new Diff(); - new ZenMode(); - new ShortcutsNavigation(); - new MiniPipelineGraph({ - container: '.js-commit-pipeline-graph', - }).bindEvents(); - initNotes(); - initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight + performanceHeight); - // eslint-disable-next-line no-jquery/no-load - $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); - fetchCommitMergeRequests(); - initDiffNotes(); + const filesContainer = $('.js-diffs-batch'); + const initAfterPageLoad = () => { + new Diff(); + new ZenMode(); + new ShortcutsNavigation(); + new MiniPipelineGraph({ + container: '.js-commit-pipeline-graph', + }).bindEvents(); + initNotes(); + initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight + performanceHeight); + // eslint-disable-next-line no-jquery/no-load + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); + fetchCommitMergeRequests(); + }; + + if (filesContainer.length) { + const batchPath = filesContainer.data('diffFilesPath'); + + axios + .get(batchPath) + .then(({ data }) => { + filesContainer.html($(data.html)); + syntaxHighlight(filesContainer); + handleLocationHash(); + initAfterPageLoad(); + }) + .catch(() => { + flash(__('An error occurred while retrieving diff files')); + }); + } else { + initAfterPageLoad(); + } }); diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 721cc6787dc..702df42d655 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -1,4 +1,5 @@ <script> +import { GlButton } from '@gitlab/ui'; import createFlash from '~/flash'; import { __ } from '../../locale'; import FileTable from './table/index.vue'; @@ -8,12 +9,15 @@ import projectPathQuery from '../queries/project_path.query.graphql'; import FilePreview from './preview/index.vue'; import { readmeFile } from '../utils/readme'; +const LIMIT = 1000; const PAGE_SIZE = 100; +export const INITIAL_FETCH_COUNT = LIMIT / PAGE_SIZE; export default { components: { FileTable, FilePreview, + GlButton, }, mixins: [getRefMixin], apollo: { @@ -43,12 +47,19 @@ export default { blobs: [], }, isLoadingFiles: false, + isOverLimit: false, + clickedShowMore: false, + pageSize: PAGE_SIZE, + fetchCounter: 0, }; }, computed: { readme() { return readmeFile(this.entries.blobs); }, + hasShowMore() { + return !this.clickedShowMore && this.fetchCounter === INITIAL_FETCH_COUNT; + }, }, watch: { @@ -76,7 +87,7 @@ export default { ref: this.ref, path: this.path || '/', nextPageCursor: this.nextPageCursor, - pageSize: PAGE_SIZE, + pageSize: this.pageSize, }, }) .then(({ data }) => { @@ -96,7 +107,11 @@ export default { if (pageInfo?.hasNextPage) { this.nextPageCursor = pageInfo.endCursor; - this.fetchFiles(); + this.fetchCounter += 1; + if (this.fetchCounter < INITIAL_FETCH_COUNT || this.clickedShowMore) { + this.fetchFiles(); + this.clickedShowMore = false; + } } }) .catch(error => { @@ -112,6 +127,10 @@ export default { .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo) .find(({ hasNextPage }) => hasNextPage); }, + showMore() { + this.clickedShowMore = true; + this.fetchFiles(); + }, }, }; </script> @@ -124,6 +143,19 @@ export default { :is-loading="isLoadingFiles" :loading-path="loadingPath" /> + <div + v-if="hasShowMore" + class="gl-border-1 gl-border-gray-100 gl-rounded-base gl-border-t-none gl-border-b-solid gl-border-l-solid gl-border-r-solid gl-rounded-top-right-none gl-rounded-top-left-none gl-mt-n1" + > + <gl-button + variant="link" + class="gl-display-flex gl-w-full gl-py-4!" + :loading="isLoadingFiles" + @click="showMore" + > + {{ s__('ProjectFileTree|Show more') }} + </gl-button> + </div> <file-preview v-if="readme" :blob="readme" /> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue index 7dbeb3055c6..c6f7d5e44ad 100644 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -1,6 +1,6 @@ <script> -import { mapState, mapActions } from 'vuex'; -import { __ } from '~/locale'; +import { mapState } from 'vuex'; +import { __, sprintf } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '~/sidebar/event_hub'; @@ -23,9 +23,10 @@ export default { required: true, type: Boolean, }, - service: { - required: true, - type: Object, + issuableType: { + required: false, + type: String, + default: 'issue', }, }, data() { @@ -34,13 +35,25 @@ export default { }; }, computed: { - ...mapState({ confidential: ({ noteableData }) => noteableData.confidential }), + ...mapState({ + confidential: ({ noteableData, confidential }) => { + if (noteableData) { + return noteableData.confidential; + } + return Boolean(confidential); + }, + }), confidentialityIcon() { return this.confidential ? 'eye-slash' : 'eye'; }, tooltipLabel() { return this.confidential ? __('Confidential') : __('Not confidential'); }, + confidentialText() { + return sprintf(__('This %{issuableType} is confidential'), { + issuableType: this.issuableType, + }); + }, }, created() { eventHub.$on('closeConfidentialityForm', this.toggleForm); @@ -49,7 +62,6 @@ export default { eventHub.$off('closeConfidentialityForm', this.toggleForm); }, methods: { - ...mapActions(['setConfidentiality']), toggleForm() { this.edit = !this.edit; }, @@ -86,7 +98,12 @@ export default { > </div> <div class="value sidebar-item-value hide-collapsed"> - <edit-form v-if="edit" :is-confidential="confidential" :full-path="fullPath" /> + <edit-form + v-if="edit" + :confidential="confidential" + :full-path="fullPath" + :issuable-type="issuableType" + /> <div v-if="!confidential" class="no-value sidebar-item-value" data-testid="not-confidential"> <icon :size="16" name="eye" aria-hidden="true" class="sidebar-item-icon inline" /> {{ __('Not confidential') }} @@ -98,7 +115,7 @@ export default { aria-hidden="true" class="sidebar-item-icon inline is-active" /> - {{ __('This issue is confidential') }} + {{ confidentialText }} </div> </div> </div> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue index 9dd4f04acdb..6dad68800f9 100644 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue @@ -1,13 +1,13 @@ <script> import editFormButtons from './edit_form_buttons.vue'; -import { s__ } from '../../../locale'; +import { __, sprintf } from '../../../locale'; export default { components: { editFormButtons, }, props: { - isConfidential: { + confidential: { required: true, type: Boolean, }, @@ -15,16 +15,32 @@ export default { required: true, type: String, }, + issuableType: { + required: true, + type: String, + }, }, computed: { confidentialityOnWarning() { - return s__( - 'confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.', + const accessLevel = __('at least Reporter access'); + + return sprintf( + __( + 'You are going to turn on the confidentiality. This means that only team members with %{accessLevel} are able to see and leave comments on the %{issuableType}.', + ), + { issuableType: this.issuableType, accessLevel: `<strong>${accessLevel}</strong>` }, + false, ); }, confidentialityOffWarning() { - return s__( - 'confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.', + const accessLevel = __('everyone'); + + return sprintf( + __( + 'You are going to turn off the confidentiality. This means %{accessLevel} will be able to see and leave a comment on this %{issuableType}.', + ), + { issuableType: this.issuableType, accessLevel: `<strong>${accessLevel}</strong>` }, + false, ); }, }, @@ -35,9 +51,9 @@ export default { <div class="dropdown show"> <div class="dropdown-menu sidebar-item-warning-message"> <div> - <p v-if="!isConfidential" v-html="confidentialityOnWarning"></p> + <p v-if="!confidential" v-html="confidentialityOnWarning"></p> <p v-else v-html="confidentialityOffWarning"></p> - <edit-form-buttons :full-path="fullPath" /> + <edit-form-buttons :full-path="fullPath" :confidential="confidential" /> </div> </div> </div> diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue index ef3e0ccfa86..be273a616a0 100644 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue @@ -1,7 +1,7 @@ <script> import $ from 'jquery'; import { GlLoadingIcon } from '@gitlab/ui'; -import { mapActions, mapState } from 'vuex'; +import { mapActions } from 'vuex'; import { __ } from '~/locale'; import Flash from '~/flash'; import eventHub from '../../event_hub'; @@ -15,6 +15,10 @@ export default { required: true, type: String, }, + confidential: { + required: true, + type: Boolean, + }, }, data() { return { @@ -22,7 +26,6 @@ export default { }; }, computed: { - ...mapState({ confidential: ({ noteableData }) => noteableData.confidential }), toggleButtonText() { if (this.isLoading) { return __('Applying'); @@ -32,7 +35,7 @@ export default { }, }, methods: { - ...mapActions(['updateConfidentialityOnIssue']), + ...mapActions(['updateConfidentialityOnIssuable']), closeForm() { eventHub.$emit('closeConfidentialityForm'); $(this.$el).trigger('hidden.gl.dropdown'); @@ -41,9 +44,14 @@ export default { this.isLoading = true; const confidential = !this.confidential; - this.updateConfidentialityOnIssue({ confidential, fullPath: this.fullPath }) - .catch(() => { - Flash(__('Something went wrong trying to change the confidentiality of this issue')); + this.updateConfidentialityOnIssuable({ confidential, fullPath: this.fullPath }) + .then(() => { + eventHub.$emit('updateIssuableConfidentiality', confidential); + }) + .catch(err => { + Flash( + err || __('Something went wrong trying to change the confidentiality of this issue'), + ); }) .finally(() => { this.closeForm(); diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 1c88883142d..9f9964ac447 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -165,9 +165,8 @@ vertical-align: top; @media(max-width: map-get($grid-breakpoints, lg)-1) { - display: block; + display: inline-block; width: 100%; - margin: 5px 0; padding: 0; border-left: 0; } diff --git a/app/controllers/groups/releases_controller.rb b/app/controllers/groups/releases_controller.rb new file mode 100644 index 00000000000..500c57a6f3e --- /dev/null +++ b/app/controllers/groups/releases_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Groups + class ReleasesController < Groups::ApplicationController + def index + respond_to do |format| + format.json do + render json: ReleaseSerializer.new.represent(releases) + end + end + end + + private + + def releases + ReleasesFinder + .new(@group, current_user, { include_subgroups: true }) + .execute(preload: false) + .page(params[:page]) + .per(30) + end + end +end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 3f2dc9b09fa..b0c6f3cc6a1 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -15,8 +15,8 @@ class Projects::CommitController < Projects::ApplicationController before_action :authorize_download_code! before_action :authorize_read_pipeline!, only: [:pipelines] before_action :commit - before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines, :merge_requests] - before_action :define_note_vars, only: [:show, :diff_for_path] + before_action :define_commit_vars, only: [:show, :diff_for_path, :diff_files, :pipelines, :merge_requests] + before_action :define_note_vars, only: [:show, :diff_for_path, :diff_files] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] BRANCH_SEARCH_LIMIT = 1000 @@ -41,6 +41,10 @@ class Projects::CommitController < Projects::ApplicationController render_diff_for_path(@commit.diffs(diff_options)) end + def diff_files + render json: { html: view_to_html_string('projects/commit/diff_files', diffs: @diffs, environment: @environment) } + end + # rubocop: disable CodeReuse/ActiveRecord def pipelines @pipelines = @commit.pipelines.order(id: :desc) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 5076172985d..8d498af8de9 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -15,6 +15,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController push_frontend_feature_flag(:prometheus_computed_alerts) push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate) push_frontend_feature_flag(:alert_runbooks) + push_frontend_feature_flag(:metrics_dashboard_new_panel_page) end before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect] before_action :authorize_create_environment!, only: [:new, :create] diff --git a/app/controllers/projects/metrics_dashboard_controller.rb b/app/controllers/projects/metrics_dashboard_controller.rb index ff60d069061..595db86c5fe 100644 --- a/app/controllers/projects/metrics_dashboard_controller.rb +++ b/app/controllers/projects/metrics_dashboard_controller.rb @@ -11,6 +11,7 @@ module Projects push_frontend_feature_flag(:prometheus_computed_alerts) push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate) push_frontend_feature_flag(:alert_runbooks) + push_frontend_feature_flag(:metrics_dashboard_new_panel_page) end def show diff --git a/app/finders/releases_finder.rb b/app/finders/releases_finder.rb index 6a754fdb5a1..e961ad4c0ca 100644 --- a/app/finders/releases_finder.rb +++ b/app/finders/releases_finder.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true class ReleasesFinder - attr_reader :project, :current_user, :params + include Gitlab::Utils::StrongMemoize - def initialize(project, current_user = nil, params = {}) - @project = project + attr_reader :parent, :current_user, :params + + def initialize(parent, current_user = nil, params = {}) + @parent = parent @current_user = current_user @params = params end def execute(preload: true) - return Release.none unless Ability.allowed?(current_user, :read_release, project) + return Release.none if projects.empty? - # See https://gitlab.com/gitlab-org/gitlab/-/issues/211988 - releases = project.releases.where.not(tag: nil) # rubocop:disable CodeReuse/ActiveRecord + releases = get_releases releases = by_tag(releases) releases = releases.preloaded if preload releases.sorted @@ -21,6 +22,34 @@ class ReleasesFinder private + def get_releases + Release.where(project_id: projects).where.not(tag: nil) # rubocop: disable CodeReuse/ActiveRecord + end + + def include_subgroups? + params.fetch(:include_subgroups, false) + end + + def projects + strong_memoize(:projects) do + if parent.is_a?(Project) + Ability.allowed?(current_user, :read_release, parent) ? [parent] : [] + elsif parent.is_a?(Group) + accessible_projects + end + end + end + + def accessible_projects + projects = if include_subgroups? + Project.for_group_and_its_subgroups(parent) + else + parent.projects + end + + projects.select { |project| Ability.allowed?(current_user, :read_release, project) } + end + # rubocop: disable CodeReuse/ActiveRecord def by_tag(releases) return releases unless params[:tag].present? diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 1b9876b9a6a..377aee1ae9e 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -229,6 +229,7 @@ module SearchHelper opts[:data]['group-id'] = @group.id opts[:data]['labels-endpoint'] = group_labels_path(@group) opts[:data]['milestones-endpoint'] = group_milestones_path(@group) + opts[:data]['releases-endpoint'] = group_releases_path(@group) else opts[:data]['labels-endpoint'] = dashboard_labels_path opts[:data]['milestones-endpoint'] = dashboard_milestones_path diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index d82b8ade617..76795561b53 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -17,7 +17,7 @@ module Ci belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :pipeline_artifacts - validates :pipeline, :project, :file_format, presence: true + validates :pipeline, :project, :file_format, :file, presence: true validates :file_store, presence: true, inclusion: { in: FILE_STORE_SUPPORTED } validates :size, presence: true, numericality: { less_than_or_equal_to: FILE_SIZE_LIMIT } validates :file_type, presence: true, uniqueness: { scope: [:pipeline_id] } diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index fe51bdc2486..dd5aedbb760 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -61,6 +61,8 @@ module Issuable end end + has_many :note_authors, -> { distinct }, through: :notes, source: :author + has_many :label_links, as: :target, dependent: :destroy, inverse_of: :target # rubocop:disable Cop/ActiveRecordDependent has_many :labels, through: :label_links has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/serializers/release_entity.rb b/app/serializers/release_entity.rb new file mode 100644 index 00000000000..6777b0f9780 --- /dev/null +++ b/app/serializers/release_entity.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class ReleaseEntity < Grape::Entity + expose :id + expose :tag # see https://gitlab.com/gitlab-org/gitlab/-/issues/36338 +end diff --git a/app/serializers/release_serializer.rb b/app/serializers/release_serializer.rb new file mode 100644 index 00000000000..05a13f71a6f --- /dev/null +++ b/app/serializers/release_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ReleaseSerializer < BaseSerializer + entity ReleaseEntity +end diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb index f7397110c13..311c7f23f16 100644 --- a/app/services/metrics/dashboard/base_service.rb +++ b/app/services/metrics/dashboard/base_service.rb @@ -13,7 +13,6 @@ module Metrics STAGES::MetricEndpointInserter, STAGES::VariableEndpointInserter, STAGES::PanelIdsInserter, - STAGES::Sorter, STAGES::AlertsInserter, STAGES::UrlValidator ].freeze diff --git a/app/services/metrics/dashboard/clone_dashboard_service.rb b/app/services/metrics/dashboard/clone_dashboard_service.rb index b5df82ea92a..d9bd9423a1b 100644 --- a/app/services/metrics/dashboard/clone_dashboard_service.rb +++ b/app/services/metrics/dashboard/clone_dashboard_service.rb @@ -13,8 +13,7 @@ module Metrics SEQUENCES = { ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH => [ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter, - ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter, - ::Gitlab::Metrics::Dashboard::Stages::Sorter + ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter ].freeze, ::Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH => [ @@ -22,8 +21,7 @@ module Metrics ].freeze, ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH => [ - ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter, - ::Gitlab::Metrics::Dashboard::Stages::Sorter + ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter ].freeze }.freeze diff --git a/app/services/metrics/dashboard/cluster_dashboard_service.rb b/app/services/metrics/dashboard/cluster_dashboard_service.rb index 49b9864b66c..4a28e847fdd 100644 --- a/app/services/metrics/dashboard/cluster_dashboard_service.rb +++ b/app/services/metrics/dashboard/cluster_dashboard_service.rb @@ -13,8 +13,7 @@ module Metrics SEQUENCE = [ STAGES::ClusterEndpointInserter, - STAGES::PanelIdsInserter, - STAGES::Sorter + STAGES::PanelIdsInserter ].freeze class << self diff --git a/app/services/metrics/dashboard/custom_metric_embed_service.rb b/app/services/metrics/dashboard/custom_metric_embed_service.rb index 22b592c7aa5..229bd17f5cf 100644 --- a/app/services/metrics/dashboard/custom_metric_embed_service.rb +++ b/app/services/metrics/dashboard/custom_metric_embed_service.rb @@ -75,7 +75,6 @@ module Metrics def panels [{ type: DEFAULT_PANEL_TYPE, - weight: DEFAULT_PANEL_WEIGHT, title: title, y_label: y_label, metrics: metrics.map(&:to_metric_hash) diff --git a/app/services/metrics/dashboard/pod_dashboard_service.rb b/app/services/metrics/dashboard/pod_dashboard_service.rb index 310f78c6a4b..c83f8618460 100644 --- a/app/services/metrics/dashboard/pod_dashboard_service.rb +++ b/app/services/metrics/dashboard/pod_dashboard_service.rb @@ -12,8 +12,7 @@ module Metrics SEQUENCE = [ STAGES::MetricEndpointInserter, STAGES::VariableEndpointInserter, - STAGES::PanelIdsInserter, - STAGES::Sorter + STAGES::PanelIdsInserter ].freeze class << self diff --git a/app/services/metrics/dashboard/predefined_dashboard_service.rb b/app/services/metrics/dashboard/predefined_dashboard_service.rb index 40925a604ff..abdef66c2e0 100644 --- a/app/services/metrics/dashboard/predefined_dashboard_service.rb +++ b/app/services/metrics/dashboard/predefined_dashboard_service.rb @@ -12,8 +12,7 @@ module Metrics SEQUENCE = [ STAGES::MetricEndpointInserter, STAGES::VariableEndpointInserter, - STAGES::PanelIdsInserter, - STAGES::Sorter + STAGES::PanelIdsInserter ].freeze class << self diff --git a/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb b/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb index eb0575c9d23..0651e569d07 100644 --- a/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb +++ b/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb @@ -15,8 +15,7 @@ module Metrics STAGES::CustomMetricsInserter, STAGES::MetricEndpointInserter, STAGES::VariableEndpointInserter, - STAGES::PanelIdsInserter, - STAGES::Sorter + STAGES::PanelIdsInserter ].freeze class << self diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb index 9560df00e4a..29b8f23f40d 100644 --- a/app/services/metrics/dashboard/system_dashboard_service.rb +++ b/app/services/metrics/dashboard/system_dashboard_service.rb @@ -18,7 +18,6 @@ module Metrics STAGES::MetricEndpointInserter, STAGES::VariableEndpointInserter, STAGES::PanelIdsInserter, - STAGES::Sorter, STAGES::AlertsInserter ].freeze diff --git a/app/views/projects/commit/diff_files.html.haml b/app/views/projects/commit/diff_files.html.haml new file mode 100644 index 00000000000..3a473be3840 --- /dev/null +++ b/app/views/projects/commit/diff_files.html.haml @@ -0,0 +1,3 @@ +- diff_files = diffs.diff_files + += render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: 'is-commit' } diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 4b76dde681e..6ba363e6555 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -3,6 +3,7 @@ - can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project) - diff_files = diffs.diff_files - diff_page_context = local_assigns.fetch(:diff_page_context, nil) +- load_diff_files_async = Feature.enabled?(:async_commit_diff_files, @project) && diff_page_context == "is-commit" .content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed .files-changed-inner @@ -26,5 +27,12 @@ - if render_overflow_warning?(diffs) = render 'projects/diffs/warning', diff_files: diffs + .files{ data: { can_create_note: can_create_note } } - = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: diff_page_context } + - if load_diff_files_async + - url = url_for(safe_params.merge(action: 'diff_files')) + .js-diffs-batch{ data: { diff_files_path: url } } + .text-center + %span.spinner.spinner-md + - else + = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: diff_page_context } diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index d17fc5e2eef..20473b47484 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -113,18 +113,17 @@ - if member.can_remove? - if current_user == user - = link_to icon('sign-out', text: _('Leave')), polymorphic_path([:leave, member.source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(member.source) }, - class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}" + = link_to polymorphic_path([:leave, member.source, :members]), method: :delete, data: { confirm: leave_confirmation_message(member.source) }, class: "btn gl-button btn-svg btn-danger align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}" do + = sprite_icon('leave', css_class: 'gl-icon') + = _('Leave') - else %button{ data: { member_path: member_path(member.member), message: remove_member_message(member), is_access_request: member.request?.to_s, qa_selector: 'delete_member_button' }, - class: "js-remove-member-button btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}", + class: "js-remove-member-button btn gl-button btn-danger align-self-center m-0 #{'ml-sm-2 btn-icon' unless force_mobile_view}", title: remove_member_title(member) } %span{ class: ('d-block d-sm-none' unless force_mobile_view) } = _("Delete") - unless force_mobile_view - = icon('trash', class: 'd-none d-sm-block') + = sprite_icon('remove', css_class: 'd-none d-sm-block gl-icon') = render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: member.can_override? - else %span.member-access-text.user-access-role= member.human_access |