diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2017-04-06 19:48:40 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2017-04-06 19:48:40 +0800 |
commit | 98a4aca6b5ce503543b0e325212265a365e64d75 (patch) | |
tree | 0c639fc91c9bebc06eecd1e5f0a2764577b97d28 | |
parent | 06b4ea243fef27fa79a201148f07c25df375f57d (diff) | |
parent | d062af91ca10bb0c48136ad9b9204b02d41fdf8c (diff) | |
download | gitlab-ce-98a4aca6b5ce503543b0e325212265a365e64d75.tar.gz |
Merge remote-tracking branch 'upstream/master' into 8998_skip_pending_commits_if_not_head
* upstream/master:
Show CI status as Favicon on Pipelines, Job and MR pages
STL file viewer
Wait for the PDF to be loaded before doing anything
remove unnecessary lease as cron job
Search for opened MRs - include reopened MRs
ProjectsFinder should handle more options
Clearly show who triggered the pipeline in email
Make it possible to preview pipeline success/failed emails
Add remove_concurrent_index to database helper
fix project authorizations migration issue
attempt to fix migration
Revert schema.rb
attempt to fix db failure
Periodically mark projects that are stuck in importing as failed
Fix html structure to prevent tooltip from not hidding
Enable creation of deploy keys with write access via the API
147 files changed, 1525 insertions, 214 deletions
diff --git a/app/assets/images/ci_favicons/icon_status_canceled.ico b/app/assets/images/ci_favicons/icon_status_canceled.ico Binary files differnew file mode 100755 index 00000000000..5a19458f2a2 --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_canceled.ico diff --git a/app/assets/images/ci_favicons/icon_status_created.ico b/app/assets/images/ci_favicons/icon_status_created.ico Binary files differnew file mode 100755 index 00000000000..4dca9640cb3 --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_created.ico diff --git a/app/assets/images/ci_favicons/icon_status_failed.ico b/app/assets/images/ci_favicons/icon_status_failed.ico Binary files differnew file mode 100755 index 00000000000..c961ff9a69b --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_failed.ico diff --git a/app/assets/images/ci_favicons/icon_status_manual.ico b/app/assets/images/ci_favicons/icon_status_manual.ico Binary files differnew file mode 100755 index 00000000000..5fbbc99ea7c --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_manual.ico diff --git a/app/assets/images/ci_favicons/icon_status_not_found.ico b/app/assets/images/ci_favicons/icon_status_not_found.ico Binary files differnew file mode 100755 index 00000000000..21afa9c72e6 --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_not_found.ico diff --git a/app/assets/images/ci_favicons/icon_status_pending.ico b/app/assets/images/ci_favicons/icon_status_pending.ico Binary files differnew file mode 100755 index 00000000000..8be32dab85a --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_pending.ico diff --git a/app/assets/images/ci_favicons/icon_status_running.ico b/app/assets/images/ci_favicons/icon_status_running.ico Binary files differnew file mode 100755 index 00000000000..f328ff1a5ed --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_running.ico diff --git a/app/assets/images/ci_favicons/icon_status_skipped.ico b/app/assets/images/ci_favicons/icon_status_skipped.ico Binary files differnew file mode 100755 index 00000000000..b4394e1b4af --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_skipped.ico diff --git a/app/assets/images/ci_favicons/icon_status_success.ico b/app/assets/images/ci_favicons/icon_status_success.ico Binary files differnew file mode 100755 index 00000000000..4f436c95242 --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_success.ico diff --git a/app/assets/images/ci_favicons/icon_status_warning.ico b/app/assets/images/ci_favicons/icon_status_warning.ico Binary files differnew file mode 100755 index 00000000000..805cc20cdec --- /dev/null +++ b/app/assets/images/ci_favicons/icon_status_warning.ico diff --git a/app/assets/javascripts/blob/3d_viewer/index.js b/app/assets/javascripts/blob/3d_viewer/index.js new file mode 100644 index 00000000000..68d4ddad551 --- /dev/null +++ b/app/assets/javascripts/blob/3d_viewer/index.js @@ -0,0 +1,147 @@ +import * as THREE from 'three/build/three.module'; +import STLLoaderClass from 'three-stl-loader'; +import OrbitControlsClass from 'three-orbit-controls'; +import MeshObject from './mesh_object'; + +const STLLoader = STLLoaderClass(THREE); +const OrbitControls = OrbitControlsClass(THREE); + +export default class Renderer { + constructor(container) { + this.renderWrapper = this.render.bind(this); + this.objects = []; + + this.container = container; + this.width = this.container.offsetWidth; + this.height = 500; + + this.loader = new STLLoader(); + + this.fov = 45; + this.camera = new THREE.PerspectiveCamera( + this.fov, + this.width / this.height, + 1, + 1000, + ); + + this.scene = new THREE.Scene(); + + this.scene.add(this.camera); + + // Setup the viewer + this.setupRenderer(); + this.setupGrid(); + this.setupLight(); + + // Setup OrbitControls + this.controls = new OrbitControls( + this.camera, + this.renderer.domElement, + ); + this.controls.minDistance = 5; + this.controls.maxDistance = 30; + this.controls.enableKeys = false; + + this.loadFile(); + } + + setupRenderer() { + this.renderer = new THREE.WebGLRenderer({ + antialias: true, + }); + + this.renderer.setClearColor(0xFFFFFF); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.renderer.setSize( + this.width, + this.height, + ); + } + + setupLight() { + // Point light illuminates the object + const pointLight = new THREE.PointLight( + 0xFFFFFF, + 2, + 0, + ); + + pointLight.castShadow = true; + + this.camera.add(pointLight); + + // Ambient light illuminates the scene + const ambientLight = new THREE.AmbientLight( + 0xFFFFFF, + 1, + ); + this.scene.add(ambientLight); + } + + setupGrid() { + this.grid = new THREE.GridHelper( + 20, + 20, + 0x000000, + 0x000000, + ); + + this.scene.add(this.grid); + } + + loadFile() { + this.loader.load(this.container.dataset.endpoint, (geo) => { + const obj = new MeshObject(geo); + + this.objects.push(obj); + this.scene.add(obj); + + this.start(); + this.setDefaultCameraPosition(); + }); + } + + start() { + // Empty the container first + this.container.innerHTML = ''; + + // Add to DOM + this.container.appendChild(this.renderer.domElement); + + // Make controls visible + this.container.parentNode.classList.remove('is-stl-loading'); + + this.render(); + } + + render() { + this.renderer.render( + this.scene, + this.camera, + ); + + requestAnimationFrame(this.renderWrapper); + } + + changeObjectMaterials(type) { + this.objects.forEach((obj) => { + obj.changeMaterial(type); + }); + } + + setDefaultCameraPosition() { + const obj = this.objects[0]; + const radius = (obj.geometry.boundingSphere.radius / 1.5); + const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2)); + + this.camera.position.set( + 0, + dist + 1, + dist, + ); + + this.camera.lookAt(this.grid); + this.controls.update(); + } +} diff --git a/app/assets/javascripts/blob/3d_viewer/mesh_object.js b/app/assets/javascripts/blob/3d_viewer/mesh_object.js new file mode 100644 index 00000000000..96758884abf --- /dev/null +++ b/app/assets/javascripts/blob/3d_viewer/mesh_object.js @@ -0,0 +1,49 @@ +import { + Matrix4, + MeshLambertMaterial, + Mesh, +} from 'three/build/three.module'; + +const defaultColor = 0xE24329; +const materials = { + default: new MeshLambertMaterial({ + color: defaultColor, + }), + wireframe: new MeshLambertMaterial({ + color: defaultColor, + wireframe: true, + }), +}; + +export default class MeshObject extends Mesh { + constructor(geo) { + super( + geo, + materials.default, + ); + + this.geometry.computeBoundingSphere(); + + this.rotation.set(-Math.PI / 2, 0, 0); + + if (this.geometry.boundingSphere.radius > 4) { + const scale = 4 / this.geometry.boundingSphere.radius; + + this.geometry.applyMatrix( + new Matrix4().makeScale( + scale, + scale, + scale, + ), + ); + this.geometry.computeBoundingSphere(); + + this.position.x = -this.geometry.boundingSphere.center.x; + this.position.z = this.geometry.boundingSphere.center.y; + } + } + + changeMaterial(type) { + this.material = materials[type]; + } +} diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js index 5b79717d1e1..a74c2db9a61 100644 --- a/app/assets/javascripts/blob/pdf/index.js +++ b/app/assets/javascripts/blob/pdf/index.js @@ -10,7 +10,7 @@ Vue.use(PDFLab, { export default () => { const el = document.getElementById('js-pdf-viewer'); - new Vue({ + return new Vue({ el, data() { return { diff --git a/app/assets/javascripts/blob/stl_viewer.js b/app/assets/javascripts/blob/stl_viewer.js new file mode 100644 index 00000000000..f611c4fe640 --- /dev/null +++ b/app/assets/javascripts/blob/stl_viewer.js @@ -0,0 +1,19 @@ +import Renderer from './3d_viewer'; + +document.addEventListener('DOMContentLoaded', () => { + const viewer = new Renderer(document.getElementById('js-stl-viewer')); + + [].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => { + el.addEventListener('click', (e) => { + const target = e.target; + + e.preventDefault(); + + document.querySelector('.js-material-changer.active').classList.remove('active'); + target.classList.add('active'); + target.blur(); + + viewer.changeObjectMaterials(target.dataset.type); + }); + }); +}); diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 6efd26ccc37..fe54ecffdfe 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -88,6 +88,7 @@ window.Build = (function() { dataType: 'json', success: function(buildData) { $('.js-build-output').html(buildData.trace_html); + gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`); if (window.location.hash === DOWN_BUILD_TRACE) { $("html,body").scrollTop(this.$buildTrace.height()); } diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 80490052389..9c7acc903d1 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -226,9 +226,11 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'projects:pipelines:builds': case 'projects:pipelines:show': const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; + const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`; new gl.Pipelines({ initTabs: true, + pipelineStatusUrl, tabsOptions: { action: controllerAction, defaultAction: 'pipelines', diff --git a/app/assets/javascripts/environments/components/environment_actions.js b/app/assets/javascripts/environments/components/environment_actions.js index 4bb7920bb5e..1418e8d86ee 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js +++ b/app/assets/javascripts/environments/components/environment_actions.js @@ -75,6 +75,7 @@ export default { class="fa fa-spinner fa-spin" aria-hidden="true"/> </span> + </button> <ul class="dropdown-menu dropdown-menu-align-right"> <li v-for="action in actions"> @@ -91,7 +92,6 @@ export default { </button> </li> </ul> - </button> </div> `, }; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 46b80c04e20..e1e6ca25446 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -2,6 +2,8 @@ (function() { (function(w) { var base; + const faviconEl = document.getElementById('favicon'); + const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null; w.gl || (w.gl = {}); (base = w.gl).utils || (base.utils = {}); w.gl.utils.isInGroupsPage = function() { @@ -361,5 +363,34 @@ fn(next, stop); }); }; + + w.gl.utils.setFavicon = (iconName) => { + if (faviconEl && iconName) { + faviconEl.setAttribute('href', `/assets/${iconName}.ico`); + } + }; + + w.gl.utils.resetFavicon = () => { + if (faviconEl) { + faviconEl.setAttribute('href', originalFavicon); + } + }; + + w.gl.utils.setCiStatusFavicon = (pageUrl) => { + $.ajax({ + url: pageUrl, + dataType: 'json', + success: function(data) { + if (data && data.icon) { + gl.utils.setFavicon(`ci_favicons/${data.icon}`); + } else { + gl.utils.resetFavicon(); + } + }, + error: function() { + gl.utils.resetFavicon(); + } + }); + }; })(window); }).call(window); diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js index 0e2af3df071..b0254b17dd2 100644 --- a/app/assets/javascripts/merge_request_widget.js +++ b/app/assets/javascripts/merge_request_widget.js @@ -38,11 +38,13 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; function MergeRequestWidget(opts) { // Initialize MergeRequestWidget behavior // - // check_enable - Boolean, whether to check automerge status - // merge_check_url - String, URL to use to check automerge status + // check_enable - Boolean, whether to check automerge status + // merge_check_url - String, URL to use to check automerge status // ci_status_url - String, URL to use to check CI status + // pipeline_status_url - String, URL to use to get CI status for Favicon // this.opts = opts; + this.opts.pipeline_status_url = `${this.opts.pipeline_status_url}.json`; this.$widgetBody = $('.mr-widget-body'); $('#modal_merge_info').modal({ show: false @@ -159,6 +161,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; _this.status = data.status; _this.hasCi = data.has_ci; _this.updateMergeButton(_this.status, _this.hasCi); + gl.utils.setCiStatusFavicon(_this.opts.pipeline_status_url); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.status !== _this.opts.ci_status || data.sha !== _this.opts.ci_sha || diff --git a/app/assets/javascripts/pipelines.js b/app/assets/javascripts/pipelines.js index 9203abefbbc..4252b615887 100644 --- a/app/assets/javascripts/pipelines.js +++ b/app/assets/javascripts/pipelines.js @@ -9,6 +9,10 @@ require('./lib/utils/bootstrap_linked_tabs'); new global.LinkedTabs(options.tabsOptions); } + if (options.pipelineStatusUrl) { + gl.utils.setCiStatusFavicon(options.pipelineStatusUrl); + } + this.addMarginToBuildColumns(); } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index ffece53a093..ddea1cf540b 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -275,3 +275,9 @@ span.idiff { } } } + +.is-stl-loading { + .stl-controls { + display: none; + } +} diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index daecfc832bf..a1975c0e341 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -3,6 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController before_action :group, only: [:show, :transfer] def index + params[:sort] ||= 'latest_activity_desc' @projects = Project.with_statistics @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb deleted file mode 100644 index 6014112256a..00000000000 --- a/app/controllers/concerns/filter_projects.rb +++ /dev/null @@ -1,17 +0,0 @@ -# == FilterProjects -# -# Controller concern to handle projects filtering -# * by name -# * by archived state -# -module FilterProjects - extend ActiveSupport::Concern - - def filter_projects(projects) - projects = projects.search(params[:name]) if params[:name].present? - projects = projects.non_archived if params[:archived].blank? - projects = projects.personal(current_user) if params[:personal].present? && current_user - - projects - end -end diff --git a/app/controllers/concerns/params_backward_compatibility.rb b/app/controllers/concerns/params_backward_compatibility.rb new file mode 100644 index 00000000000..b0e3d9c7b34 --- /dev/null +++ b/app/controllers/concerns/params_backward_compatibility.rb @@ -0,0 +1,7 @@ +module ParamsBackwardCompatibility + private + + def set_non_archived_param + params[:non_archived] = params[:archived].blank? + end +end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index be00d765f73..5a1efcab1a3 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -1,10 +1,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController - include FilterProjects + include ParamsBackwardCompatibility + + before_action :set_non_archived_param + before_action :default_sorting def index - @projects = load_projects(current_user.authorized_projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) + @projects = load_projects(params.merge(non_public: true)).page(params[:page]) respond_to do |format| format.html { @last_push = current_user.recent_push } @@ -21,10 +22,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def starred - @projects = load_projects(current_user.viewable_starred_projects) - @projects = @projects.includes(:forked_from_project, :tags) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) + @projects = load_projects(params.merge(starred: true)). + includes(:forked_from_project, :tags).page(params[:page]) @last_push = current_user.recent_push @groups = [] @@ -41,14 +40,18 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController private - def load_projects(base_scope) - projects = base_scope.sorted_by_activity.includes(:route, namespace: :route) + def default_sorting + params[:sort] ||= 'latest_activity_desc' + @sort = params[:sort] + end - filter_projects(projects) + def load_projects(finder_params) + ProjectsFinder.new(params: finder_params, current_user: current_user). + execute.includes(:route, namespace: :route) end def load_events - @events = Event.in_projects(load_projects(current_user.authorized_projects)) + @events = Event.in_projects(load_projects(params.merge(non_public: true))) @events = event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 6167f9bd335..8f1870759e4 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,14 +1,12 @@ class Explore::ProjectsController < Explore::ApplicationController - include FilterProjects + include ParamsBackwardCompatibility + + before_action :set_non_archived_param def index - @projects = load_projects - @tags = @projects.tags_on(:tags) - @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? - @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).page(params[:page]) + params[:sort] ||= 'latest_activity_desc' + @sort = params[:sort] + @projects = load_projects.page(params[:page]) respond_to do |format| format.html @@ -21,10 +19,9 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = load_projects(Project.trending) - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) + params[:trending] = true + @sort = params[:sort] + @projects = load_projects.page(params[:page]) respond_to do |format| format.html @@ -37,10 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def starred - @projects = load_projects - @projects = filter_projects(@projects) - @projects = @projects.reorder('star_count DESC') - @projects = @projects.page(params[:page]) + @projects = load_projects.reorder('star_count DESC').page(params[:page]) respond_to do |format| format.html @@ -52,10 +46,10 @@ class Explore::ProjectsController < Explore::ApplicationController end end - protected + private - def load_projects(base_scope = nil) - base_scope ||= ProjectsFinder.new.execute(current_user) - base_scope.includes(:route, namespace: :route) + def load_projects + ProjectsFinder.new(current_user: current_user, params: params). + execute.includes(:route, namespace: :route) end end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 8b69c18d689..29ffaeb19c1 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -27,7 +27,7 @@ class Groups::ApplicationController < ApplicationController end def group_projects - @projects ||= GroupProjectsFinder.new(group).execute(current_user) + @projects ||= GroupProjectsFinder.new(group: group, current_user: current_user).execute end def authorize_admin_group! diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 05f9ee1ee90..78c9f1f7004 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,7 +1,7 @@ class GroupsController < Groups::ApplicationController - include FilterProjects include IssuesAction include MergeRequestsAction + include ParamsBackwardCompatibility respond_to :html @@ -105,15 +105,16 @@ class GroupsController < Groups::ApplicationController protected def setup_projects + set_non_archived_param + params[:sort] ||= 'latest_activity_desc' + @sort = params[:sort] + options = {} options[:only_owned] = true if params[:shared] == '0' options[:only_shared] = true if params[:shared] == '1' - @projects = GroupProjectsFinder.new(group, options).execute(current_user) + @projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user).execute @projects = @projects.includes(:namespace) - @projects = @projects.sorted_by_activity - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]) if params[:name].blank? end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index ba46e2528e6..1eb3800e49d 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -9,7 +9,7 @@ class Projects::ForksController < Projects::ApplicationController def index base_query = project.forks.includes(:creator) - @forks = base_query.merge(ProjectsFinder.new.execute(current_user)) + @forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute) @total_forks_count = base_query.size @private_forks_count = @total_forks_count - @forks.size @public_forks_count = @total_forks_count - @private_forks_count diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2683614d2e8..a452bbba422 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -140,6 +140,6 @@ class UsersController < ApplicationController end def projects_for_current_user - ProjectsFinder.new.execute(current_user) + ProjectsFinder.new(current_user: current_user).execute end end diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb index 3b9a421b118..f043c38c6f9 100644 --- a/app/finders/group_projects_finder.rb +++ b/app/finders/group_projects_finder.rb @@ -1,42 +1,63 @@ -class GroupProjectsFinder < UnionFinder - def initialize(group, options = {}) +# GroupProjectsFinder +# +# Used to filter Projects by set of params +# +# Arguments: +# current_user - which user use +# project_ids_relation: int[] - project ids to use +# group +# options: +# only_owned: boolean +# only_shared: boolean +# params: +# sort: string +# visibility_level: int +# tags: string[] +# personal: boolean +# search: string +# non_archived: boolean +# +class GroupProjectsFinder < ProjectsFinder + attr_reader :group, :options + + def initialize(group:, params: {}, options: {}, current_user: nil, project_ids_relation: nil) + super(params: params, current_user: current_user, project_ids_relation: project_ids_relation) @group = group @options = options end - def execute(current_user = nil) - segments = group_projects(current_user) - find_union(segments, Project) - end - private - def group_projects(current_user) - only_owned = @options.fetch(:only_owned, false) - only_shared = @options.fetch(:only_shared, false) + def init_collection + only_owned = options.fetch(:only_owned, false) + only_shared = options.fetch(:only_shared, false) projects = [] if current_user - if @group.users.include?(current_user) - projects << @group.projects unless only_shared - projects << @group.shared_projects unless only_owned + if group.users.include?(current_user) + projects << group.projects unless only_shared + projects << group.shared_projects unless only_owned else unless only_shared - projects << @group.projects.visible_to_user(current_user) - projects << @group.projects.public_to_user(current_user) + projects << group.projects.visible_to_user(current_user) + projects << group.projects.public_to_user(current_user) end unless only_owned - projects << @group.shared_projects.visible_to_user(current_user) - projects << @group.shared_projects.public_to_user(current_user) + projects << group.shared_projects.visible_to_user(current_user) + projects << group.shared_projects.public_to_user(current_user) end end else - projects << @group.projects.public_only unless only_shared - projects << @group.shared_projects.public_only unless only_owned + projects << group.projects.public_only unless only_shared + projects << group.shared_projects.public_only unless only_owned end projects end + + def union(items) + find_union(items, Project) + end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f7ebb1807d7..4cc42b88a2a 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -116,9 +116,9 @@ class IssuableFinder if current_user && params[:authorized_only].presence && !current_user_related? current_user.authorized_projects elsif group - GroupProjectsFinder.new(group).execute(current_user) + GroupProjectsFinder.new(group: group, current_user: current_user).execute else - projects_finder.execute(current_user, item_project_ids(items)) + ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute end @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) @@ -405,8 +405,4 @@ class IssuableFinder def current_user_related? params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end - - def projects_finder - @projects_finder ||= ProjectsFinder.new - end end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index e52083f86e4..042d792dada 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -83,7 +83,7 @@ class LabelsFinder < UnionFinder def projects return @projects if defined?(@projects) - @projects = skip_authorization ? Project.all : ProjectsFinder.new.execute(current_user) + @projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute @projects = @projects.in_namespace(params[:group_id]) if group? @projects = @projects.where(id: params[:project_ids]) if projects? @projects = @projects.reorder(nil) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 18ec45f300d..f6d8226bf3f 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -1,19 +1,93 @@ +# ProjectsFinder +# +# Used to filter Projects by set of params +# +# Arguments: +# current_user - which user use +# project_ids_relation: int[] - project ids to use +# params: +# trending: boolean +# non_public: boolean +# starred: boolean +# sort: string +# visibility_level: int +# tags: string[] +# personal: boolean +# search: string +# non_archived: boolean +# class ProjectsFinder < UnionFinder - def execute(current_user = nil, project_ids_relation = nil) - segments = all_projects(current_user) - segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation + attr_accessor :params + attr_reader :current_user, :project_ids_relation - find_union(segments, Project).with_route + def initialize(params: {}, current_user: nil, project_ids_relation: nil) + @params = params + @current_user = current_user + @project_ids_relation = project_ids_relation + end + + def execute + items = init_collection + items = by_ids(items) + items = union(items) + items = by_personal(items) + items = by_visibilty_level(items) + items = by_tags(items) + items = by_search(items) + items = by_archived(items) + sort(items) end private - def all_projects(current_user) + def init_collection projects = [] - projects << current_user.authorized_projects if current_user - projects << Project.unscoped.public_to_user(current_user) + if params[:trending].present? + projects << Project.trending + elsif params[:starred].present? && current_user + projects << current_user.viewable_starred_projects + else + projects << current_user.authorized_projects if current_user + projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present? + end projects end + + def by_ids(items) + project_ids_relation ? items.map { |item| item.where(id: project_ids_relation) } : items + end + + def union(items) + find_union(items, Project).with_route + end + + def by_personal(items) + (params[:personal].present? && current_user) ? items.personal(current_user) : items + end + + def by_visibilty_level(items) + params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items + end + + def by_tags(items) + params[:tag].present? ? items.tagged_with(params[:tag]) : items + end + + def by_search(items) + params[:search] ||= params[:name] + params[:search].present? ? items.search(params[:search]) : items + end + + def sort(items) + params[:sort].present? ? items.sort(params[:sort]) : items + end + + def by_archived(projects) + # Back-compatibility with the places where `params[:archived]` can be set explicitly to `false` + params[:non_archived] = !Gitlab::Utils.to_boolean(params[:archived]) if params.key?(:archived) + + params[:non_archived] ? projects.non_archived : projects + end end diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index b7f091f334d..dc13386184e 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -95,7 +95,7 @@ class TodosFinder def projects(items) item_project_ids = items.reorder(nil).select(:project_id) - ProjectsFinder.new.execute(current_user, item_project_ids) + ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute end def type? diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 5c89cbea3fc..3a5d1b97c36 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -25,8 +25,8 @@ module SortingHelper def projects_sort_options_hash options = { sort_value_name => sort_title_name, - sort_value_recently_updated => sort_title_recently_updated, - sort_value_oldest_updated => sort_title_oldest_updated, + sort_value_latest_activity => sort_title_latest_activity, + sort_value_oldest_activity => sort_title_oldest_activity, sort_value_recently_created => sort_title_recently_created, sort_value_oldest_created => sort_title_oldest_created } @@ -78,6 +78,14 @@ module SortingHelper 'Last updated' end + def sort_title_oldest_activity + 'Oldest updated' + end + + def sort_title_latest_activity + 'Last updated' + end + def sort_title_oldest_created 'Oldest created' end @@ -198,6 +206,14 @@ module SortingHelper 'updated_desc' end + def sort_value_oldest_activity + 'latest_activity_asc' + end + + def sort_value_latest_activity + 'latest_activity_desc' + end + def sort_value_oldest_created 'created_asc' end diff --git a/app/models/blob.rb b/app/models/blob.rb index f82126f8e65..801d3442803 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -58,6 +58,10 @@ class Blob < SimpleDelegator binary? && extname.downcase.delete('.') == 'sketch' end + def stl? + extname.downcase.delete('.') == 'stl' + end + def size_within_svg_limits? size <= MAXIMUM_SVG_SIZE end @@ -81,6 +85,8 @@ class Blob < SimpleDelegator 'notebook' elsif sketch? 'sketch' + elsif stl? + 'stl' elsif text? 'text' else diff --git a/app/models/project.rb b/app/models/project.rb index 22f1ea115ea..e289b82e325 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -351,10 +351,15 @@ class Project < ActiveRecord::Base end def sort(method) - if method == 'storage_size_desc' + case method.to_s + when 'storage_size_desc' # storage_size is a joined column so we need to # pass a string to avoid AR adding the table name reorder('project_statistics.storage_size DESC, projects.id DESC') + when 'latest_activity_desc' + reorder(last_activity_at: :desc) + when 'latest_activity_asc' + reorder(last_activity_at: :asc) else order_by(method) end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 4cc21696eb6..cb58c115d54 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -12,7 +12,7 @@ class GroupPolicy < BasePolicy can_read ||= globally_viewable can_read ||= member can_read ||= @user.admin? - can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any? + can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? can! :read_group if can_read # Only group masters and group owners can create new projects @@ -41,6 +41,6 @@ class GroupPolicy < BasePolicy return true if @subject.internal? && !@user.external? return true if @subject.users.include?(@user) - GroupProjectsFinder.new(@subject).execute(@user).any? + GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 5a53b973059..582d5c47b66 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -39,7 +39,7 @@ module MergeRequests private # Returns all origin and fork merge requests from `@project` satisfying passed arguments. - def merge_requests_for(source_branch, mr_states: [:opened]) + def merge_requests_for(source_branch, mr_states: [:opened, :reopened]) MergeRequest .with_state(mr_states) .where(source_branch: source_branch, source_project_id: @project.id) diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index c1549df5ac6..8409b592b72 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -8,7 +8,7 @@ module Search def execute group = Group.find_by(id: params[:group_id]) if params[:group_id].present? - projects = ProjectsFinder.new.execute(current_user) + projects = ProjectsFinder.new(current_user: current_user).execute if group projects = projects.inside_path(group.full_path) diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 1bbd4602ecf..8843d4e7c84 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,4 +1,4 @@ -- publicish_project_count = ProjectsFinder.new.execute(current_user).count +- publicish_project_count = ProjectsFinder.new(current_user: current_user).execute.count .blank-state.blank-state-welcome %h2.blank-state-welcome-title Welcome to GitLab diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index f6d8bb08a64..a611481a0a4 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -23,7 +23,7 @@ %title= page_title(site_name) %meta{ name: "description", content: page_description } - = favicon_link_tag favicon + = favicon_link_tag favicon, id: 'favicon' = stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "print", media: "print" diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 85a1aea3a61..4beb6fcee5d 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -3,8 +3,8 @@ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } %tbody %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } - %img{ alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;line-height:1;" } + %img{ alt: "✖", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } Your pipeline has failed. %tr.spacer @@ -16,7 +16,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" } - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } @@ -26,7 +26,7 @@ = @project.name %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr @@ -37,7 +37,7 @@ = @pipeline.ref %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr @@ -52,13 +52,13 @@ = @merge_request.to_reference .commit{ style: "color:#5c5c5c;font-weight:300;" } = @pipeline.git_commit_message.truncate(50) + - commit = @pipeline.commit %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit Author + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - - commit = @pipeline.commit %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } @@ -68,15 +68,48 @@ - else %span = commit.author_name + - if commit.different_committer? + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Committed by + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + - if commit.committer + %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } + = commit.committer.name + - else + %span + = commit.committer_name + %tr.spacer %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } -- failed = @pipeline.statuses.latest.failed %tr.pre-section - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;" } - Pipeline - %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } - = "\##{@pipeline.id}" + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px 0 5px;text-align:center;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } + Pipeline + %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } + = "\##{@pipeline.id}" + triggered by + - if @pipeline.user + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } + %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } + %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } + = @pipeline.user.name + - else + %td{ style: "font-family:'Menlo','Liberation Mono','Consolas','DejaVu Sans Mono','Ubuntu Mono','Courier New','andale mono','lucida console',monospace;font-size:14px;line-height:1.4;vertical-align:baseline;padding:0 5px;" } + API +- failed = @pipeline.statuses.latest.failed +%tr + %td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" } had = failed.size failed @@ -94,8 +127,8 @@ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;" } - %img{ alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#d22f57;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;line-height:10px" } + %img{ alt: "✖", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" } = build.stage %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index 520a2fc7d68..c1a4ea40cf5 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -14,9 +14,21 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) <% else -%> Commit Author: <%= commit.author_name %> <% end -%> +<% if commit.different_committer? -%> +<% if commit.committer -%> +Committed by: <%= commit.committer.name %> ( <%= user_url(commit.committer) %> ) +<% else -%> +Committed by: <%= commit.committer_name %> +<% end -%> +<% end -%> +<% if @pipeline.user -%> +Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> ) +<% else -%> +Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API +<% end -%> <% failed = @pipeline.statuses.latest.failed -%> -Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. +had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. <% failed.each do |build| -%> <%= render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %> diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 19d4add06f5..9c2e2a599b2 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -16,7 +16,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" } - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } @@ -26,7 +26,7 @@ = @project.name %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr @@ -37,7 +37,7 @@ = @pipeline.ref %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr @@ -52,13 +52,13 @@ = @merge_request.to_reference .commit{ style: "color:#5c5c5c;font-weight:300;" } = @pipeline.git_commit_message.truncate(50) + - commit = @pipeline.commit %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit Author + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - - commit = @pipeline.commit %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } @@ -68,17 +68,50 @@ - else %span = commit.author_name + - if commit.different_committer? + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Committed by + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + - if commit.committer + %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } + = commit.committer.name + - else + %span + = commit.committer_name + %tr.spacer %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } %tr.success-message - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" } - - build_count = @pipeline.statuses.latest.size + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px 0 5px;text-align:center;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } + Pipeline + %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } + = "\##{@pipeline.id}" + triggered by + - if @pipeline.user + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } + %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } + %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } + = @pipeline.user.name + - else + %td{ style: "font-family:'Menlo','Liberation Mono','Consolas','DejaVu Sans Mono','Ubuntu Mono','Courier New','andale mono','lucida console',monospace;font-size:14px;line-height:1.4;vertical-align:baseline;padding:0 5px;" } + API +%tr + %td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" } + - job_count = @pipeline.statuses.latest.size - stage_count = @pipeline.stages_count - Pipeline - %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } - = "\##{@pipeline.id}" successfully completed - #{build_count} #{'build'.pluralize(build_count)} + #{job_count} #{'job'.pluralize(job_count)} in #{stage_count} #{'stage'.pluralize(stage_count)}. diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 0970a3a4e09..ddced2279e1 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -14,7 +14,19 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) <% else -%> Commit Author: <%= commit.author_name %> <% end -%> +<% if commit.different_committer? -%> +<% if commit.committer -%> +Committed by: <%= commit.committer.name %> ( <%= user_url(commit.committer) %> ) +<% else -%> +Committed by: <%= commit.committer_name %> +<% end -%> +<% end -%> <% build_count = @pipeline.statuses.latest.size -%> <% stage_count = @pipeline.stages_count -%> -Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. +<% if @pipeline.user -%> +Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> ) +<% else -%> +Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API +<% end -%> +successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. diff --git a/app/views/projects/blob/_stl.html.haml b/app/views/projects/blob/_stl.html.haml new file mode 100644 index 00000000000..a9332a0eeb6 --- /dev/null +++ b/app/views/projects/blob/_stl.html.haml @@ -0,0 +1,12 @@ +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('stl_viewer') + +.file-content.is-stl-loading + .text-center#js-stl-viewer{ data: { endpoint: namespace_project_raw_path(@project.namespace, @project, @id) } } + = icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading') + .text-center.prepend-top-default.append-bottom-default.stl-controls + .btn-group + %button.btn.btn-default.btn-sm.js-material-changer{ data: { type: 'wireframe' } } + Wireframe + %button.btn.btn-default.btn-sm.active.js-material-changer{ data: { type: 'default' } } + Solid diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 0b0fb7854c2..c716b69b35b 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -12,6 +12,7 @@ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", + pipeline_status_url: "#{pipeline_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index 2d25b8aad62..8939aeb6c3a 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -1,4 +1,4 @@ -- @sort ||= sort_value_recently_updated +- @sort ||= sort_value_latest_activity .dropdown - toggle_text = projects_sort_options_hash[@sort] = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 68a6fd76e70..b33ba2ed7c1 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -2,6 +2,8 @@ class RepositoryImportWorker include Sidekiq::Worker include DedicatedSidekiqQueue + sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_EXPIRATION + attr_accessor :project, :current_user def perform(project_id) @@ -12,7 +14,7 @@ class RepositoryImportWorker import_url: @project.import_url, path: @project.path_with_namespace) - project.update_column(:import_error, nil) + project.update_columns(import_jid: self.jid, import_error: nil) result = Projects::ImportService.new(project, current_user).execute diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb new file mode 100644 index 00000000000..bfc5e667bb6 --- /dev/null +++ b/app/workers/stuck_import_jobs_worker.rb @@ -0,0 +1,37 @@ +class StuckImportJobsWorker + include Sidekiq::Worker + include CronjobQueue + + IMPORT_EXPIRATION = 15.hours.to_i + + def perform + stuck_projects.find_in_batches(batch_size: 500) do |group| + jids = group.map(&:import_jid) + + # Find the jobs that aren't currently running or that exceeded the threshold. + completed_jids = Gitlab::SidekiqStatus.completed_jids(jids) + + if completed_jids.any? + completed_ids = group.select { |project| completed_jids.include?(project.import_jid) }.map(&:id) + + fail_batch!(completed_jids, completed_ids) + end + end + end + + private + + def stuck_projects + Project.select('id, import_jid').with_import_status(:started).where.not(import_jid: nil) + end + + def fail_batch!(completed_jids, completed_ids) + Project.where(id: completed_ids).update_all(import_status: 'failed', import_error: error_message) + + Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_jids.join(', ')}") + end + + def error_message + "Import timed out. Import took longer than #{IMPORT_EXPIRATION} seconds" + end +end diff --git a/changelogs/unreleased/12818-ci-status-as-favicon.yml b/changelogs/unreleased/12818-ci-status-as-favicon.yml new file mode 100644 index 00000000000..70194178d90 --- /dev/null +++ b/changelogs/unreleased/12818-ci-status-as-favicon.yml @@ -0,0 +1,4 @@ +--- +title: Show CI status as Favicon on Pipelines, Job and MR pages +merge_request: 10144 +author: diff --git a/changelogs/unreleased/28810-projectfinder-should-handle-more-options.yml b/changelogs/unreleased/28810-projectfinder-should-handle-more-options.yml new file mode 100644 index 00000000000..e4be16d4b37 --- /dev/null +++ b/changelogs/unreleased/28810-projectfinder-should-handle-more-options.yml @@ -0,0 +1,4 @@ +--- +title: ProjectsFinder should handle more options +merge_request: 9682 +author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/30021-api-deploy_keys-can_push-is-not-honoured.yml b/changelogs/unreleased/30021-api-deploy_keys-can_push-is-not-honoured.yml new file mode 100644 index 00000000000..7584995a11f --- /dev/null +++ b/changelogs/unreleased/30021-api-deploy_keys-can_push-is-not-honoured.yml @@ -0,0 +1,4 @@ +--- +title: Enable creation of deploy keys with write access via the API +merge_request: +author: diff --git a/changelogs/unreleased/30291-reopen-mr.yml b/changelogs/unreleased/30291-reopen-mr.yml new file mode 100644 index 00000000000..4ae3e90eeba --- /dev/null +++ b/changelogs/unreleased/30291-reopen-mr.yml @@ -0,0 +1,4 @@ +--- +title: Include reopened MRs when searching for opened ones +merge_request: 10407 +author: diff --git a/changelogs/unreleased/30493-env-deploy-tooltip.yml b/changelogs/unreleased/30493-env-deploy-tooltip.yml new file mode 100644 index 00000000000..8fadaaa7bd2 --- /dev/null +++ b/changelogs/unreleased/30493-env-deploy-tooltip.yml @@ -0,0 +1,5 @@ +--- +title: Fixes HTML structure that was preventing the tooltip to disappear when hovering + out of the button. +merge_request: +author: diff --git a/changelogs/unreleased/add_remove_concurrent_index_to_database_helper.yml b/changelogs/unreleased/add_remove_concurrent_index_to_database_helper.yml new file mode 100644 index 00000000000..c7b06e45607 --- /dev/null +++ b/changelogs/unreleased/add_remove_concurrent_index_to_database_helper.yml @@ -0,0 +1,4 @@ +--- +title: Add remove_concurrent_index to database helper +merge_request: 10441 +author: blackst0ne diff --git a/changelogs/unreleased/fix-gh-import-status-check.yml b/changelogs/unreleased/fix-gh-import-status-check.yml new file mode 100644 index 00000000000..d04bc2954a0 --- /dev/null +++ b/changelogs/unreleased/fix-gh-import-status-check.yml @@ -0,0 +1,4 @@ +--- +title: Periodically mark projects that are stuck in importing as failed +merge_request: +author: diff --git a/changelogs/unreleased/tc-fix-pipeline-recipient.yml b/changelogs/unreleased/tc-fix-pipeline-recipient.yml new file mode 100644 index 00000000000..0337533fdb2 --- /dev/null +++ b/changelogs/unreleased/tc-fix-pipeline-recipient.yml @@ -0,0 +1,4 @@ +--- +title: Clearly show who triggered the pipeline in email +merge_request: 10283 +author: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index e8fef0000c1..f7cae84088e 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -349,6 +349,9 @@ Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsW Settings.cron_jobs['remove_unreferenced_lfs_objects_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['cron'] ||= '20 0 * * *' Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'RemoveUnreferencedLfsObjectsWorker' +Settings.cron_jobs['stuck_import_jobs_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['stuck_import_jobs_worker']['cron'] ||= '15 * * * *' +Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWorker' # # GitLab Shell diff --git a/config/webpack.config.js b/config/webpack.config.js index 69d8c5640f7..dc431e4d566 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -42,6 +42,7 @@ var config = { profile: './profile/profile_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js', snippet: './snippet/snippet_bundle.js', + stl_viewer: './blob/stl_viewer.js', terminal: './terminal/terminal_bundle.js', u2f: ['vendor/u2f'], users: './users/users_bundle.js', diff --git a/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb b/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb index 7a8ed99c68f..178e4bf5ed3 100644 --- a/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb +++ b/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexOnRequestedAtToMembers < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb index 4bb4204cebd..081df23f394 100644 --- a/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb +++ b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class RemoveKeysFingerprintIndexIfExists < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb index e35af38aac3..76bb6a09639 100644 --- a/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb +++ b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddUniqueIndexToKeysFingerprint < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb index 6ca486c63d1..48f4495b0a4 100644 --- a/db/migrate/20160620115026_add_index_on_runners_locked.rb +++ b/db/migrate/20160620115026_add_index_on_runners_locked.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIndexOnRunnersLocked < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb index a05a4c679e3..fec17ffb7f6 100644 --- a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb +++ b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexForPipelineUserId < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb index 75a3eb15124..12e11bc3fbe 100644 --- a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb +++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class MergeRequestDiffRemoveUniq < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers disable_ddl_transaction! diff --git a/db/migrate/20160725104452_merge_request_diff_add_index.rb b/db/migrate/20160725104452_merge_request_diff_add_index.rb index 6d04242dd25..60d81e0bdc0 100644 --- a/db/migrate/20160725104452_merge_request_diff_add_index.rb +++ b/db/migrate/20160725104452_merge_request_diff_add_index.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class MergeRequestDiffAddIndex < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers disable_ddl_transaction! diff --git a/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb b/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb index 5fd51cb65f1..6d7733762c8 100644 --- a/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb +++ b/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class RemoveBuildsEnableIndexOnProjects < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160803161903_add_unique_index_to_lists_label_id.rb b/db/migrate/20160803161903_add_unique_index_to_lists_label_id.rb index baf2e70b127..9c1511963f7 100644 --- a/db/migrate/20160803161903_add_unique_index_to_lists_label_id.rb +++ b/db/migrate/20160803161903_add_unique_index_to_lists_label_id.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddUniqueIndexToListsLabelId < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb index 3f074723b4a..30d98a0124e 100644 --- a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb +++ b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddDeletedAtToNamespaces < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160808085602_add_index_for_build_token.rb b/db/migrate/20160808085602_add_index_for_build_token.rb index 6c5d7268e72..0446b2f2e15 100644 --- a/db/migrate/20160808085602_add_index_for_build_token.rb +++ b/db/migrate/20160808085602_add_index_for_build_token.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexForBuildToken < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160810142633_remove_redundant_indexes.rb b/db/migrate/20160810142633_remove_redundant_indexes.rb index 8641c6ffa8f..d7ab022d7bc 100644 --- a/db/migrate/20160810142633_remove_redundant_indexes.rb +++ b/db/migrate/20160810142633_remove_redundant_indexes.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class RemoveRedundantIndexes < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160819221631_add_index_to_note_discussion_id.rb b/db/migrate/20160819221631_add_index_to_note_discussion_id.rb index 8f693e97a58..843643c4e95 100644 --- a/db/migrate/20160819221631_add_index_to_note_discussion_id.rb +++ b/db/migrate/20160819221631_add_index_to_note_discussion_id.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIndexToNoteDiscussionId < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160819232256_add_incoming_email_token_to_users.rb b/db/migrate/20160819232256_add_incoming_email_token_to_users.rb index bcad3416d04..a004a3802a2 100644 --- a/db/migrate/20160819232256_add_incoming_email_token_to_users.rb +++ b/db/migrate/20160819232256_add_incoming_email_token_to_users.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIncomingEmailTokenToUsers < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160919145149_add_group_id_to_labels.rb b/db/migrate/20160919145149_add_group_id_to_labels.rb index e20e693f3aa..917c2b0c521 100644 --- a/db/migrate/20160919145149_add_group_id_to_labels.rb +++ b/db/migrate/20160919145149_add_group_id_to_labels.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddGroupIdToLabels < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20160920160832_add_index_to_labels_title.rb b/db/migrate/20160920160832_add_index_to_labels_title.rb index 19f7b1076a7..e38c655baee 100644 --- a/db/migrate/20160920160832_add_index_to_labels_title.rb +++ b/db/migrate/20160920160832_add_index_to_labels_title.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToLabelsTitle < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161017125927_add_unique_index_to_labels.rb b/db/migrate/20161017125927_add_unique_index_to_labels.rb index f2b56ebfb7b..b8f6a803a0a 100644 --- a/db/migrate/20161017125927_add_unique_index_to_labels.rb +++ b/db/migrate/20161017125927_add_unique_index_to_labels.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddUniqueIndexToLabels < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb index 35ad22b6c01..b77daf12f68 100644 --- a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb +++ b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddPipelineIdToMergeRequestMetrics < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb b/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb index 4b1b29e1265..f263377fbc6 100644 --- a/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb +++ b/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddUniqueIndexToSubscriptions < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161106185620_add_project_import_data_project_index.rb b/db/migrate/20161106185620_add_project_import_data_project_index.rb index 94b8ddd46f5..b3746dc4f6c 100644 --- a/db/migrate/20161106185620_add_project_import_data_project_index.rb +++ b/db/migrate/20161106185620_add_project_import_data_project_index.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddProjectImportDataProjectIndex < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161124111395_add_index_to_parent_id.rb b/db/migrate/20161124111395_add_index_to_parent_id.rb index 73f9d92bb22..065643e058d 100644 --- a/db/migrate/20161124111395_add_index_to_parent_id.rb +++ b/db/migrate/20161124111395_add_index_to_parent_id.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIndexToParentId < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161128142110_remove_unnecessary_indexes.rb b/db/migrate/20161128142110_remove_unnecessary_indexes.rb index 8100287ef48..699a9368eb3 100644 --- a/db/migrate/20161128142110_remove_unnecessary_indexes.rb +++ b/db/migrate/20161128142110_remove_unnecessary_indexes.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class RemoveUnnecessaryIndexes < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers disable_ddl_transaction! diff --git a/db/migrate/20161202152035_add_index_to_routes.rb b/db/migrate/20161202152035_add_index_to_routes.rb index 6d6c8906204..552b5fab68c 100644 --- a/db/migrate/20161202152035_add_index_to_routes.rb +++ b/db/migrate/20161202152035_add_index_to_routes.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIndexToRoutes < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161206153749_remove_uniq_path_index_from_namespace.rb b/db/migrate/20161206153749_remove_uniq_path_index_from_namespace.rb index 2977917f2d1..7d39c2ae626 100644 --- a/db/migrate/20161206153749_remove_uniq_path_index_from_namespace.rb +++ b/db/migrate/20161206153749_remove_uniq_path_index_from_namespace.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class RemoveUniqPathIndexFromNamespace < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161206153751_add_path_index_to_namespace.rb b/db/migrate/20161206153751_add_path_index_to_namespace.rb index b0bac7d121e..623037e35cd 100644 --- a/db/migrate/20161206153751_add_path_index_to_namespace.rb +++ b/db/migrate/20161206153751_add_path_index_to_namespace.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddPathIndexToNamespace < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161206153753_remove_uniq_name_index_from_namespace.rb b/db/migrate/20161206153753_remove_uniq_name_index_from_namespace.rb index cc9d4974baa..9296ae36aa5 100644 --- a/db/migrate/20161206153753_remove_uniq_name_index_from_namespace.rb +++ b/db/migrate/20161206153753_remove_uniq_name_index_from_namespace.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class RemoveUniqNameIndexFromNamespace < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161206153754_add_name_index_to_namespace.rb b/db/migrate/20161206153754_add_name_index_to_namespace.rb index b3f3cb68a99..2bbd039ff27 100644 --- a/db/migrate/20161206153754_add_name_index_to_namespace.rb +++ b/db/migrate/20161206153754_add_name_index_to_namespace.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddNameIndexToNamespace < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161207231621_create_environment_name_unique_index.rb b/db/migrate/20161207231621_create_environment_name_unique_index.rb index 5ff0f5bae4d..15093350f12 100644 --- a/db/migrate/20161207231621_create_environment_name_unique_index.rb +++ b/db/migrate/20161207231621_create_environment_name_unique_index.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class CreateEnvironmentNameUniqueIndex < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb b/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb index ede0316e860..42a90091b87 100644 --- a/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb +++ b/db/migrate/20161209153400_add_unique_index_for_environment_slug.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddUniqueIndexForEnvironmentSlug < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20161212142807_add_lower_path_index_to_routes.rb b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb index 53f4c6bbb18..76db5179795 100644 --- a/db/migrate/20161212142807_add_lower_path_index_to_routes.rb +++ b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddLowerPathIndexToRoutes < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb b/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb index 4ea953f2b78..c006098fafd 100644 --- a/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb +++ b/db/migrate/20170121123724_add_index_to_ci_builds_for_status_runner_id_and_type.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToCiBuildsForStatusRunnerIdAndType < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb b/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb index 620befcf4d7..00aa0b311b1 100644 --- a/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb +++ b/db/migrate/20170121130655_add_index_to_ci_runners_for_is_shared.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToCiRunnersForIsShared < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170130204620_add_index_to_project_authorizations.rb b/db/migrate/20170130204620_add_index_to_project_authorizations.rb index 0884296131d..f256251516a 100644 --- a/db/migrate/20170130204620_add_index_to_project_authorizations.rb +++ b/db/migrate/20170130204620_add_index_to_project_authorizations.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToProjectAuthorizations < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers @@ -6,8 +7,9 @@ class AddIndexToProjectAuthorizations < ActiveRecord::Migration disable_ddl_transaction! def up - add_concurrent_index(:project_authorizations, :project_id) unless - index_exists?(:project_authorizations, :project_id) + unless index_exists?(:project_authorizations, :project_id) + add_concurrent_index(:project_authorizations, :project_id) + end end def down diff --git a/db/migrate/20170131221752_add_relative_position_to_issues.rb b/db/migrate/20170131221752_add_relative_position_to_issues.rb index 1baad0893e3..fd18d8b6a60 100644 --- a/db/migrate/20170131221752_add_relative_position_to_issues.rb +++ b/db/migrate/20170131221752_add_relative_position_to_issues.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddRelativePositionToIssues < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb b/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb index 31ef458c44f..b1b0a601007 100644 --- a/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb +++ b/db/migrate/20170204181513_add_index_to_labels_for_type_and_project.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToLabelsForTypeAndProject < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb index 70fb0ef12f9..2c20f6a48ab 100644 --- a/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb +++ b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToLabelsForTitleAndProject < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb index 07d4f8af27f..c31057f2617 100644 --- a/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb +++ b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexToCiTriggerRequestsForCommitId < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170210103609_add_index_to_user_agent_detail.rb b/db/migrate/20170210103609_add_index_to_user_agent_detail.rb index 2d8329b7862..ba4976a5ce8 100644 --- a/db/migrate/20170210103609_add_index_to_user_agent_detail.rb +++ b/db/migrate/20170210103609_add_index_to_user_agent_detail.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIndexToUserAgentDetail < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb b/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb index 8a96a784c97..884c4e569d6 100644 --- a/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb +++ b/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class AddIndexForLatestSuccessfulPipeline < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20170216141440_drop_index_for_builds_project_status.rb b/db/migrate/20170216141440_drop_index_for_builds_project_status.rb index a2839f52d89..56ad566ca67 100644 --- a/db/migrate/20170216141440_drop_index_for_builds_project_status.rb +++ b/db/migrate/20170216141440_drop_index_for_builds_project_status.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class DropIndexForBuildsProjectStatus < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20170222143500_remove_old_project_id_columns.rb b/db/migrate/20170222143500_remove_old_project_id_columns.rb index eac93e8e407..268144a2552 100644 --- a/db/migrate/20170222143500_remove_old_project_id_columns.rb +++ b/db/migrate/20170222143500_remove_old_project_id_columns.rb @@ -1,3 +1,4 @@ +# rubocop:disable RemoveIndex class RemoveOldProjectIdColumns < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers disable_ddl_transaction! diff --git a/db/migrate/20170313213916_add_index_to_user_ghost.rb b/db/migrate/20170313213916_add_index_to_user_ghost.rb index c429039c275..fe5847ed225 100644 --- a/db/migrate/20170313213916_add_index_to_user_ghost.rb +++ b/db/migrate/20170313213916_add_index_to_user_ghost.rb @@ -1,6 +1,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. +# rubocop:disable RemoveIndex class AddIndexToUserGhost < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170405080720_add_import_jid_to_projects.rb b/db/migrate/20170405080720_add_import_jid_to_projects.rb new file mode 100644 index 00000000000..55b87b9d56d --- /dev/null +++ b/db/migrate/20170405080720_add_import_jid_to_projects.rb @@ -0,0 +1,9 @@ +class AddImportJidToProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :projects, :import_jid, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index a7efdcbe111..59dbe9fd69a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170402231018) do +ActiveRecord::Schema.define(version: 20170405080720) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -922,6 +922,7 @@ ActiveRecord::Schema.define(version: 20170402231018) do t.boolean "only_allow_merge_if_all_discussions_are_resolved" t.integer "auto_cancel_pending_pipelines", default: 0, null: false t.boolean "printing_merge_request_link_enabled", default: true, null: false + t.string "import_jid" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index fd8335d251e..587922d0136 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -58,10 +58,22 @@ migration was tested. ## Removing indices -If you need to remove index, please add a condition like in following example: +When removing an index make sure to use the method `remove_concurrent_index` instead +of the regular `remove_index` method. The `remove_concurrent_index` method +automatically drops concurrent indexes when using PostgreSQL, removing the +need for downtime. To use this method you must disable transactions by calling +the method `disable_ddl_transaction!` in the body of your migration class like +so: ```ruby -remove_index :namespaces, column: :name if index_exists?(:namespaces, :name) +class MyMigration < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + remove_concurrent_index :table_name, :column_name if index_exists?(:table_name, :column_name) + end +end ``` ## Adding indices diff --git a/generator_templates/active_record/migration/create_table_migration.rb b/generator_templates/active_record/migration/create_table_migration.rb index aad8626a720..59a9d37df0f 100644 --- a/generator_templates/active_record/migration/create_table_migration.rb +++ b/generator_templates/active_record/migration/create_table_migration.rb @@ -12,12 +12,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration # migration requires downtime. # DOWNTIME_REASON = '' - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. + # When using the methods "add_concurrent_index", "remove_concurrent_index" or + # "add_column_with_default" you must disable the use of transactions + # as these methods can not run in an existing transaction. + # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure + # that either of them is the _only_ method called in the migration, + # any other changes should go in a separate migration. + # This ensures that upon failure _only_ the index creation or removing fails + # and can be retried or reverted easily. # # To disable transactions uncomment the following line and remove these # comments: diff --git a/generator_templates/active_record/migration/migration.rb b/generator_templates/active_record/migration/migration.rb index 825bc8bdf61..08752b3af50 100644 --- a/generator_templates/active_record/migration/migration.rb +++ b/generator_templates/active_record/migration/migration.rb @@ -12,12 +12,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration # migration requires downtime. # DOWNTIME_REASON = '' - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. + # When using the methods "add_concurrent_index", "remove_concurrent_index" or + # "add_column_with_default" you must disable the use of transactions + # as these methods can not run in an existing transaction. + # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure + # that either of them is the _only_ method called in the migration, + # any other changes should go in a separate migration. + # This ensures that upon failure _only_ the index creation or removing fails + # and can be retried or reverted easily. # # To disable transactions uncomment the following line and remove these # comments: diff --git a/generator_templates/rails/post_deployment_migration/migration.rb b/generator_templates/rails/post_deployment_migration/migration.rb index 1a7b8d5bf35..f2dff84b618 100644 --- a/generator_templates/rails/post_deployment_migration/migration.rb +++ b/generator_templates/rails/post_deployment_migration/migration.rb @@ -6,12 +6,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration DOWNTIME = false - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. + # When using the methods "add_concurrent_index", "remove_concurrent_index" or + # "add_column_with_default" you must disable the use of transactions + # as these methods can not run in an existing transaction. + # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure + # that either of them is the _only_ method called in the migration, + # any other changes should go in a separate migration. + # This ensures that upon failure _only_ the index creation or removing fails + # and can be retried or reverted easily. # # To disable transactions uncomment the following line and remove these # comments: diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index b888ede6fe8..8a54f7f3f05 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -47,6 +47,7 @@ module API params do requires :key, type: String, desc: 'The new deploy key' requires :title, type: String, desc: 'The name of the deploy key' + optional :can_push, type: Boolean, desc: "Can deploy key push to the project's repository" end post ":id/deploy_keys" do params[:key].strip! diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 8f3799417e3..605769eddde 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -142,7 +142,7 @@ module API end get ":id/projects" do group = find_group!(params[:id]) - projects = GroupProjectsFinder.new(group).execute(current_user) + projects = GroupProjectsFinder.new(group: group, current_user: current_user).execute projects = filter_projects(projects) entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project present paginate(projects), with: entity, current_user: current_user diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 0fbe1669d45..766fbea53e6 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -84,7 +84,7 @@ module API end get do entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails - present_projects ProjectsFinder.new.execute(current_user), with: entity, statistics: params[:statistics] + present_projects ProjectsFinder.new(current_user: current_user).execute, with: entity, statistics: params[:statistics] end desc 'Create new project' do diff --git a/lib/api/users.rb b/lib/api/users.rb index 530ca0b5235..992a751b37d 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -341,7 +341,7 @@ module API not_found!('User') unless user events = user.events. - merge(ProjectsFinder.new.execute(current_user)). + merge(ProjectsFinder.new(current_user: current_user).execute). references(:project). with_associations. recent diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb index c5b37622d79..9b27411ae21 100644 --- a/lib/api/v3/groups.rb +++ b/lib/api/v3/groups.rb @@ -151,7 +151,7 @@ module API end get ":id/projects" do group = find_group!(params[:id]) - projects = GroupProjectsFinder.new(group).execute(current_user) + projects = GroupProjectsFinder.new(group: group, current_user: current_user).execute projects = filter_projects(projects) entity = params[:simple] ? ::API::Entities::BasicProjectDetails : Entities::Project present paginate(projects), with: entity, current_user: current_user diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb index b753dbab381..ba9748ada59 100644 --- a/lib/api/v3/projects.rb +++ b/lib/api/v3/projects.rb @@ -107,7 +107,7 @@ module API end get '/visible' do entity = current_user ? ::API::V3::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails - present_projects ProjectsFinder.new.execute(current_user), with: entity + present_projects ProjectsFinder.new(current_user: current_user).execute, with: entity end desc 'Get a projects list for authenticated user' do diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb index 5e18cecc431..f4cda3b2eba 100644 --- a/lib/api/v3/users.rb +++ b/lib/api/v3/users.rb @@ -138,7 +138,7 @@ module API not_found!('User') unless user events = user.events. - merge(ProjectsFinder.new.execute(current_user)). + merge(ProjectsFinder.new(current_user: current_user).execute). references(:project). with_associations. recent diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index fc445ab9483..525aa920328 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -26,6 +26,30 @@ module Gitlab add_index(table_name, column_name, options) end + # Removes an existed index, concurrently when supported + # + # On PostgreSQL this method removes an index concurrently. + # + # Example: + # + # remove_concurrent_index :users, :some_column + # + # See Rails' `remove_index` for more info on the available arguments. + def remove_concurrent_index(table_name, column_name, options = {}) + if transaction_open? + raise 'remove_concurrent_index can not be run inside a transaction, ' \ + 'you can disable transactions by calling disable_ddl_transaction! ' \ + 'in the body of your migration class' + end + + if Database.postgresql? + options = options.merge({ algorithm: :concurrently }) + disable_statement_timeout + end + + remove_index(table_name, options.merge({ column: column_name })) + end + # Adds a foreign key with only minimal locking on the tables involved. # # This method only requires minimal locking when using PostgreSQL. When diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb index 11e5f1b645c..ca8d3271541 100644 --- a/lib/gitlab/sidekiq_status.rb +++ b/lib/gitlab/sidekiq_status.rb @@ -72,6 +72,8 @@ module Gitlab # job_ids - The Sidekiq job IDs to check. # # Returns an array of true or false indicating job completion. + # true = job is still running + # false = job completed def self.job_status(job_ids) keys = job_ids.map { |jid| key_for(jid) } @@ -82,6 +84,17 @@ module Gitlab end end + # Returns the JIDs that are completed + # + # job_ids - The Sidekiq job IDs to check. + # + # Returns an array of completed JIDs + def self.completed_jids(job_ids) + Sidekiq.redis do |redis| + job_ids.reject { |jid| redis.exists(key_for(jid)) } + end + end + def self.key_for(jid) STATUS_KEY % jid end diff --git a/lib/gitlab/sidekiq_status/client_middleware.rb b/lib/gitlab/sidekiq_status/client_middleware.rb index d47609f490d..00983b3284a 100644 --- a/lib/gitlab/sidekiq_status/client_middleware.rb +++ b/lib/gitlab/sidekiq_status/client_middleware.rb @@ -2,7 +2,9 @@ module Gitlab module SidekiqStatus class ClientMiddleware def call(_, job, _, _) - Gitlab::SidekiqStatus.set(job['jid']) + status_expiration = job['status_expiration'] || Gitlab::SidekiqStatus::DEFAULT_EXPIRATION + + Gitlab::SidekiqStatus.set(job['jid'], status_expiration) yield end end diff --git a/package.json b/package.json index 3f64d65d57a..312e38f7407 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,9 @@ "raw-loader": "^0.5.1", "select2": "3.5.2-browserify", "stats-webpack-plugin": "^0.4.3", + "three": "^0.84.0", + "three-orbit-controls": "^82.1.0", + "three-stl-loader": "^1.0.4", "timeago.js": "^2.0.5", "underscore": "^1.8.3", "visibilityjs": "^1.2.4", diff --git a/rubocop/cop/migration/add_concurrent_index.rb b/rubocop/cop/migration/add_concurrent_index.rb index 332fb7dcbd7..69852f4d580 100644 --- a/rubocop/cop/migration/add_concurrent_index.rb +++ b/rubocop/cop/migration/add_concurrent_index.rb @@ -9,7 +9,7 @@ module RuboCop include MigrationHelpers MSG = '`add_concurrent_index` is not reversible so you must manually define ' \ - 'the `up` and `down` methods in your migration class, using `remove_index` in `down`'.freeze + 'the `up` and `down` methods in your migration class, using `remove_concurrent_index` in `down`'.freeze def on_send(node) return unless in_migration?(node) diff --git a/rubocop/cop/migration/remove_concurrent_index.rb b/rubocop/cop/migration/remove_concurrent_index.rb new file mode 100644 index 00000000000..268c49865cb --- /dev/null +++ b/rubocop/cop/migration/remove_concurrent_index.rb @@ -0,0 +1,29 @@ +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if `remove_concurrent_index` is used with `up`/`down` methods + # and not `change`. + class RemoveConcurrentIndex < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = '`remove_concurrent_index` is not reversible so you must manually define ' \ + 'the `up` and `down` methods in your migration class, using `add_concurrent_index` in `down`'.freeze + + def on_send(node) + return unless in_migration?(node) + return unless node.children[1] == :remove_concurrent_index + + node.each_ancestor(:def) do |def_node| + add_offense(def_node, :name) if method_name(def_node) == :change + end + end + + def method_name(node) + node.children[0] + end + end + end + end +end diff --git a/rubocop/cop/migration/remove_index.rb b/rubocop/cop/migration/remove_index.rb new file mode 100644 index 00000000000..613b35dd00d --- /dev/null +++ b/rubocop/cop/migration/remove_index.rb @@ -0,0 +1,26 @@ +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if indexes are removed in a concurrent manner. + class RemoveIndex < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = '`remove_index` requires downtime, use `remove_concurrent_index` instead'.freeze + + def on_def(node) + return unless in_migration?(node) + + node.each_descendant(:send) do |send_node| + add_offense(send_node, :selector) if method_name(send_node) == :remove_index + end + end + + def method_name(node) + node.children[1] + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index a50a522cf9d..d580aa6857a 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -5,3 +5,5 @@ require_relative 'cop/migration/add_column_with_default' require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_index' +require_relative 'cop/migration/remove_concurrent_index' +require_relative 'cop/migration/remove_index' diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb index dd93b439b2b..4e140102492 100644 --- a/spec/factories/keys.rb +++ b/spec/factories/keys.rb @@ -23,5 +23,9 @@ FactoryGirl.define do factory :another_deploy_key, class: 'DeployKey' do end end + + factory :write_access_key, class: 'DeployKey' do + can_push true + end end end diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index ef97b061ca7..3c7c9bdcd08 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -3,8 +3,9 @@ require 'spec_helper' describe GroupProjectsFinder do let(:group) { create(:group) } let(:current_user) { create(:user) } + let(:options) { {} } - let(:finder) { described_class.new(source_user) } + let(:finder) { described_class.new(group: group, current_user: current_user, options: options) } let!(:public_project) { create(:empty_project, :public, group: group, path: '1') } let!(:private_project) { create(:empty_project, :private, group: group, path: '2') } @@ -18,22 +19,27 @@ describe GroupProjectsFinder do shared_project_3.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) end + subject { finder.execute } + describe 'with a group member current user' do - before { group.add_user(current_user, Gitlab::Access::MASTER) } + before do + group.add_master(current_user) + end context "only shared" do - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) } + let(:options) { { only_shared: true } } + + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1]) } end context "only owned" do - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([private_project, public_project]) } + let(:options) { { only_owned: true } } + + it { is_expected.to match_array([private_project, public_project]) } end context "all" do - subject { described_class.new(group).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } end end @@ -44,47 +50,57 @@ describe GroupProjectsFinder do end context "only shared" do + let(:options) { { only_shared: true } } + context "without external user" do - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) } + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1]) } end context "with external user" do - before { current_user.update_attributes(external: true) } - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_2, shared_project_1]) } + before do + current_user.update_attributes(external: true) + end + + it { is_expected.to match_array([shared_project_2, shared_project_1]) } end end context "only owned" do + let(:options) { { only_owned: true } } + context "without external user" do - before { private_project.team << [current_user, Gitlab::Access::MASTER] } - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([private_project, public_project]) } + before do + private_project.team << [current_user, Gitlab::Access::MASTER] + end + + it { is_expected.to match_array([private_project, public_project]) } end context "with external user" do - before { current_user.update_attributes(external: true) } - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([public_project]) } - end + before do + current_user.update_attributes(external: true) + end - context "all" do - subject { described_class.new(group).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, public_project]) } + it { is_expected.to eq([public_project]) } end end + + context "all" do + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) } + end end describe "no user" do context "only shared" do - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_1]) } + let(:options) { { only_shared: true } } + + it { is_expected.to match_array([shared_project_3, shared_project_1]) } end context "only owned" do - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([public_project]) } + let(:options) { { only_owned: true } } + + it { is_expected.to eq([public_project]) } end end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index e44e7434c80..148adcffe3b 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -21,38 +21,144 @@ describe ProjectsFinder do create(:empty_project, :private, name: 'D', path: 'D') end - let(:finder) { described_class.new } + let(:params) { {} } + let(:current_user) { user } + let(:project_ids_relation) { nil } + let(:finder) { described_class.new(params: params, current_user: current_user, project_ids_relation: project_ids_relation) } + + subject { finder.execute } describe 'without a user' do - subject { finder.execute } + let(:current_user) { nil } it { is_expected.to eq([public_project]) } end describe 'with a user' do - subject { finder.execute(user) } - describe 'without private projects' do - it { is_expected.to eq([public_project, internal_project]) } + it { is_expected.to match_array([public_project, internal_project]) } end describe 'with private projects' do before do - private_project.add_user(user, Gitlab::Access::MASTER) + private_project.add_master(user) end - it do - is_expected.to eq([public_project, internal_project, private_project]) - end + it { is_expected.to match_array([public_project, internal_project, private_project]) } end end describe 'with project_ids_relation' do let(:project_ids_relation) { Project.where(id: internal_project.id) } - subject { finder.execute(user, project_ids_relation) } - it { is_expected.to eq([internal_project]) } end + + describe 'filter by visibility_level' do + before do + private_project.add_master(user) + end + + context 'private' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::PRIVATE } } + + it { is_expected.to eq([private_project]) } + end + + context 'internal' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL } } + + it { is_expected.to eq([internal_project]) } + end + + context 'public' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::PUBLIC } } + + it { is_expected.to eq([public_project]) } + end + end + + describe 'filter by tags' do + before do + public_project.tag_list.add('foo') + public_project.save! + end + + let(:params) { { tag: 'foo' } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by personal' do + let!(:personal_project) { create(:empty_project, namespace: user.namespace) } + let(:params) { { personal: true } } + + it { is_expected.to eq([personal_project]) } + end + + describe 'filter by search' do + let(:params) { { search: 'C' } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by name for backward compatibility' do + let(:params) { { name: 'C' } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by archived' do + let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') } + + context 'non_archived=true' do + let(:params) { { non_archived: true } } + + it { is_expected.to match_array([public_project, internal_project]) } + end + + context 'non_archived=false' do + let(:params) { { non_archived: false } } + + it { is_expected.to match_array([public_project, internal_project, archived_project]) } + end + + describe 'filter by archived for backward compatibility' do + let(:params) { { archived: false } } + + it { is_expected.to match_array([public_project, internal_project]) } + end + end + + describe 'filter by trending' do + let!(:trending_project) { create(:trending_project, project: public_project) } + let(:params) { { trending: true } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by non_public' do + let(:params) { { non_public: true } } + before do + private_project.add_developer(current_user) + end + + it { is_expected.to eq([private_project]) } + end + + describe 'filter by viewable_starred_projects' do + let(:params) { { starred: true } } + before do + current_user.toggle_star(public_project) + end + + it { is_expected.to eq([public_project]) } + end + + describe 'sorting' do + let(:params) { { sort: 'name_asc' } } + + it { is_expected.to eq([internal_project, public_project]) } + end end end diff --git a/spec/javascripts/blob/3d_viewer/mesh_object_spec.js b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js new file mode 100644 index 00000000000..d1ebae33dab --- /dev/null +++ b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js @@ -0,0 +1,42 @@ +import { + BoxGeometry, +} from 'three/build/three.module'; +import MeshObject from '~/blob/3d_viewer/mesh_object'; + +describe('Mesh object', () => { + it('defaults to non-wireframe material', () => { + const object = new MeshObject( + new BoxGeometry(10, 10, 10), + ); + + expect(object.material.wireframe).toBeFalsy(); + }); + + it('changes to wirefame material', () => { + const object = new MeshObject( + new BoxGeometry(10, 10, 10), + ); + + object.changeMaterial('wireframe'); + + expect(object.material.wireframe).toBeTruthy(); + }); + + it('scales object down', () => { + const object = new MeshObject( + new BoxGeometry(10, 10, 10), + ); + const radius = object.geometry.boundingSphere.radius; + + expect(radius).not.toBeGreaterThan(4); + }); + + it('does not scale object down', () => { + const object = new MeshObject( + new BoxGeometry(1, 1, 1), + ); + const radius = object.geometry.boundingSphere.radius; + + expect(radius).toBeLessThan(1); + }); +}); diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index 19a4e55a9db..d3a4d04345b 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -3,6 +3,18 @@ import testPDF from './test.pdf'; describe('PDF renderer', () => { let viewer; + let app; + + const checkLoaded = (done) => { + if (app.loading) { + setTimeout(() => { + checkLoaded(done); + }, 100); + } else { + done(); + } + }; + preloadFixtures('static/pdf_viewer.html.raw'); beforeEach(() => { @@ -21,11 +33,9 @@ describe('PDF renderer', () => { describe('successful response', () => { beforeEach((done) => { - renderPDF(); + app = renderPDF(); - setTimeout(() => { - done(); - }, 500); + checkLoaded(done); }); it('does not show loading icon', () => { @@ -50,11 +60,9 @@ describe('PDF renderer', () => { describe('error getting file', () => { beforeEach((done) => { viewer.dataset.endpoint = 'invalid/endpoint'; - renderPDF(); + app = renderPDF(); - setTimeout(() => { - done(); - }, 500); + checkLoaded(done); }); it('does not show loading icon', () => { diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index 549c7af8ea8..beee6cb2969 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -75,6 +75,7 @@ describe('Build', () => { expect(url).toBe(`${BUILD_URL}.json`); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); success.call(context, { trace_html: '<span>Example</span>', status: 'running' }); @@ -83,6 +84,7 @@ describe('Build', () => { it('removes the spinner', () => { const [{ success, context }] = $.ajax.calls.argsFor(0); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); success.call(context, { trace_html: '<span>Example</span>', status: 'success' }); expect($('.js-build-refresh').length).toBe(0); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 5a93d479c1f..03f3c206f44 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -310,5 +310,56 @@ require('~/lib/utils/common_utils'); }); }, 10000); }); + + describe('gl.utils.setFavicon', () => { + it('should set page favicon to provided favicon', () => { + const faviconName = 'custom_favicon'; + const fakeLink = { + setAttribute() {}, + }; + + spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); + spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { + expect(attr).toEqual('href'); + expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true); + }); + gl.utils.setFavicon(faviconName); + }); + }); + + describe('gl.utils.resetFavicon', () => { + it('should reset page favicon to tanuki', () => { + const fakeLink = { + setAttribute() {}, + }; + + spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); + spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { + expect(attr).toEqual('href'); + expect(val).toMatch(/favicon/); + }); + gl.utils.resetFavicon(); + }); + }); + + describe('gl.utils.setCiStatusFavicon', () => { + it('should set page favicon to CI status favicon based on provided status', () => { + const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`; + const FAVICON_PATH = 'ci_favicons/'; + const FAVICON = 'icon_status_success'; + const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub(); + const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub(); + spyOn($, 'ajax').and.callFake(function (options) { + options.success({ icon: FAVICON }); + expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON); + options.success(); + expect(spyResetFavicon).toHaveBeenCalled(); + options.error(); + expect(spyResetFavicon).toHaveBeenCalled(); + }); + + gl.utils.setCiStatusFavicon(BUILD_URL); + }); + }); }); })(); diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index d5193b41c33..88dae8c3e06 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -142,18 +142,21 @@ require('~/lib/utils/datetime_utility'); it('should call showCIStatus even if a notification should not be displayed', function() { var spy; spy = spyOn(this["class"], 'showCIStatus').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status); }); it('should call showCIStatus when a notification should be displayed', function() { var spy; spy = spyOn(this["class"], 'showCIStatus').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(true); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status); }); it('should call showCICoverage when the coverage rate is set', function() { var spy; spy = spyOn(this["class"], 'showCICoverage').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage); }); @@ -161,12 +164,14 @@ require('~/lib/utils/datetime_utility'); var spy; this.ciStatusData.coverage = null; spy = spyOn(this["class"], 'showCICoverage').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); return expect(spy).not.toHaveBeenCalled(); }); it('should not display a notification on the first check after the widget has been created', function() { var spy; spy = spyOn(window, 'notify'); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); @@ -174,6 +179,7 @@ require('~/lib/utils/datetime_utility'); it('should update the pipeline URL when the pipeline changes', function() { var spy; spy = spyOn(this["class"], 'updatePipelineUrls').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); this.ciStatusData.pipeline += 1; this["class"].getCIStatus(false); @@ -182,6 +188,7 @@ require('~/lib/utils/datetime_utility'); it('should update the commit URL when the sha changes', function() { var spy; spy = spyOn(this["class"], 'updateCommitUrls').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); this.ciStatusData.sha = "9b50b99a"; this["class"].getCIStatus(false); diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index e007044868c..4ac79454647 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -58,6 +58,48 @@ describe Gitlab::Database::MigrationHelpers, lib: true do end end + describe '#remove_concurrent_index' do + context 'outside a transaction' do + before do + allow(model).to receive(:transaction_open?).and_return(false) + end + + context 'using PostgreSQL' do + before do + allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + allow(model).to receive(:disable_statement_timeout) + end + + it 'removes the index concurrently' do + expect(model).to receive(:remove_index). + with(:users, { algorithm: :concurrently, column: :foo }) + + model.remove_concurrent_index(:users, :foo) + end + end + + context 'using MySQL' do + it 'removes an index' do + expect(Gitlab::Database).to receive(:postgresql?).and_return(false) + + expect(model).to receive(:remove_index). + with(:users, { column: :foo }) + + model.remove_concurrent_index(:users, :foo) + end + end + end + + context 'inside a transaction' do + it 'raises RuntimeError' do + expect(model).to receive(:transaction_open?).and_return(true) + + expect { model.remove_concurrent_index(:users, :foo) }. + to raise_error(RuntimeError) + end + end + end + describe '#add_concurrent_foreign_key' do context 'inside a transaction' do it 'raises an error' do diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb index 287bf62d9bd..6307f8c16a3 100644 --- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::SidekiqStatus::ClientMiddleware do describe '#call' do it 'tracks the job in Redis' do - expect(Gitlab::SidekiqStatus).to receive(:set).with('123') + expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION) described_class.new. call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil } diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb index 56f06b61afb..496e50fbae4 100644 --- a/spec/lib/gitlab/sidekiq_status_spec.rb +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -73,4 +73,17 @@ describe Gitlab::SidekiqStatus do expect(key).to include('123') end end + + describe 'completed', :redis do + it 'returns the completed job' do + expect(described_class.completed_jids(%w(123))).to eq(['123']) + end + + it 'returns only the jobs completed' do + described_class.set('123') + described_class.set('456') + + expect(described_class.completed_jids(%w(123 456 789))).to eq(['789']) + end + end end diff --git a/spec/mailers/previews/notify_preview.rb b/spec/mailers/previews/notify_preview.rb new file mode 100644 index 00000000000..0e1ccb5b847 --- /dev/null +++ b/spec/mailers/previews/notify_preview.rb @@ -0,0 +1,11 @@ +class NotifyPreview < ActionMailer::Preview + def pipeline_success_email + pipeline = Ci::Pipeline.last + Notify.pipeline_success_email(pipeline, pipeline.user.try(:email)) + end + + def pipeline_failed_email + pipeline = Ci::Pipeline.last + Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email)) + end +end diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 09b1fda3796..0f29766db41 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -111,6 +111,20 @@ describe Blob do end end + describe '#stl?' do + it 'is falsey with image extension' do + git_blob = Gitlab::Git::Blob.new(name: 'file.png') + + expect(described_class.decorate(git_blob)).not_to be_stl + end + + it 'is truthy with STL extension' do + git_blob = Gitlab::Git::Blob.new(name: 'file.stl') + + expect(described_class.decorate(git_blob)).to be_stl + end + end + describe '#to_partial_path' do let(:project) { double(lfs_enabled?: true) } @@ -122,7 +136,8 @@ describe Blob do lfs_pointer?: false, svg?: false, text?: false, - binary?: false + binary?: false, + stl?: false ) described_class.decorate(double).tap do |blob| @@ -175,6 +190,11 @@ describe Blob do blob = stubbed_blob(text?: true, sketch?: true, binary?: true) expect(blob.to_partial_path(project)).to eq 'sketch' end + + it 'handles STLs' do + blob = stubbed_blob(text?: true, stl?: true) + expect(blob.to_partial_path(project)).to eq 'stl' + end end describe '#size_within_svg_limits?' do diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 4f4b18cf0e0..e1beac28dab 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -108,6 +108,15 @@ describe API::DeployKeys, api: true do expect(response).to have_http_status(201) end + + it 'accepts can_push parameter' do + key_attrs = attributes_for :write_access_key + + post api("/projects/#{project.id}/deploy_keys", admin), key_attrs + + expect(response).to have_http_status(201) + expect(json_response['can_push']).to eq(true) + end end describe 'DELETE /projects/:id/deploy_keys/:key_id' do diff --git a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb new file mode 100644 index 00000000000..a714bf4e5d5 --- /dev/null +++ b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/remove_concurrent_index' + +describe RuboCop::Cop::Migration::RemoveConcurrentIndex do + include CopHelper + + subject(:cop) { described_class.new } + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when remove_concurrent_index is used inside a change method' do + inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + + it 'registers no offense when remove_concurrent_index is used inside an up method' do + inspect_source(cop, 'def up; remove_concurrent_index :table, :column; end') + + expect(cop.offenses.size).to eq(0) + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end') + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/rubocop/cop/migration/remove_index_spec.rb b/spec/rubocop/cop/migration/remove_index_spec.rb new file mode 100644 index 00000000000..31923cb7429 --- /dev/null +++ b/spec/rubocop/cop/migration/remove_index_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/remove_index' + +describe RuboCop::Cop::Migration::RemoveIndex do + include CopHelper + + subject(:cop) { described_class.new } + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when remove_index is used' do + inspect_source(cop, 'def change; remove_index :table, :column; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, 'def change; remove_index :table, :column; end') + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index c22d145ca5d..03215a4624a 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -49,6 +49,7 @@ describe MergeRequests::RefreshService, services: true do context 'push to origin repo source branch' do let(:refresh_service) { service.new(@project, @user) } + before do allow(refresh_service).to receive(:execute_hooks) refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') @@ -70,6 +71,32 @@ describe MergeRequests::RefreshService, services: true do end end + context 'push to origin repo source branch when an MR was reopened' do + let(:refresh_service) { service.new(@project, @user) } + + before do + @merge_request.update(state: :reopened) + + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it 'executes hooks with update action' do + expect(refresh_service).to have_received(:execute_hooks). + with(@merge_request, 'update', @oldrev) + + expect(@merge_request.notes).not_to be_empty + expect(@merge_request).to be_open + expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey + expect(@merge_request.diff_head_sha).to eq(@newrev) + expect(@fork_merge_request).to be_open + expect(@fork_merge_request.notes).to be_empty + expect(@build_failed_todo).to be_done + expect(@fork_build_failed_todo).to be_done + end + end + context 'push to origin repo target branch' do before do service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb new file mode 100644 index 00000000000..f627f9165fb --- /dev/null +++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'notify/pipeline_failed_email.html.haml' do + include Devise::Test::ControllerHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + user: user, + ref: project.default_branch, + sha: project.commit.sha, + status: :success) + end + + before do + assign(:project, project) + assign(:pipeline, pipeline) + assign(:merge_request, merge_request) + end + + context 'pipeline with user' do + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has failed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content pipeline.user.name + end + end + + context 'pipeline without user' do + before do + pipeline.update_attribute(:user, nil) + end + + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has failed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content "by API" + end + end +end diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb new file mode 100644 index 00000000000..ecd096ee579 --- /dev/null +++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'notify/pipeline_success_email.html.haml' do + include Devise::Test::ControllerHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + user: user, + ref: project.default_branch, + sha: project.commit.sha, + status: :success) + end + + before do + assign(:project, project) + assign(:pipeline, pipeline) + assign(:merge_request, merge_request) + end + + context 'pipeline with user' do + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has passed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content pipeline.user.name + end + end + + context 'pipeline without user' do + before do + pipeline.update_attribute(:user, nil) + end + + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has passed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content "by API" + end + end +end diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index fbb22439f33..5a2c0671dac 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -23,10 +23,12 @@ describe RepositoryImportWorker do error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found } expect_any_instance_of(Projects::ImportService).to receive(:execute). and_return({ status: :error, message: error }) + allow(subject).to receive(:jid).and_return('123') subject.perform(project.id) expect(project.reload.import_error).to include("https://*****:*****@test.com/root/repoC.git/") + expect(project.reload.import_jid).not_to be_nil end end end diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb new file mode 100644 index 00000000000..466277a5e5e --- /dev/null +++ b/spec/workers/stuck_import_jobs_worker_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe StuckImportJobsWorker do + let(:worker) { described_class.new } + let(:exclusive_lease_uuid) { SecureRandom.uuid } + + before do + allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid) + end + + describe 'long running import' do + let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') } + + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(['123']) + end + + it 'marks the project as failed' do + expect { worker.perform }.to change { project.reload.import_status }.to('failed') + end + end + + describe 'running import' do + let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') } + + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([]) + end + + it 'does not mark the project as failed' do + worker.perform + + expect(project.reload.import_status).to eq('started') + end + end +end diff --git a/yarn.lock b/yarn.lock index 65eef75af1a..9f2b8fe3d6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4305,6 +4305,18 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +three-orbit-controls@^82.1.0: + version "82.1.0" + resolved "https://registry.yarnpkg.com/three-orbit-controls/-/three-orbit-controls-82.1.0.tgz#11a7f33d0a20ecec98f098b37780f6537374fab4" + +three-stl-loader@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/three-stl-loader/-/three-stl-loader-1.0.4.tgz#6b3319a31e3b910aab1883d19b00c81a663c3e03" + +three@^0.84.0: + version "0.84.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.84.0.tgz#95be85a55a0fa002aa625ed559130957dcffd918" + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" |