From 708ee0bcb2c20cc73db53c092a26f916139d15d4 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Sep 2020 18:10:12 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../components/sidebar/board_editable_item.vue | 79 +++++++++++++++++++++ .../cycle_analytics/cycle_analytics_store.js | 3 - .../javascripts/groups/members/components/app.vue | 15 ---- app/assets/javascripts/groups/members/index.js | 34 +++++---- .../pipelines/components/graph/job_item.vue | 3 +- .../releases/components/releases_pagination.vue | 20 ++++++ .../components/releases_pagination_graphql.vue | 35 ++++++++++ .../components/releases_pagination_rest.vue | 24 +++++++ app/assets/javascripts/releases/mount_edit.js | 3 + app/assets/javascripts/releases/mount_index.js | 7 +- app/assets/javascripts/releases/mount_new.js | 3 + app/assets/javascripts/releases/mount_show.js | 3 + app/assets/javascripts/releases/stores/index.js | 3 - .../releases/stores/modules/list/index.js | 8 +-- .../releases/stores/modules/list/state.js | 14 +++- .../vuex_shared/modules/members/index.js | 6 ++ .../vuex_shared/modules/members/state.js | 5 ++ app/graphql/resolvers/projects_resolver.rb | 10 ++- app/models/cycle_analytics/level_base.rb | 2 +- app/models/project.rb | 6 ++ app/models/remote_mirror.rb | 4 ++ app/services/lfs/push_service.rb | 80 ++++++++++++++++++++++ .../projects/update_remote_mirror_service.rb | 20 ++++++ app/views/groups/group_members/index.html.haml | 2 +- app/workers/update_merge_requests_worker.rb | 2 - 25 files changed, 339 insertions(+), 52 deletions(-) create mode 100644 app/assets/javascripts/boards/components/sidebar/board_editable_item.vue create mode 100644 app/assets/javascripts/releases/components/releases_pagination.vue create mode 100644 app/assets/javascripts/releases/components/releases_pagination_graphql.vue create mode 100644 app/assets/javascripts/releases/components/releases_pagination_rest.vue create mode 100644 app/assets/javascripts/vuex_shared/modules/members/index.js create mode 100644 app/assets/javascripts/vuex_shared/modules/members/state.js create mode 100644 app/services/lfs/push_service.rb (limited to 'app') diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue new file mode 100644 index 00000000000..8df03ea581f --- /dev/null +++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue @@ -0,0 +1,79 @@ + + + diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js index 4f9069f61a5..3a160d0532c 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js @@ -23,9 +23,6 @@ const EMPTY_STAGE_TEXTS = { staging: __( 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.', ), - production: __( - 'The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.', - ), }; export default { diff --git a/app/assets/javascripts/groups/members/components/app.vue b/app/assets/javascripts/groups/members/components/app.vue index e8570f7246f..e94b28f5773 100644 --- a/app/assets/javascripts/groups/members/components/app.vue +++ b/app/assets/javascripts/groups/members/components/app.vue @@ -1,21 +1,6 @@ diff --git a/app/assets/javascripts/groups/members/index.js b/app/assets/javascripts/groups/members/index.js index 68fab42b543..4ca1756f10c 100644 --- a/app/assets/javascripts/groups/members/index.js +++ b/app/assets/javascripts/groups/members/index.js @@ -1,5 +1,7 @@ import Vue from 'vue'; +import Vuex from 'vuex'; import App from './components/app.vue'; +import membersModule from '~/vuex_shared/modules/members'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; export default el => { @@ -7,26 +9,22 @@ export default el => { return () => {}; } + Vue.use(Vuex); + + const { members, groupId } = el.dataset; + + const store = new Vuex.Store({ + ...membersModule({ + members: convertObjectPropsToCamelCase(JSON.parse(members), { deep: true }), + sourceId: parseInt(groupId, 10), + currentUserId: gon.current_user_id || null, + }), + }); + return new Vue({ el, components: { App }, - data() { - const { members, groupId, currentUserId } = this.$options.el.dataset; - - return { - members: convertObjectPropsToCamelCase(JSON.parse(members), { deep: true }), - groupId: parseInt(groupId, 10), - ...(currentUserId ? { currentUserId: parseInt(currentUserId, 10) } : {}), - }; - }, - render(createElement) { - return createElement('app', { - props: { - members: this.members, - groupId: this.groupId, - currentUserId: this.currentUserId, - }, - }); - }, + store, + render: createElement => createElement('app'), }); }; diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue index b04dd967d80..0fe0b671273 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_item.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue @@ -132,8 +132,9 @@ export default { v-gl-tooltip="{ boundary, placement: 'bottom' }" :href="status.details_path" :title="tooltipText" - :class="cssClassJobName" + :class="jobClasses" class="js-pipeline-graph-job-link qa-job-link menu-item" + data-testid="job-with-link" > diff --git a/app/assets/javascripts/releases/components/releases_pagination.vue b/app/assets/javascripts/releases/components/releases_pagination.vue new file mode 100644 index 00000000000..062c72b445b --- /dev/null +++ b/app/assets/javascripts/releases/components/releases_pagination.vue @@ -0,0 +1,20 @@ + + + diff --git a/app/assets/javascripts/releases/components/releases_pagination_graphql.vue b/app/assets/javascripts/releases/components/releases_pagination_graphql.vue new file mode 100644 index 00000000000..50d2796b0bd --- /dev/null +++ b/app/assets/javascripts/releases/components/releases_pagination_graphql.vue @@ -0,0 +1,35 @@ + + diff --git a/app/assets/javascripts/releases/components/releases_pagination_rest.vue b/app/assets/javascripts/releases/components/releases_pagination_rest.vue new file mode 100644 index 00000000000..52e88f5dc9b --- /dev/null +++ b/app/assets/javascripts/releases/components/releases_pagination_rest.vue @@ -0,0 +1,24 @@ + + + diff --git a/app/assets/javascripts/releases/mount_edit.js b/app/assets/javascripts/releases/mount_edit.js index c7385b3c57f..623b18591a0 100644 --- a/app/assets/javascripts/releases/mount_edit.js +++ b/app/assets/javascripts/releases/mount_edit.js @@ -1,8 +1,11 @@ import Vue from 'vue'; +import Vuex from 'vuex'; import ReleaseEditNewApp from './components/app_edit_new.vue'; import createStore from './stores'; import createDetailModule from './stores/modules/detail'; +Vue.use(Vuex); + export default () => { const el = document.getElementById('js-edit-release-page'); diff --git a/app/assets/javascripts/releases/mount_index.js b/app/assets/javascripts/releases/mount_index.js index 4023b905504..c193cb9de9f 100644 --- a/app/assets/javascripts/releases/mount_index.js +++ b/app/assets/javascripts/releases/mount_index.js @@ -1,7 +1,10 @@ import Vue from 'vue'; +import Vuex from 'vuex'; import ReleaseListApp from './components/app_index.vue'; import createStore from './stores'; -import listModule from './stores/modules/list'; +import createListModule from './stores/modules/list'; + +Vue.use(Vuex); export default () => { const el = document.getElementById('js-releases-page'); @@ -10,7 +13,7 @@ export default () => { el, store: createStore({ modules: { - list: listModule, + list: createListModule(el.dataset), }, featureFlags: { graphqlReleaseData: Boolean(gon.features?.graphqlReleaseData), diff --git a/app/assets/javascripts/releases/mount_new.js b/app/assets/javascripts/releases/mount_new.js index 68003f6a346..10725e47740 100644 --- a/app/assets/javascripts/releases/mount_new.js +++ b/app/assets/javascripts/releases/mount_new.js @@ -1,8 +1,11 @@ import Vue from 'vue'; +import Vuex from 'vuex'; import ReleaseEditNewApp from './components/app_edit_new.vue'; import createStore from './stores'; import createDetailModule from './stores/modules/detail'; +Vue.use(Vuex); + export default () => { const el = document.getElementById('js-new-release-page'); diff --git a/app/assets/javascripts/releases/mount_show.js b/app/assets/javascripts/releases/mount_show.js index 7ddc8e786c1..eef015ee0a6 100644 --- a/app/assets/javascripts/releases/mount_show.js +++ b/app/assets/javascripts/releases/mount_show.js @@ -1,8 +1,11 @@ import Vue from 'vue'; +import Vuex from 'vuex'; import ReleaseShowApp from './components/app_show.vue'; import createStore from './stores'; import createDetailModule from './stores/modules/detail'; +Vue.use(Vuex); + export default () => { const el = document.getElementById('js-show-release-page'); diff --git a/app/assets/javascripts/releases/stores/index.js b/app/assets/javascripts/releases/stores/index.js index 7f211145ccf..b2e93d789d7 100644 --- a/app/assets/javascripts/releases/stores/index.js +++ b/app/assets/javascripts/releases/stores/index.js @@ -1,8 +1,5 @@ -import Vue from 'vue'; import Vuex from 'vuex'; -Vue.use(Vuex); - export default ({ modules, featureFlags }) => new Vuex.Store({ modules, diff --git a/app/assets/javascripts/releases/stores/modules/list/index.js b/app/assets/javascripts/releases/stores/modules/list/index.js index e4633b15a0c..0f97fa83ced 100644 --- a/app/assets/javascripts/releases/stores/modules/list/index.js +++ b/app/assets/javascripts/releases/stores/modules/list/index.js @@ -1,10 +1,10 @@ -import state from './state'; +import createState from './state'; import * as actions from './actions'; import mutations from './mutations'; -export default { +export default initialState => ({ namespaced: true, actions, mutations, - state, -}; + state: createState(initialState), +}); diff --git a/app/assets/javascripts/releases/stores/modules/list/state.js b/app/assets/javascripts/releases/stores/modules/list/state.js index c251f56c9c5..9fe313745fc 100644 --- a/app/assets/javascripts/releases/stores/modules/list/state.js +++ b/app/assets/javascripts/releases/stores/modules/list/state.js @@ -1,4 +1,16 @@ -export default () => ({ +export default ({ + projectId, + projectPath, + documentationPath, + illustrationPath, + newReleasePath = '', +}) => ({ + projectId, + projectPath, + documentationPath, + illustrationPath, + newReleasePath, + isLoading: false, hasError: false, releases: [], diff --git a/app/assets/javascripts/vuex_shared/modules/members/index.js b/app/assets/javascripts/vuex_shared/modules/members/index.js new file mode 100644 index 00000000000..ec6a94178f3 --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/members/index.js @@ -0,0 +1,6 @@ +import createState from './state'; + +export default initialState => ({ + namespaced: true, + state: createState(initialState), +}); diff --git a/app/assets/javascripts/vuex_shared/modules/members/state.js b/app/assets/javascripts/vuex_shared/modules/members/state.js new file mode 100644 index 00000000000..1511961245c --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/members/state.js @@ -0,0 +1,5 @@ +export default ({ members, sourceId, currentUserId }) => ({ + members, + sourceId, + currentUserId, +}); diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb index f75f591b381..3bbadf87a71 100644 --- a/app/graphql/resolvers/projects_resolver.rb +++ b/app/graphql/resolvers/projects_resolver.rb @@ -12,9 +12,13 @@ module Resolvers required: false, description: 'Search query for project name, path, or description' + argument :ids, [GraphQL::ID_TYPE], + required: false, + description: 'Filter projects by IDs' + def resolve(**args) ProjectsFinder - .new(current_user: current_user, params: project_finder_params(args)) + .new(current_user: current_user, params: project_finder_params(args), project_ids_relation: parse_gids(args[:ids])) .execute end @@ -27,5 +31,9 @@ module Resolvers search: params[:search] }.compact end + + def parse_gids(gids) + gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Project).model_id } + end end end diff --git a/app/models/cycle_analytics/level_base.rb b/app/models/cycle_analytics/level_base.rb index 543349ebf8f..967de9a22b4 100644 --- a/app/models/cycle_analytics/level_base.rb +++ b/app/models/cycle_analytics/level_base.rb @@ -2,7 +2,7 @@ module CycleAnalytics module LevelBase - STAGES = %i[issue plan code test review staging production].freeze + STAGES = %i[issue plan code test review staging].freeze def all_medians_by_stage STAGES.each_with_object({}) do |stage_name, medians_per_stage| diff --git a/app/models/project.rb b/app/models/project.rb index b931f145260..c7ef8db5801 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1469,6 +1469,12 @@ class Project < ApplicationRecord forked_from_project || fork_network&.root_project end + def lfs_objects_for_repository_types(*types) + LfsObject + .joins(:lfs_objects_projects) + .where(lfs_objects_projects: { project: self, repository_type: types }) + end + def lfs_objects_oids(oids: []) oids(lfs_objects, oids: oids) end diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index 8b15d481c1b..6b8b34ce4d2 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -210,6 +210,10 @@ class RemoteMirror < ApplicationRecord super(usernames_whitelist: %w[git]) end + def bare_url + Gitlab::UrlSanitizer.new(read_attribute(:url)).full_url + end + def ensure_remote! return unless project return unless remote_name && remote_url diff --git a/app/services/lfs/push_service.rb b/app/services/lfs/push_service.rb new file mode 100644 index 00000000000..6e1a11ebff8 --- /dev/null +++ b/app/services/lfs/push_service.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Lfs + # Lfs::PushService pushes the LFS objects associated with a project to a + # remote URL + class PushService < BaseService + include Gitlab::Utils::StrongMemoize + + # Match the canonical LFS client's batch size: + # https://github.com/git-lfs/git-lfs/blob/master/tq/transfer_queue.go#L19 + BATCH_SIZE = 100 + + def execute + lfs_objects_relation.each_batch(of: BATCH_SIZE) do |objects| + push_objects(objects) + end + + success + rescue => err + error(err.message) + end + + private + + # Currently we only set repository_type for design repository objects, so + # push mirroring must send objects with a `nil` repository type - but if the + # wiki repository uses LFS, its objects will also be sent. This will be + # addressed by https://gitlab.com/gitlab-org/gitlab/-/issues/250346 + def lfs_objects_relation + project.lfs_objects_for_repository_types(nil, :project) + end + + def push_objects(objects) + rsp = lfs_client.batch('upload', objects) + objects = objects.index_by(&:oid) + + rsp.fetch('objects', []).each do |spec| + actions = spec['actions'] + object = objects[spec['oid']] + + upload_object!(object, spec) if actions&.key?('upload') + verify_object!(object, spec) if actions&.key?('verify') + end + end + + def upload_object!(object, spec) + authenticated = spec['authenticated'] + upload = spec.dig('actions', 'upload') + + # The server wants us to upload the object but something is wrong + unless object && object.size == spec['size'].to_i + log_error("Couldn't match object #{spec['oid']}/#{spec['size']}") + return + end + + lfs_client.upload(object, upload, authenticated: authenticated) + end + + def verify_object!(object, spec) + # TODO: the remote has requested that we make another call to verify that + # the object has been sent correctly. + # https://gitlab.com/gitlab-org/gitlab/-/issues/250654 + log_error("LFS upload verification requested, but not supported for #{object.oid}") + end + + def url + params.fetch(:url) + end + + def credentials + params.fetch(:credentials) + end + + def lfs_client + strong_memoize(:lfs_client) do + Gitlab::Lfs::Client.new(url, credentials: credentials) + end + end + end +end diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb index 7961f689259..5c41f00aac2 100644 --- a/app/services/projects/update_remote_mirror_service.rb +++ b/app/services/projects/update_remote_mirror_service.rb @@ -31,6 +31,9 @@ module Projects remote_mirror.update_start! remote_mirror.ensure_remote! + # LFS objects must be sent first, or the push has dangling pointers + send_lfs_objects!(remote_mirror) + response = remote_mirror.update_repository if response.divergent_refs.any? @@ -43,6 +46,23 @@ module Projects end end + def send_lfs_objects!(remote_mirror) + return unless Feature.enabled?(:push_mirror_syncs_lfs, project) + return unless project.lfs_enabled? + + # TODO: Support LFS sync over SSH + # https://gitlab.com/gitlab-org/gitlab/-/issues/249587 + return unless remote_mirror.url =~ /\Ahttps?:\/\//i + return unless remote_mirror.password_auth? + + Lfs::PushService.new( + project, + current_user, + url: remote_mirror.bare_url, + credentials: remote_mirror.credentials + ).execute + end + def retry_or_fail(mirror, message, tries) if tries < MAX_TRIES mirror.mark_for_retry!(message) diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 54da3e56ea8..ed7b201323a 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -4,7 +4,7 @@ - show_access_requests = can_manage_members && @requesters.exists? - invited_active = params[:search_invited].present? || params[:invited_members_page].present? - vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group) -- data_attributes = { group_id: @group.id, current_user_id: current_user&.id } +- data_attributes = { group_id: @group.id } - form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center' diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb index 98534b258a7..402c1777662 100644 --- a/app/workers/update_merge_requests_worker.rb +++ b/app/workers/update_merge_requests_worker.rb @@ -9,8 +9,6 @@ class UpdateMergeRequestsWorker # rubocop:disable Scalability/IdempotentWorker weight 3 loggable_arguments 2, 3, 4 - LOG_TIME_THRESHOLD = 90 # seconds - # rubocop: disable CodeReuse/ActiveRecord def perform(project_id, user_id, oldrev, newrev, ref) project = Project.find_by(id: project_id) -- cgit v1.2.1