Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fcfe959a5d
commit
0914eb1a9c
|
@ -1,9 +1,10 @@
|
|||
workhorse:verify:
|
||||
extends: .workhorse:rules:workhorse
|
||||
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.18
|
||||
image: ${GITLAB_DEPENDENCY_PROXY}golang:${GO_VERSION}
|
||||
stage: test
|
||||
needs: []
|
||||
script:
|
||||
- go version
|
||||
- make -C workhorse # test build
|
||||
- make -C workhorse verify
|
||||
|
||||
|
@ -12,7 +13,6 @@ workhorse:verify:
|
|||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}:git-2.36
|
||||
variables:
|
||||
GITALY_ADDRESS: "tcp://127.0.0.1:8075"
|
||||
GO_VERSION: "1.18"
|
||||
stage: test
|
||||
needs:
|
||||
- setup-test-env
|
||||
|
@ -27,7 +27,7 @@ workhorse:test go:
|
|||
extends: .workhorse:test
|
||||
parallel:
|
||||
matrix:
|
||||
- GO_VERSION: ["1.17", "1.18"]
|
||||
- GO_VERSION: ["1.17", "1.18", "1.19"]
|
||||
script:
|
||||
- make -C workhorse test-coverage
|
||||
coverage: '/\d+.\d+%/'
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -357,7 +357,7 @@ group :development do
|
|||
gem 'solargraph', '~> 0.46.0', require: false
|
||||
|
||||
gem 'letter_opener_web', '~> 2.0.0'
|
||||
gem 'lookbook', '~> 1.0'
|
||||
gem 'lookbook', '~> 1.0', '>= 1.0.8'
|
||||
|
||||
# Better errors handler
|
||||
gem 'better_errors', '~> 2.9.1'
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{"name":"activestorage","version":"6.1.6.1","platform":"ruby","checksum":"3fbf4c355a69a46e14676004ad8e06245bdce7f96858e72782715218326aafc5"},
|
||||
{"name":"activesupport","version":"6.1.6.1","platform":"ruby","checksum":"5fc9fd6fe6f755e7523bb3aaf4370fb91a8416b39e3202939fd8bded4fec606d"},
|
||||
{"name":"acts-as-taggable-on","version":"9.0.0","platform":"ruby","checksum":"5a409be0eae125b7b02c1a7316264b40d4a583584a13d4ea4a6d82acdb351b86"},
|
||||
{"name":"addressable","version":"2.8.0","platform":"ruby","checksum":"f76d29d2d1f54b6c6a49aec58f9583b08d97e088c227a3fcba92f6c6531d5908"},
|
||||
{"name":"addressable","version":"2.8.1","platform":"ruby","checksum":"bc724a176ef02118c8a3ed6b5c04c39cf59209607ffcce77b91d0261dbadedfa"},
|
||||
{"name":"aes_key_wrap","version":"1.1.0","platform":"ruby","checksum":"b935f4756b37375895db45669e79dfcdc0f7901e12d4e08974d5540c8e0776a5"},
|
||||
{"name":"akismet","version":"3.0.0","platform":"ruby","checksum":"74991b8e3d3257eeea996b47069abb8da2006c84a144255123e8dffd1c86b230"},
|
||||
{"name":"android_key_attestation","version":"0.3.0","platform":"ruby","checksum":"467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b"},
|
||||
|
@ -90,7 +90,7 @@
|
|||
{"name":"crass","version":"1.0.6","platform":"ruby","checksum":"dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d"},
|
||||
{"name":"creole","version":"0.5.0","platform":"ruby","checksum":"951701e2d80760f156b1cb2a93471ca97c076289becc067a33b745133ed32c03"},
|
||||
{"name":"crystalball","version":"0.7.0","platform":"ruby","checksum":"6e729f372a5071daec877adb40c5df4cb25fe21f350635e2a9624373fc151ef2"},
|
||||
{"name":"css_parser","version":"1.11.0","platform":"ruby","checksum":"568926c3193579446ad3e3f9d761c73e2918ee5b3b7757a1a49ec166c67d6de1"},
|
||||
{"name":"css_parser","version":"1.12.0","platform":"ruby","checksum":"8b7c04bca32257da0c65bd7b1fa585df5a0fd9f5197ccd78498d5598dd900784"},
|
||||
{"name":"cvss-suite","version":"3.0.1","platform":"ruby","checksum":"b5ca9e9e94032a42fd0dc28c1e305378b62c949e35ed7111fc4a1d76f68ad3f9"},
|
||||
{"name":"danger","version":"8.6.1","platform":"ruby","checksum":"d95eb58b41f68d3aaa9bbef697916b6b4d161a38819517c98562531be75cdfd8"},
|
||||
{"name":"danger-gitlab","version":"8.0.0","platform":"ruby","checksum":"497dd7d0f6513913de651019223d8058cf494df10acbd17de92b175dfa04a3a8"},
|
||||
|
@ -309,7 +309,7 @@
|
|||
{"name":"lockbox","version":"0.6.2","platform":"ruby","checksum":"0136677875c3d6e27cef87cd7bd66610404e2b3cd7f07f1ac8ed34e48f18dc3c"},
|
||||
{"name":"lograge","version":"0.11.2","platform":"ruby","checksum":"4cbd1554b86f545d795eff15a0c24fd25057d2ac4e1caa5fc186168b3da932ef"},
|
||||
{"name":"loofah","version":"2.19.0","platform":"ruby","checksum":"302791371f473611e342f9e469e7f2fbf1155bb1b3a978a83ac7df625298feba"},
|
||||
{"name":"lookbook","version":"1.0.3","platform":"ruby","checksum":"c53e130a37588e32f66be3b9418b1efcb51bef69946b276edd3b4348a71cbcd6"},
|
||||
{"name":"lookbook","version":"1.0.8","platform":"ruby","checksum":"e4b8789c5ff25c6443394da1d6b62966642c324e19c42d7f4cf3b5da2fe75f77"},
|
||||
{"name":"lru_redux","version":"1.1.0","platform":"ruby","checksum":"ee71d0ccab164c51de146c27b480a68b3631d5b4297b8ffe8eda1c72de87affb"},
|
||||
{"name":"lumberjack","version":"1.2.7","platform":"ruby","checksum":"a5c6aae6b4234f1420dbcd80b23e3bca0817bd239440dde097ebe3fa63c63b1f"},
|
||||
{"name":"mail","version":"2.7.1","platform":"ruby","checksum":"ec2a3d489f7510b90d8eaa3f6abaad7038cf1d663cdf8ee66d0214a0bdf99c03"},
|
||||
|
@ -418,7 +418,7 @@
|
|||
{"name":"pry-byebug","version":"3.9.0","platform":"ruby","checksum":"3bba08f97fea15b89cc299f3b5136e3b85763cd18cf84960eac4fbfbeb2ede24"},
|
||||
{"name":"pry-rails","version":"0.3.9","platform":"ruby","checksum":"468662575abb6b67f4a9831219f99290d5eae7bf186e64dd810d0a3e4a8cc4b1"},
|
||||
{"name":"pry-shell","version":"0.5.1","platform":"ruby","checksum":"2b9000e30677acf5d66f55fa53d31934b7c069d9e0f738a0b84eed03a4ab677d"},
|
||||
{"name":"public_suffix","version":"4.0.7","platform":"ruby","checksum":"8be161e2421f8d45b0098c042c06486789731ea93dc3a896d30554ee38b573b8"},
|
||||
{"name":"public_suffix","version":"5.0.0","platform":"ruby","checksum":"26ee4fbce33ada25eb117ac71f2c24bf4d8b3414ab6b34f05b4708a3e90f1c6b"},
|
||||
{"name":"puma","version":"5.6.5","platform":"java","checksum":"29d78fc2bc070b9db285a3334a890c3e0ece9bb369388065f0f340ccb1e57faf"},
|
||||
{"name":"puma","version":"5.6.5","platform":"ruby","checksum":"661029d15a115e9f6c0641a69c830ffd9f1b9ac63fcd0791d94ccd900e03f863"},
|
||||
{"name":"puma_worker_killer","version":"0.3.1","platform":"ruby","checksum":"9c5534d296b5e92d1ad4a578f2daf2aa71563003c84f7263f0a8dfd22b5c614a"},
|
||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -155,8 +155,8 @@ GEM
|
|||
zeitwerk (~> 2.3)
|
||||
acts-as-taggable-on (9.0.0)
|
||||
activerecord (>= 6.0, < 7.1)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
akismet (3.0.0)
|
||||
android_key_attestation (0.3.0)
|
||||
|
@ -301,7 +301,7 @@ GEM
|
|||
creole (0.5.0)
|
||||
crystalball (0.7.0)
|
||||
git
|
||||
css_parser (1.11.0)
|
||||
css_parser (1.12.0)
|
||||
addressable
|
||||
cvss-suite (3.0.1)
|
||||
danger (8.6.1)
|
||||
|
@ -830,7 +830,7 @@ GEM
|
|||
loofah (2.19.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
lookbook (1.0.3)
|
||||
lookbook (1.0.8)
|
||||
actioncable
|
||||
css_parser
|
||||
htmlbeautifier (~> 1.3)
|
||||
|
@ -1048,7 +1048,7 @@ GEM
|
|||
pry (~> 0.13.0)
|
||||
tty-markdown
|
||||
tty-prompt
|
||||
public_suffix (4.0.7)
|
||||
public_suffix (5.0.0)
|
||||
puma (5.6.5)
|
||||
nio4r (~> 2.0)
|
||||
puma_worker_killer (0.3.1)
|
||||
|
@ -1671,7 +1671,7 @@ DEPENDENCIES
|
|||
lockbox (~> 0.6.2)
|
||||
lograge (~> 0.5)
|
||||
loofah (~> 2.19.0)
|
||||
lookbook (~> 1.0)
|
||||
lookbook (~> 1.0, >= 1.0.8)
|
||||
lru_redux
|
||||
mail (= 2.7.1)
|
||||
mail-smtp_pool (~> 0.1.0)!
|
||||
|
@ -1817,4 +1817,4 @@ DEPENDENCIES
|
|||
yajl-ruby (~> 1.4.3)
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.15
|
||||
2.3.22
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import initPipelineSchedulesFormApp from '~/pipeline_schedules/mount_pipeline_schedules_form_app';
|
||||
import initForm from '../shared/init_form';
|
||||
|
||||
if (gon.features?.pipelineSchedulesVue) {
|
||||
initPipelineSchedulesFormApp('#pipeline-schedules-form-edit');
|
||||
} else {
|
||||
initForm();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import initPipelineSchedulesApp from '~/pipeline_schedules/mount_pipeline_schedules_app';
|
||||
import PipelineSchedulesTakeOwnershipModal from '~/pipeline_schedules/components/take_ownership_modal.vue';
|
||||
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
|
||||
|
||||
function initPipelineSchedules() {
|
||||
function initPipelineSchedulesCallout() {
|
||||
const el = document.getElementById('pipeline-schedules-callout');
|
||||
|
||||
if (!el) {
|
||||
|
@ -15,6 +16,7 @@ function initPipelineSchedules() {
|
|||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'PipelineSchedulesCalloutRoot',
|
||||
provide: {
|
||||
docsUrl,
|
||||
illustrationUrl,
|
||||
|
@ -25,6 +27,8 @@ function initPipelineSchedules() {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: move take ownership feature into new Vue app
|
||||
// located in directory app/assets/javascripts/pipeline_schedules/components
|
||||
function initTakeownershipModal() {
|
||||
const modalId = 'pipeline-take-ownership-modal';
|
||||
const buttonSelector = 'js-take-ownership-button';
|
||||
|
@ -63,5 +67,10 @@ function initTakeownershipModal() {
|
|||
});
|
||||
}
|
||||
|
||||
initPipelineSchedules();
|
||||
initPipelineSchedulesCallout();
|
||||
|
||||
if (gon.features?.pipelineSchedulesVue) {
|
||||
initPipelineSchedulesApp();
|
||||
} else {
|
||||
initTakeownershipModal();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import initPipelineSchedulesFormApp from '~/pipeline_schedules/mount_pipeline_schedules_form_app';
|
||||
import initForm from '../shared/init_form';
|
||||
|
||||
if (gon.features?.pipelineSchedulesVue) {
|
||||
initPipelineSchedulesFormApp('#pipeline-schedules-form-new');
|
||||
} else {
|
||||
initForm();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
import PipelineSchedulesTable from './table/pipeline_schedules_table.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PipelineSchedulesTable,
|
||||
},
|
||||
inject: {
|
||||
fullPath: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<pipeline-schedules-table />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
import { GlForm } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlForm,
|
||||
},
|
||||
inject: {
|
||||
fullPath: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-form />
|
||||
</template>
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import { GlTableLite } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlTableLite,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-table-lite />
|
||||
</template>
|
|
@ -0,0 +1,32 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import PipelineSchedules from './components/pipeline_schedules.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
export default () => {
|
||||
const containerEl = document.querySelector('#pipeline-schedules-app');
|
||||
|
||||
if (!containerEl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { fullPath } = containerEl.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: containerEl,
|
||||
name: 'PipelineSchedulesRoot',
|
||||
apolloProvider,
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(PipelineSchedules);
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import PipelineSchedulesForm from './components/pipeline_schedules_form.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
export default (selector) => {
|
||||
const containerEl = document.querySelector(selector);
|
||||
|
||||
if (!containerEl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { fullPath } = containerEl.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: containerEl,
|
||||
name: 'PipelineSchedulesFormRoot',
|
||||
apolloProvider,
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(PipelineSchedulesForm);
|
||||
},
|
||||
});
|
||||
};
|
|
@ -10,6 +10,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
|||
before_action :authorize_update_pipeline_schedule!, only: [:edit, :update]
|
||||
before_action :authorize_take_ownership_pipeline_schedule!, only: [:take_ownership]
|
||||
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
|
||||
before_action :push_schedule_feature_flag, only: [:index, :new, :edit]
|
||||
|
||||
feature_category :continuous_integration
|
||||
urgency :low
|
||||
|
@ -115,4 +116,8 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
|||
def authorize_admin_pipeline_schedule!
|
||||
return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
|
||||
end
|
||||
|
||||
def push_schedule_feature_flag
|
||||
push_frontend_feature_flag(:pipeline_schedules_vue, @project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,8 +5,6 @@ module Integrations
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# TODO: Once we rename the tables we can't rely on `table_name` anymore.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/331953
|
||||
belongs_to :integration, inverse_of: self.table_name.to_sym, foreign_key: :integration_id
|
||||
|
||||
validates :integration, presence: true
|
||||
|
|
|
@ -7,4 +7,7 @@
|
|||
= _("Edit Pipeline Schedule")
|
||||
%hr
|
||||
|
||||
- if Feature.enabled?(:pipeline_schedules_vue, @project)
|
||||
#pipeline-schedules-form-edit{ data: { full_path: @project.full_path } }
|
||||
- else
|
||||
= render "form"
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
- add_page_specific_style 'page_bundles/pipeline_schedules'
|
||||
|
||||
#pipeline-schedules-callout{ data: { docs_url: help_page_path('ci/pipelines/schedules'), illustration_url: image_path('illustrations/pipeline_schedule_callout.svg') } }
|
||||
|
||||
- if Feature.enabled?(:pipeline_schedules_vue, @project)
|
||||
#pipeline-schedules-app{ data: { full_path: @project.full_path } }
|
||||
- else
|
||||
.top-area
|
||||
- schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) }
|
||||
= render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
|
||||
|
|
|
@ -8,4 +8,7 @@
|
|||
%h1.page-title.gl-font-size-h-display
|
||||
= _("Schedule a new pipeline")
|
||||
|
||||
- if Feature.enabled?(:pipeline_schedules_vue, @project)
|
||||
#pipeline-schedules-form-new{ data: { full_path: @project.full_path } }
|
||||
- else
|
||||
= render "form"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: pipeline_schedules_vue
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99155
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375139
|
||||
milestone: '15.5'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
default_enabled: false
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveIndexForRequestedNonInvitedAwaitingMembers < Gitlab::Database::Migration[2.0]
|
||||
INDEX_NAME = 'index_members_on_non_requested_non_invited_and_state_awaiting'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :members, INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
clause = '((requested_at IS NULL) AND (invite_token IS NULL) AND (access_level > 5) AND (state = 1))'
|
||||
|
||||
add_concurrent_index :members, :source_id, where: clause, name: INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
e1106d4b77704a1ac4c185e0a6b500966dc61f46569de55650875aa6a89b7f9d
|
|
@ -29229,8 +29229,6 @@ CREATE INDEX index_members_on_member_namespace_id ON members USING btree (member
|
|||
|
||||
CREATE INDEX index_members_on_member_role_id ON members USING btree (member_role_id);
|
||||
|
||||
CREATE INDEX index_members_on_non_requested_non_invited_and_state_awaiting ON members USING btree (source_id) WHERE ((requested_at IS NULL) AND (invite_token IS NULL) AND (access_level > 5) AND (state = 1));
|
||||
|
||||
CREATE INDEX index_members_on_requested_at ON members USING btree (requested_at);
|
||||
|
||||
CREATE INDEX index_members_on_source_id_and_source_type ON members USING btree (source_id, source_type);
|
||||
|
|
|
@ -135,40 +135,70 @@ API endpoints have the same abstraction level as controllers.
|
|||
|
||||
Everything that resides in `app/services`.
|
||||
|
||||
Services should consider inheriting from:
|
||||
Service classes represent operations that coordinates changes between models
|
||||
(such as entities and value objects). Changes impact the state of the application.
|
||||
|
||||
- `BaseContainerService` for services scoped by container (project or group)
|
||||
- `BaseProjectService` for services scoped to projects
|
||||
- `BaseGroupService` for services scoped to groups
|
||||
1. When an object makes no changes to the state of the application, then it's not a service.
|
||||
It may be a [finder](#finders) or a value object.
|
||||
1. When there is no operation, there is no need to execute a service. The class would
|
||||
probably be better designed as an entity, a value object, or a policy.
|
||||
|
||||
or, create a new base class and update the list above.
|
||||
When implementing a service class, consider:
|
||||
|
||||
Legacy classes inherited from `BaseService` for historical reasons.
|
||||
|
||||
In Service classes the use of `execute` and `#execute` is preferred over `call` and `#call`.
|
||||
|
||||
Model properties should be passed to the constructor in a `params` hash, and will be assigned directly.
|
||||
|
||||
To pass extra parameters (which need to be processed, and are not model properties),
|
||||
include an `options` hash in the constructor and store it in an instance variable:
|
||||
1. A service class initializer should contain in its arguments:
|
||||
1. A [model](#models) instance that is being acted upon. Should be first positional
|
||||
argument of the initializer. The argument name of the argument is left to the
|
||||
developer's discretion, such as: `issue`, `project`, `merge_request`.
|
||||
1. When service represents an action initiated by a user or executed in the
|
||||
context of a user, the initializer must have the `current_user:` keyword argument.
|
||||
Services with `current_user:` argument run high-level business logic.
|
||||
1. When service does not have a user context and it's not directly initiated
|
||||
by a user (like background service or side-effects), the `current_user:`
|
||||
argument is not needed. This describes low-level domain logic or instance-wide logic.
|
||||
1. For all additional data required by a service, the explicit keyword arguments are recommended.
|
||||
When a service requires too long of a list of arguments, consider splitting them into:
|
||||
- `params`: A hash with model properties that will be assigned directly.
|
||||
- `options`: A hash with extra parameters (which need to be processed,
|
||||
and are not model properties). The `options` hash should be stored in an instance variable.
|
||||
|
||||
```ruby
|
||||
# container: Project, or Group
|
||||
# current_user: Current user
|
||||
# params: Model properties from the controller, already allowlisted with strong parameters
|
||||
# options: Configuration for this service, can be any of the following:
|
||||
# notify: Whether to send a notifcation to the current user
|
||||
# cc: Email address to copy when sending a notification
|
||||
def initialize(container:, current_user: nil, params: {}, options: {})
|
||||
super(container, current_user, params)
|
||||
# merge_request: A model instance that is being acted upon.
|
||||
# assignee: new MR assignee that will be assigned to the MR
|
||||
# after the service is executed.
|
||||
def initialize(merge_request, assignee:)
|
||||
@merge_request = merge_request
|
||||
@assignee = assignee
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
# issue: A model instance that is being acted upon.
|
||||
# current_user: Current user.
|
||||
# params: Model properties.
|
||||
# options: Configuration for this service. Can be any of the following:
|
||||
# - notify: Whether to send a notification to the current user.
|
||||
# - cc: Email address to copy when sending a notification.
|
||||
def initialize(issue:, current_user:, params: {}, options: {})
|
||||
@issue = issue
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
@options = options
|
||||
end
|
||||
```
|
||||
|
||||
View the [initial discussion](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90008#note_988744060)
|
||||
and [further discussion](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90853#note_1053425083).
|
||||
1. It implements a single public instance method `#execute`, which invokes service class behavior:
|
||||
- The `#execute` method takes no arguments. All required data is passed into initializer.
|
||||
- Optional. If needed, the `#execute` method returns its result via [`ServiceResponse`](#serviceresponse).
|
||||
|
||||
Classes that are not service objects should be [created elsewhere](directory_structure.md#use-namespaces-to-define-bounded-contexts), such as in `lib`.
|
||||
Several base classes implement the service classes convention. You may consider inheriting from:
|
||||
|
||||
- `BaseContainerService` for services scoped by container (project or group).
|
||||
- `BaseProjectService` for services scoped to projects.
|
||||
- `BaseGroupService` for services scoped to groups.
|
||||
|
||||
Classes that are not service objects should be
|
||||
[created elsewhere](directory_structure.md#use-namespaces-to-define-bounded-contexts),
|
||||
such as in `lib`.
|
||||
|
||||
#### ServiceResponse
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ disqus_identifier: 'https://docs.gitlab.com/ee/gitlab-basics/add-merge-request.h
|
|||
|
||||
There are many different ways to create a merge request.
|
||||
|
||||
NOTE:
|
||||
Use [branch naming patterns](../repository/branches/index.md#naming) to streamline merge request creation.
|
||||
|
||||
## From the merge request list
|
||||
|
||||
You can create a merge request from the list of merge requests.
|
||||
|
|
|
@ -13,9 +13,10 @@ other.
|
|||
|
||||
After pushing your changes to a new branch, you can:
|
||||
|
||||
- Create a [merge request](../../merge_requests/index.md)
|
||||
- Perform inline code review
|
||||
- [Discuss](../../../discussions/index.md) your implementation with your team
|
||||
- Create a [merge request](../../merge_requests/index.md). You can streamline this process
|
||||
by following [branch naming patterns](#naming).
|
||||
- Perform inline code review.
|
||||
- [Discuss](../../../discussions/index.md) your implementation with your team.
|
||||
- Preview changes submitted to a new branch with [Review Apps](../../../../ci/review_apps/index.md).
|
||||
|
||||
You can also request [approval](../../merge_requests/approvals/index.md)
|
||||
|
@ -42,6 +43,18 @@ See also:
|
|||
- [GitLab Flow](../../../../topics/gitlab_flow.md) documentation.
|
||||
- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
|
||||
|
||||
## Naming
|
||||
|
||||
Prefix a branch name with an issue number to streamline merge request creation.
|
||||
When you create a merge request for a branch with a name beginning with an issue
|
||||
number, GitLab:
|
||||
|
||||
- Marks the issue as related. If your project is configured with a
|
||||
[default closing pattern](../../issues/managing_issues.md#default-closing-pattern),
|
||||
merging this merge request [also closes](../../issues/managing_issues.md#closing-issues-automatically)
|
||||
the related issue.
|
||||
- Copies label and milestone metadata from the issue.
|
||||
|
||||
## Compare
|
||||
|
||||
To compare branches in a repository:
|
||||
|
|
|
@ -115,6 +115,9 @@ the target branch. Select **Create directory** to finish.
|
|||
|
||||
There are multiple ways to create a branch from the GitLab web interface.
|
||||
|
||||
NOTE:
|
||||
Use [branch naming patterns](branches/index.md#naming) to streamline merge request creation.
|
||||
|
||||
### Create a new branch from an issue
|
||||
|
||||
> The **Create merge request** button [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/349566) to open the merge request creation form in GitLab 14.8.
|
||||
|
|
|
@ -11,6 +11,10 @@ RSpec.describe 'Pipeline Schedules', :js do
|
|||
let(:scope) { nil }
|
||||
let!(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(pipeline_schedules_vue: false)
|
||||
end
|
||||
|
||||
context 'logged in as the pipeline schedule owner' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
|
|
@ -735,6 +735,8 @@ RSpec.describe 'Pipeline', :js do
|
|||
end
|
||||
|
||||
it 'displays the PipelineSchedule in an inactive state' do
|
||||
stub_feature_flags(pipeline_schedules_vue: false)
|
||||
|
||||
visit project_pipeline_schedules_path(project)
|
||||
page.click_link('Inactive')
|
||||
|
||||
|
|
|
@ -860,6 +860,8 @@ RSpec.describe 'Pipeline', :js do
|
|||
end
|
||||
|
||||
it 'displays the PipelineSchedule in an inactive state' do
|
||||
stub_feature_flags(pipeline_schedules_vue: false)
|
||||
|
||||
visit project_pipeline_schedules_path(project)
|
||||
page.click_link('Inactive')
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ RSpec.describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', t
|
|||
|
||||
before do
|
||||
sign_in(user)
|
||||
stub_feature_flags(pipeline_schedules_vue: false)
|
||||
end
|
||||
|
||||
it 'pipeline_schedules/edit.html' do
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlForm } from '@gitlab/ui';
|
||||
import PipelineSchedulesForm from '~/pipeline_schedules/components/pipeline_schedules_form.vue';
|
||||
|
||||
describe('Pipeline schedules form', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(PipelineSchedulesForm);
|
||||
};
|
||||
|
||||
const findForm = () => wrapper.findComponent(GlForm);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('displays form', () => {
|
||||
expect(findForm().exists()).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import PipelineSchedules from '~/pipeline_schedules/components/pipeline_schedules.vue';
|
||||
import PipelineSchedulesTable from '~/pipeline_schedules/components/table/pipeline_schedules_table.vue';
|
||||
|
||||
describe('Pipeline schedules app', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(PipelineSchedules);
|
||||
};
|
||||
|
||||
const findTable = () => wrapper.findComponent(PipelineSchedulesTable);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('displays table', () => {
|
||||
expect(findTable().exists()).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlTableLite } from '@gitlab/ui';
|
||||
import PipelineSchedulesTable from '~/pipeline_schedules/components/table/pipeline_schedules_table.vue';
|
||||
|
||||
describe('Pipeline schedules table', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(PipelineSchedulesTable);
|
||||
};
|
||||
|
||||
const findTable = () => wrapper.findComponent(GlTableLite);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('displays table', () => {
|
||||
expect(findTable().exists()).toBe(true);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue