Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
8acb6b76
Commit
8acb6b76
authored
Oct 27, 2017
by
Lin Jen-Shin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merging EE doc into CE
parent
15e3d337
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
382 additions
and
0 deletions
+382
-0
doc/development/ee_features.md
doc/development/ee_features.md
+382
-0
No files found.
doc/development/ee_features.md
0 → 100644
View file @
8acb6b76
# Guidelines for implementing Enterprise Edition feature
-
**Write the code and the tests.**
: As with any code, EE features should have
good test coverage to prevent regressions.
-
**Write documentation.**
: Add documentation to the
`doc/`
directory. Describe
the feature and include screenshots, if applicable.
-
**Submit a MR to the `www-gitlab-com` project.**
: Add the new feature to the
[
EE features list
][
ee-features-list
]
.
## Act as CE when unlicensed
Since the implementation of
[
GitLab CE features to work with unlicensed EE instance
][
ee-as-ce
]
GitLab Enterprise Edition should work like GitLab Community Edition
when no license is active. So EE features always should be guarded by
`project.feature_available?`
or
`group.feature_available?`
(or
`License.feature_available?`
if it is a system-wide feature).
CE specs should remain untouched as much as possible and extra specs
should be added for EE. Licensed features can be stubbed using the
spec helper
`stub_licensed_features`
in
`EE::LicenseHelpers`
.
[
ee-as-ce
]:
https://gitlab.com/gitlab-org/gitlab-ee/issues/2500
## Separation of EE code
We want a
[
single code base
][]
eventually, but before we reach the goal,
we still need to merge changes from GitLab CE to EE. To help us get there,
we should make sure that we no longer edit CE files in place in order to
implement EE features.
Instead, all EE codes should be put inside the
`ee/`
top-level directory, and
tests should be put inside
`spec/ee/`
. We don't use
`ee/spec`
for now due to
technical limitation. The rest of codes should be as close as to the CE files.
[
single code base
]:
https://gitlab.com/gitlab-org/gitlab-ee/issues/2952#note_41016454
### EE-only features
If the feature being developed is not present in any form in CE, we don't
need to put the codes under
`EE`
namespace. For example, an EE model could
go into:
`ee/app/models/awesome.rb`
using
`Awesome`
as the class name. This
is applied not only to models. Here's a list of other examples:
-
`ee/app/controllers/foos_controller.rb`
-
`ee/app/finders/foos_finder.rb`
-
`ee/app/helpers/foos_helper.rb`
-
`ee/app/mailers/foos_mailer.rb`
-
`ee/app/models/foo.rb`
-
`ee/app/policies/foo_policy.rb`
-
`ee/app/serializers/foo_entity.rb`
-
`ee/app/serializers/foo_serializer.rb`
-
`ee/app/services/foo/create_service.rb`
-
`ee/app/validators/foo_attr_validator.rb`
-
`ee/app/workers/foo_worker.rb`
### EE features based on CE features
For features that build on existing CE features, write a module in the
`EE`
namespace and
`prepend`
it in the CE class. This makes conflicts
less likely to happen during CE to EE merges because only one line is
added to the CE class - the
`prepend`
line.
Since the module would require an
`EE`
namespace, the file should also be
put in an
`ee/`
sub-directory. For example, we want to extend the user model
in EE, so we have a module called
`::EE::User`
put inside
`ee/app/models/ee/user.rb`
.
This is also not just applied to models. Here's a list of other examples:
-
`ee/app/controllers/ee/foos_controller.rb`
-
`ee/app/finders/ee/foos_finder.rb`
-
`ee/app/helpers/ee/foos_helper.rb`
-
`ee/app/mailers/ee/foos_mailer.rb`
-
`ee/app/models/ee/foo.rb`
-
`ee/app/policies/ee/foo_policy.rb`
-
`ee/app/serializers/ee/foo_entity.rb`
-
`ee/app/serializers/ee/foo_serializer.rb`
-
`ee/app/services/ee/foo/create_service.rb`
-
`ee/app/validators/ee/foo_attr_validator.rb`
-
`ee/app/workers/ee/foo_worker.rb`
#### Overriding CE methods
To override a method present in the CE codebase, use
`prepend`
. It
lets you override a method in a class with a method from a module, while
still having access the class's implementation with
`super`
.
There are a few gotchas with it:
-
you should always add a
`raise NotImplementedError unless defined?(super)`
guard clause in the "overrider" method to ensure that if the method gets
renamed in CE, the EE override won't be silently forgotten.
-
when the "overrider" would add a line in the middle of the CE
implementation, you should refactor the CE method and split it in
smaller methods. Or create a "hook" method that is empty in CE,
and with the EE-specific implementation in EE.
-
when the original implementation contains a guard clause (e.g.
`return unless condition`
), we cannot easily extend the behaviour by
overriding the method, because we can't know when the overridden method
(i.e. calling
`super`
in the overriding method) would want to stop early.
In this case, we shouldn't just override it, but update the original method
to make it call the other method we want to extend, like a
[
template method
pattern
](
https://en.wikipedia.org/wiki/Template_method_pattern
)
.
For example, given this base:
```
ruby
class
Base
def
execute
return
unless
enabled?
# ...
# ...
end
end
```
Instead of just overriding
`Base#execute`
, we should update it and extract
the behaviour into another method:
```
ruby
class
Base
def
execute
return
unless
enabled?
do_something
end
private
def
do_something
# ...
# ...
end
end
```
Then we're free to override that
`do_something`
without worrying about the
guards:
```
ruby
module
EE::Base
def
do_something
# Follow the above pattern to call super and extend it
end
end
```
This would require updating CE first, or make sure this is back ported to CE.
When prepending, place them in the
`ee/`
specific sub-directory, and
wrap class or module in
`module EE`
to avoid naming conflicts.
For example to override the CE implementation of
`ApplicationController#after_sign_out_path_for`
:
```
ruby
def
after_sign_out_path_for
(
resource
)
current_application_settings
.
after_sign_out_path
.
presence
||
new_user_session_path
end
```
Instead of modifying the method in place, you should add
`prepend`
to
the existing file:
```
ruby
class
ApplicationController
<
ActionController
::
Base
prepend
EE
::
ApplicationController
# ...
def
after_sign_out_path_for
(
resource
)
current_application_settings
.
after_sign_out_path
.
presence
||
new_user_session_path
end
# ...
end
```
And create a new file in the
`ee/`
sub-directory with the altered
implementation:
```
ruby
module
EE
class
ApplicationController
def
after_sign_out_path_for
(
resource
)
raise
NotImplementedError
unless
defined?
(
super
)
if
Gitlab
::
Geo
.
secondary?
Gitlab
::
Geo
.
primary_node
.
oauth_logout_url
(
@geo_logout_state
)
else
super
end
end
end
end
```
#### Use self-descriptive wrapper methods
When it's not possible/logical to modify the implementation of a
method. Wrap it in a self-descriptive method and use that method.
For example, in CE only an
`admin`
is allowed to access all private
projects/groups, but in EE also an
`auditor`
has full private
access. It would be incorrect to override the implementation of
`User#admin?`
, so instead add a method
`full_private_access?`
to
`app/models/users.rb`
. The implementation in CE will be:
```
ruby
def
full_private_access?
admin?
end
```
In EE, the implementation
`ee/app/models/ee/users.rb`
would be:
```
ruby
def
full_private_access?
raise
NotImplementedError
unless
defined?
(
super
)
super
||
auditor?
end
```
In
`lib/gitlab/visibility_level.rb`
this method is used to return the
allowed visibilty levels:
```
ruby
def
levels_for_user
(
user
=
nil
)
if
user
.
full_private_access?
[
PRIVATE
,
INTERNAL
,
PUBLIC
]
elsif
# ...
end
```
See
[
CE MR
][
ce-mr-full-private
]
and
[
EE MR
][
ee-mr-full-private
]
for
full implementation details.
[
ce-mr-full-private
]:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12373
[
ee-mr-full-private
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2199
### Code in `app/controllers/`
In controllers, the most common type of conflict is with
`before_action`
that
has a list of actions in CE but EE adds some actions to that list.
The same problem often occurs for
`params.require`
/
`params.permit`
calls.
**Mitigations**
Separate CE and EE actions/keywords. For instance for
`params.require`
in
`ProjectsController`
:
```
ruby
def
project_params
params
.
require
(
:project
).
permit
(
project_params_attributes
)
end
# Always returns an array of symbols, created however best fits the use case.
# It _should_ be sorted alphabetically.
def
project_params_attributes
%i[
description
name
path
]
end
```
In the
`EE::ProjectsController`
module:
```
ruby
def
project_params_attributes
super
+
project_params_attributes_ee
end
def
project_params_attributes_ee
%i[
approvals_before_merge
approver_group_ids
approver_ids
...
]
end
```
### Code in `app/models/`
EE-specific models should
`extend EE::Model`
.
For example, if EE has a specific
`Tanuki`
model, you would
place it in
`ee/app/models/ee/tanuki.rb`
.
### Code in `app/views/`
It's a very frequent problem that EE is adding some specific view code in a CE
view. For instance the approval code in the project's settings page.
**Mitigations**
Blocks of code that are EE-specific should be moved to partials. This
avoids conflicts with big chunks of HAML code that that are not fun to
resolve when you add the indentation to the equation.
EE-specific views should be placed in
`ee/app/views/ee/`
, using extra
sub-directories if appropriate.
### Code in `lib/`
Place EE-specific logic in the top-level
`EE`
module namespace. Namespace the
class beneath the
`EE`
module just as you would normally.
For example, if CE has LDAP classes in
`lib/gitlab/ldap/`
then you would place
EE-specific LDAP classes in
`ee/lib/ee/gitlab/ldap`
.
### Code in `spec/`
When you're testing EE-only features, avoid adding examples to the
existing CE specs. Also do no change existing CE examples, since they
should remain working as-is when EE is running without a license.
Instead place EE specs in the
`spec/ee/spec`
folder.
## JavaScript code in `assets/javascripts/`
To separate EE-specific JS-files we can also move the files into an
`ee`
folder.
For example there can be an
`app/assets/javascripts/protected_branches/protected_branches_bundle.js`
and an
EE counterpart
`ee/app/assets/javascripts/protected_branches/protected_branches_bundle.js`
.
That way we can create a separate webpack bundle in
`webpack.config.js`
:
```
javascript
protected_branches
:
'
~/protected_branches
'
,
ee_protected_branches
:
'
ee/protected_branches/protected_branches_bundle.js
'
,
```
With the separate bundle in place, we can decide which bundle to load inside the
view, using the
`page_specific_javascript_bundle_tag`
helper.
```
haml
-
content_for
:page_specific_javascripts
do
=
page_specific_javascript_bundle_tag
(
'protected_branches'
)
```
## SCSS code in `assets/stylesheets`
To separate EE-specific styles in SCSS files, if a component you're adding styles for
is limited to only EE, it is better to have a separate SCSS file in appropriate directory
within
`app/assets/stylesheets`
.
In some cases, this is not entirely possible or creating dedicated SCSS file is an overkill,
e.g. a text style of some component is different for EE. In such cases,
styles are usually kept in stylesheet that is common for both CE and EE, and it is wise
to isolate such ruleset from rest of CE rules (along with adding comment describing the same)
to avoid conflicts during CE to EE merge.
#### Bad
```
scss
.section-body
{
.section-title
{
background
:
$gl-header-color
;
}
&
.ee-section-body
{
.section-title
{
background
:
$gl-header-color-cyan
;
}
}
}
```
#### Good
```
scss
.section-body
{
.section-title
{
background
:
$gl-header-color
;
}
}
/* EE-specific styles */
.section-body.ee-section-body
{
.section-title
{
background
:
$gl-header-color-cyan
;
}
}
```
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment