From d57e27ef353787dac501d6970c546c9d86dd1f88 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 1 Oct 2020 15:10:05 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- app/assets/javascripts/alert_handler.js | 22 ++++++--- .../incidents/components/incidents_list.vue | 8 +++- app/assets/javascripts/incidents/constants.js | 1 + app/assets/javascripts/lib/utils/common_utils.js | 1 + .../pages/projects/incidents/show/index.js | 7 +++ app/assets/javascripts/repository/index.js | 56 ++++++++++++++++------ .../queries/path_last_commit.query.graphql | 9 ++++ app/assets/javascripts/sidebar/mount_sidebar.js | 6 +-- app/controllers/projects/incidents_controller.rb | 44 +++++++++++++++++ app/controllers/projects/issues_controller.rb | 2 +- .../resolvers/concerns/resolves_merge_requests.rb | 2 +- app/graphql/types/merge_request_type.rb | 4 -- app/helpers/nav_helper.rb | 3 +- app/helpers/startupjs_helper.rb | 13 +++++ app/models/issue.rb | 1 + app/models/merge_request_context_commit.rb | 4 +- .../generate_image_versions_service.rb | 3 ++ app/services/issuable/clone/attributes_rewriter.rb | 2 +- app/views/layouts/_startup_js.html.haml | 22 ++++++++- app/views/projects/ci/builds/_build.html.haml | 10 ++-- app/views/projects/incidents/_new_branch.html.haml | 1 + app/views/projects/incidents/show.html.haml | 1 + app/views/projects/tree/show.html.haml | 2 + 23 files changed, 183 insertions(+), 41 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/incidents/show/index.js create mode 100644 app/helpers/startupjs_helper.rb create mode 100644 app/views/projects/incidents/_new_branch.html.haml create mode 100644 app/views/projects/incidents/show.html.haml (limited to 'app') diff --git a/app/assets/javascripts/alert_handler.js b/app/assets/javascripts/alert_handler.js index 8fffb61d1dd..26b0142f6a2 100644 --- a/app/assets/javascripts/alert_handler.js +++ b/app/assets/javascripts/alert_handler.js @@ -1,13 +1,21 @@ -// This allows us to dismiss alerts that we've migrated from bootstrap -// Note: This ONLY works on alerts that are created on page load +// This allows us to dismiss alerts and banners that we've migrated from bootstrap +// Note: This ONLY works on elements that are created on page load // You can follow this effort in the following epic // https://gitlab.com/groups/gitlab-org/-/epics/4070 export default function initAlertHandler() { - const ALERT_SELECTOR = '.gl-alert'; - const CLOSE_SELECTOR = '.gl-alert-dismiss'; + const DISMISSIBLE_SELECTORS = ['.gl-alert', '.gl-banner']; + const DISMISS_LABEL = '[aria-label="Dismiss"]'; + const DISMISS_CLASS = '.gl-alert-dismiss'; - const dismissAlert = ({ target }) => target.closest(ALERT_SELECTOR).remove(); - const closeButtons = document.querySelectorAll(`${ALERT_SELECTOR} ${CLOSE_SELECTOR}`); - closeButtons.forEach(alert => alert.addEventListener('click', dismissAlert)); + DISMISSIBLE_SELECTORS.forEach(selector => { + const elements = document.querySelectorAll(selector); + elements.forEach(element => { + const button = element.querySelector(DISMISS_LABEL) || element.querySelector(DISMISS_CLASS); + if (!button) { + return; + } + button.addEventListener('click', () => element.remove()); + }); + }); } diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue index 5688d2b5575..78eb828fd19 100644 --- a/app/assets/javascripts/incidents/components/incidents_list.vue +++ b/app/assets/javascripts/incidents/components/incidents_list.vue @@ -19,6 +19,7 @@ import Api from '~/api'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { convertToSnakeCase } from '~/lib/utils/text_utility'; import { s__, __ } from '~/locale'; import { urlParamsToObject } from '~/lib/utils/common_utils'; @@ -40,6 +41,7 @@ import { TH_CREATED_AT_TEST_ID, TH_SEVERITY_TEST_ID, TH_PUBLISHED_TEST_ID, + INCIDENT_DETAILS_PATH, } from '../constants'; const tdClass = @@ -111,6 +113,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], inject: [ 'projectPath', 'newIssuePath', @@ -332,7 +335,10 @@ export default { return Boolean(assignees.nodes?.length); }, navigateToIncidentDetails({ iid }) { - return visitUrl(joinPaths(this.issuePath, iid)); + const path = this.glFeatures.issuesIncidentDetails + ? joinPaths(this.issuePath, INCIDENT_DETAILS_PATH) + : this.issuePath; + return visitUrl(joinPaths(path, iid)); }, handlePageChange(page) { const { startCursor, endCursor } = this.incidents.pageInfo; diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js index 40f8814d9da..797439495e3 100644 --- a/app/assets/javascripts/incidents/constants.js +++ b/app/assets/javascripts/incidents/constants.js @@ -38,3 +38,4 @@ export const DEFAULT_PAGE_SIZE = 20; export const TH_CREATED_AT_TEST_ID = { 'data-testid': 'incident-management-created-at-sort' }; export const TH_SEVERITY_TEST_ID = { 'data-testid': 'incident-management-severity-sort' }; export const TH_PUBLISHED_TEST_ID = { 'data-testid': 'incident-management-published-sort' }; +export const INCIDENT_DETAILS_PATH = 'incident'; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index bcf302cc262..28b624168d5 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -44,6 +44,7 @@ export const checkPageAndAction = (page, action) => { return pagePath === page && actionPath === action; }; +export const isInIncidentPage = () => checkPageAndAction('issues', 'incident'); export const isInIssuePage = () => checkPageAndAction('issues', 'show'); export const isInMRPage = () => checkPageAndAction('merge_requests', 'show'); export const isInEpicPage = () => checkPageAndAction('epics', 'show'); diff --git a/app/assets/javascripts/pages/projects/incidents/show/index.js b/app/assets/javascripts/pages/projects/incidents/show/index.js new file mode 100644 index 00000000000..540b0dd1de8 --- /dev/null +++ b/app/assets/javascripts/pages/projects/incidents/show/index.js @@ -0,0 +1,7 @@ +import initRelatedIssues from '~/related_issues'; +import initShow from '../../issues/show'; + +document.addEventListener('DOMContentLoaded', () => { + initShow(); + initRelatedIssues(); +}); diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 65da8f70b40..1d6749654f5 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -12,12 +12,17 @@ import { setTitle } from './utils/title'; import { updateFormAction } from './utils/dom'; import { convertObjectPropsToCamelCase, parseBoolean } from '../lib/utils/common_utils'; import { __ } from '../locale'; +import PathLastCommitQuery from './queries/path_last_commit.query.graphql'; export default function setupVueRepositoryList() { const el = document.getElementById('js-tree-list'); const { dataset } = el; const { projectPath, projectShortPath, ref, escapedRef, fullName } = dataset; const router = createRouter(projectPath, escapedRef); + const pathRegex = /-\/tree\/[^/]+\/(.+$)/; + const matches = window.location.href.match(pathRegex); + + const currentRoutePath = matches ? matches[1] : ''; apolloProvider.clients.defaultClient.cache.writeData({ data: { @@ -29,6 +34,43 @@ export default function setupVueRepositoryList() { }, }); + const initLastCommitApp = () => + new Vue({ + el: document.getElementById('js-last-commit'), + router, + apolloProvider, + render(h) { + return h(LastCommit, { + props: { + currentPath: this.$route.params.path, + }, + }); + }, + }); + + if (window.gl.startup_graphql_calls) { + const query = window.gl.startup_graphql_calls.find( + call => call.operationName === 'pathLastCommit', + ); + query.fetchCall + .then(res => res.json()) + .then(res => { + apolloProvider.clients.defaultClient.writeQuery({ + query: PathLastCommitQuery, + data: res.data, + variables: { + projectPath, + ref, + path: currentRoutePath, + }, + }); + }) + .catch(() => {}) + .finally(() => initLastCommitApp()); + } else { + initLastCommitApp(); + } + router.afterEach(({ params: { path } }) => { setTitle(path, ref, fullName); }); @@ -77,20 +119,6 @@ export default function setupVueRepositoryList() { }); } - // eslint-disable-next-line no-new - new Vue({ - el: document.getElementById('js-last-commit'), - router, - apolloProvider, - render(h) { - return h(LastCommit, { - props: { - currentPath: this.$route.params.path, - }, - }); - }, - }); - const treeHistoryLinkEl = document.getElementById('js-tree-history-link'); const { historyLink } = treeHistoryLinkEl.dataset; diff --git a/app/assets/javascripts/repository/queries/path_last_commit.query.graphql b/app/assets/javascripts/repository/queries/path_last_commit.query.graphql index 51f3f790a5d..d845f7c6224 100644 --- a/app/assets/javascripts/repository/queries/path_last_commit.query.graphql +++ b/app/assets/javascripts/repository/queries/path_last_commit.query.graphql @@ -1,8 +1,12 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) { project(fullPath: $projectPath) { + __typename repository { + __typename tree(path: $path, ref: $ref) { + __typename lastCommit { + __typename sha title titleHtml @@ -13,15 +17,20 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) { authorName authorGravatar author { + __typename name avatarUrl webPath } signatureHtml pipelines(ref: $ref, first: 1) { + __typename edges { + __typename node { + __typename detailedStatus { + __typename detailsPath icon tooltip diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index ffc7f2c07ba..a25a7b0b2fe 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -14,7 +14,7 @@ import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptio import SidebarSeverity from './components/severity/sidebar_severity.vue'; import Translate from '../vue_shared/translate'; import createDefaultClient from '~/lib/graphql'; -import { isInIssuePage, parseBoolean } from '~/lib/utils/common_utils'; +import { isInIssuePage, isInIncidentPage, parseBoolean } from '~/lib/utils/common_utils'; import createFlash from '~/flash'; import { __ } from '~/locale'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; @@ -51,7 +51,7 @@ function mountAssigneesComponent(mediator) { projectPath: fullPath, field: el.dataset.field, signedIn: el.hasAttribute('data-signed-in'), - issuableType: isInIssuePage() ? 'issue' : 'merge_request', + issuableType: isInIssuePage() || isInIncidentPage() ? 'issue' : 'merge_request', }, }), }); @@ -158,7 +158,7 @@ function mountLockComponent() { const initialData = JSON.parse(dataNode.innerHTML); let importStore; - if (isInIssuePage()) { + if (isInIssuePage() || isInIncidentPage()) { importStore = import(/* webpackChunkName: 'notesStore' */ '~/notes/stores').then( ({ store }) => store, ); diff --git a/app/controllers/projects/incidents_controller.rb b/app/controllers/projects/incidents_controller.rb index c0760f01db2..1c915842e61 100644 --- a/app/controllers/projects/incidents_controller.rb +++ b/app/controllers/projects/incidents_controller.rb @@ -1,8 +1,52 @@ # frozen_string_literal: true class Projects::IncidentsController < Projects::ApplicationController + include IssuableActions + include Gitlab::Utils::StrongMemoize + before_action :authorize_read_issue! + before_action :check_feature_flag, only: [:show] + before_action :load_incident, only: [:show] + + before_action do + push_frontend_feature_flag(:issues_incident_details, @project) + end def index end + + private + + def incident + strong_memoize(:incident) do + incident_finder + .execute + .inc_relations_for_view + .iid_in(params[:id]) + .without_order + .first + end + end + + def load_incident + @issue = incident # needed by rendered view + return render_404 unless can?(current_user, :read_issue, incident) + + @noteable = incident + @note = incident.project.notes.new(noteable: issuable) + end + + alias_method :issuable, :incident + + def incident_finder + IssuesFinder.new(current_user, project_id: @project.id, issue_types: :incident) + end + + def serializer + IssueSerializer.new(current_user: current_user, project: incident.project) + end + + def check_feature_flag + render_404 unless Feature.enabled?(:issues_incident_details, @project) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c5a50349144..319a5183429 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -239,7 +239,7 @@ class Projects::IssuesController < Projects::ApplicationController return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by - @issuable = @noteable = @issue ||= @project.issues.includes(author: :status).where(iid: params[:id]).reorder(nil).take! + @issuable = @noteable = @issue ||= @project.issues.inc_relations_for_view.iid_in(params[:id]).without_order.take! @note = @project.notes.new(noteable: @issuable) return render_404 unless can?(current_user, :read_issue, @issue) diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index 0c01efd4f9a..3d845c8e9df 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -40,7 +40,7 @@ module ResolvesMergeRequests author: [:author], merged_at: [:metrics], commit_count: [:metrics], - approved_by: [:approver_users], + approved_by: [:approved_by_users], milestone: [:milestone], head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }] } diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 56c88491684..573818b1b7a 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -174,10 +174,6 @@ module Types def commit_count object&.metrics&.commits_count end - - def approvers - object.approver_users - end end end Types::MergeRequestType.prepend_if_ee('::EE::Types::MergeRequestType') diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 578c7ae7923..3c757a4ef26 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -55,7 +55,8 @@ module NavHelper current_path?('projects/merge_requests/conflicts#show') || current_path?('issues#show') || current_path?('milestones#show') || - current_path?('issues#designs') + current_path?('issues#designs') || + current_path?('incidents#show') end def admin_monitoring_nav_links diff --git a/app/helpers/startupjs_helper.rb b/app/helpers/startupjs_helper.rb new file mode 100644 index 00000000000..da95cfe03ee --- /dev/null +++ b/app/helpers/startupjs_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module StartupjsHelper + def page_startup_graphql_calls + @graphql_startup_calls + end + + def add_page_startup_graphql_call(query, variables = {}) + @graphql_startup_calls ||= [] + query_str = File.read(File.join(Rails.root, "app/assets/javascripts/#{query}.query.graphql")) + @graphql_startup_calls << { query: query_str, variables: variables } + end +end diff --git a/app/models/issue.rb b/app/models/issue.rb index a49296e711f..ad2a6981b71 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -126,6 +126,7 @@ class Issue < ApplicationRecord scope :counts_by_state, -> { reorder(nil).group(:state_id).count } scope :service_desk, -> { where(author: ::User.support_bot) } + scope :inc_relations_for_view, -> { includes(author: :status) } # An issue can be uniquely identified by project_id and iid # Takes one or more sets of composite IDs, expressed as hash-like records of diff --git a/app/models/merge_request_context_commit.rb b/app/models/merge_request_context_commit.rb index a2982a5dd73..59cc82cfaf5 100644 --- a/app/models/merge_request_context_commit.rb +++ b/app/models/merge_request_context_commit.rb @@ -22,8 +22,8 @@ class MergeRequestContextCommit < ApplicationRecord end # create MergeRequestContextCommit by given commit sha and it's diff file record - def self.bulk_insert(*args) - Gitlab::Database.bulk_insert('merge_request_context_commits', *args) # rubocop:disable Gitlab/BulkInsert + def self.bulk_insert(rows, **args) + Gitlab::Database.bulk_insert('merge_request_context_commits', rows, **args) # rubocop:disable Gitlab/BulkInsert end def to_commit diff --git a/app/services/design_management/generate_image_versions_service.rb b/app/services/design_management/generate_image_versions_service.rb index 213aac164ff..e56d163c461 100644 --- a/app/services/design_management/generate_image_versions_service.rb +++ b/app/services/design_management/generate_image_versions_service.rb @@ -48,6 +48,9 @@ module DesignManagement # Store and process the file action.image_v432x230.store!(raw_file) action.save! + rescue CarrierWave::IntegrityError => e + Gitlab::ErrorTracking.log_exception(e, project_id: project.id, design_id: action.design_id, version_id: action.version_id) + log_error(e.message) rescue CarrierWave::UploadError => e Gitlab::ErrorTracking.track_exception(e, project_id: project.id, design_id: action.design_id, version_id: action.version_id) log_error(e.message) diff --git a/app/services/issuable/clone/attributes_rewriter.rb b/app/services/issuable/clone/attributes_rewriter.rb index b185ab592ff..c84074039ea 100644 --- a/app/services/issuable/clone/attributes_rewriter.rb +++ b/app/services/issuable/clone/attributes_rewriter.rb @@ -56,7 +56,7 @@ module Issuable end def copy_resource_weight_events - return unless original_entity.respond_to?(:resource_weight_events) + return unless both_respond_to?(:resource_weight_events) copy_events(ResourceWeightEvent.table_name, original_entity.resource_weight_events) do |event| event.attributes diff --git a/app/views/layouts/_startup_js.html.haml b/app/views/layouts/_startup_js.html.haml index 33c759b7a7c..f312e00c394 100644 --- a/app/views/layouts/_startup_js.html.haml +++ b/app/views/layouts/_startup_js.html.haml @@ -1,9 +1,11 @@ -- return unless page_startup_api_calls.present? +- return unless page_startup_api_calls.present? || page_startup_graphql_calls.present? = javascript_tag nonce: true do :plain var gl = window.gl || {}; gl.startup_calls = #{page_startup_api_calls.to_json}; + gl.startup_graphql_calls = #{page_startup_graphql_calls.to_json}; + if (gl.startup_calls && window.fetch) { Object.keys(gl.startup_calls).forEach(apiCall => { // fetch won’t send cookies in older browsers, unless you set the credentials init option. @@ -14,3 +16,21 @@ }; }); } + if (gl.startup_graphql_calls && window.fetch) { + const url = `#{api_graphql_url}` + + const opts = { + method: "POST", + headers: { "Content-Type": "application/json", 'X-CSRF-Token': "#{form_authenticity_token}" }, + }; + + gl.startup_graphql_calls = gl.startup_graphql_calls.map(call => ({ + operationName: call.query.match(/^query (.+)\(/)[1], + fetchCall: fetch(url, { + ...opts, + credentials: 'same-origin', + body: JSON.stringify(call) + }) + })) + } + diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 90db2eb3518..b01665daff4 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -101,11 +101,11 @@ = sprite_icon('download') - if can?(current_user, :update_build, job) - if job.active? - = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'btn btn-build' do + = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'btn gl-button btn-build' do = sprite_icon('close') - elsif job.scheduled? .btn-group - .btn.btn-default{ disabled: true } + .btn.gl-button.btn-default{ disabled: true } = sprite_icon('planning') %time.js-remaining-time{ datetime: job.scheduled_at.utc.iso8601 } = duration_in_numbers(job.execute_in) @@ -113,17 +113,17 @@ = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: s_('DelayedJobs|Start now'), - class: 'btn btn-default btn-build has-tooltip', + class: 'btn gl-button btn-default btn-build has-tooltip', data: { confirm: confirmation_message } do = sprite_icon('play') = link_to unschedule_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: s_('DelayedJobs|Unschedule'), - class: 'btn btn-default btn-build has-tooltip' do + class: 'btn gl-button btn-default btn-build has-tooltip' do = sprite_icon('time-out') - elsif allow_retry - if job.playable? && !admin && can?(current_user, :update_build, job) - = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), class: 'btn btn-build' do + = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), class: 'btn gl-button btn-build' do = custom_icon('icon_play') - elsif job.retryable? = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build gl-button btn-icon btn-default' do diff --git a/app/views/projects/incidents/_new_branch.html.haml b/app/views/projects/incidents/_new_branch.html.haml new file mode 100644 index 00000000000..f250fbc4b8b --- /dev/null +++ b/app/views/projects/incidents/_new_branch.html.haml @@ -0,0 +1 @@ += render 'projects/issues/new_branch' diff --git a/app/views/projects/incidents/show.html.haml b/app/views/projects/incidents/show.html.haml new file mode 100644 index 00000000000..b0ddc85df5d --- /dev/null +++ b/app/views/projects/incidents/show.html.haml @@ -0,0 +1 @@ += render template: 'projects/issues/show' diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 3dd12a7b641..3f5904391ad 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,3 +1,5 @@ +- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1] +- add_page_startup_graphql_call('repository/queries/path_last_commit', { projectPath: @project.full_path, ref: current_ref, currentRoutePath: current_route_path }) - breadcrumb_title _("Repository") - @content_class = "limit-container-width" unless fluid_layout -- cgit v1.2.3