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
8bce1b3f
Commit
8bce1b3f
authored
Aug 30, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
6507a9e5
72bdd37f
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
351 additions
and
138 deletions
+351
-138
app/models/user.rb
app/models/user.rb
+5
-1
config/feature_flags/development/linear_user_membership_groups.yml
...ature_flags/development/linear_user_membership_groups.yml
+8
-0
config/helpers/incremental_webpack_compiler.js
config/helpers/incremental_webpack_compiler.js
+0
-131
config/helpers/incremental_webpack_compiler/compiler.js
config/helpers/incremental_webpack_compiler/compiler.js
+117
-0
config/helpers/incremental_webpack_compiler/history.js
config/helpers/incremental_webpack_compiler/history.js
+176
-0
config/helpers/incremental_webpack_compiler/index.js
config/helpers/incremental_webpack_compiler/index.js
+17
-0
config/helpers/incremental_webpack_compiler/log.js
config/helpers/incremental_webpack_compiler/log.js
+3
-0
config/webpack.config.js
config/webpack.config.js
+4
-0
spec/models/user_spec.rb
spec/models/user_spec.rb
+21
-6
No files found.
app/models/user.rb
View file @
8bce1b3f
...
@@ -1000,7 +1000,11 @@ class User < ApplicationRecord
...
@@ -1000,7 +1000,11 @@ class User < ApplicationRecord
# Returns the groups a user is a member of, either directly or through a parent group
# Returns the groups a user is a member of, either directly or through a parent group
def
membership_groups
def
membership_groups
Gitlab
::
ObjectHierarchy
.
new
(
groups
).
base_and_descendants
if
Feature
.
enabled?
(
:linear_user_membership_groups
,
self
,
default_enabled: :yaml
)
groups
.
self_and_descendants
else
Gitlab
::
ObjectHierarchy
.
new
(
groups
).
base_and_descendants
end
end
end
# Returns a relation of groups the user has access to, including their parent
# Returns a relation of groups the user has access to, including their parent
...
...
config/feature_flags/development/linear_user_membership_groups.yml
0 → 100644
View file @
8bce1b3f
---
name
:
linear_user_membership_groups
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68842
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/339432
milestone
:
'
14.3'
type
:
development
group
:
group::access
default_enabled
:
false
config/helpers/incremental_webpack_compiler.js
deleted
100644 → 0
View file @
6507a9e5
/* eslint-disable max-classes-per-file, no-underscore-dangle */
const
fs
=
require
(
'
fs
'
);
const
path
=
require
(
'
path
'
);
const
log
=
(
msg
,
...
rest
)
=>
console
.
log
(
`IncrementalWebpackCompiler:
${
msg
}
`
,
...
rest
);
// If we force a recompile immediately, the page reload doesn't seem to work.
// Five seconds seem to work fine and the user can read the message
const
TIMEOUT
=
5000
;
/* eslint-disable class-methods-use-this */
class
NoopCompiler
{
constructor
()
{
this
.
enabled
=
false
;
}
filterEntryPoints
(
entryPoints
)
{
return
entryPoints
;
}
logStatus
()
{}
setupMiddleware
()
{}
}
/* eslint-enable class-methods-use-this */
class
IncrementalWebpackCompiler
{
constructor
(
historyFilePath
)
{
this
.
enabled
=
true
;
this
.
history
=
{};
this
.
compiledEntryPoints
=
new
Set
([
// Login page
'
pages.sessions.new
'
,
// Explore page
'
pages.root
'
,
]);
this
.
historyFilePath
=
historyFilePath
;
this
.
_loadFromHistory
();
}
filterEntryPoints
(
entrypoints
)
{
return
Object
.
fromEntries
(
Object
.
entries
(
entrypoints
).
map
(([
key
,
val
])
=>
{
if
(
this
.
compiledEntryPoints
.
has
(
key
))
{
return
[
key
,
val
];
}
return
[
key
,
[
'
./webpack_non_compiled_placeholder.js
'
]];
}),
);
}
logStatus
(
totalCount
)
{
const
current
=
this
.
compiledEntryPoints
.
size
;
log
(
`Currently compiling route entrypoints:
${
current
}
of
${
totalCount
}
`
);
}
setupMiddleware
(
app
,
server
)
{
app
.
use
((
req
,
res
,
next
)
=>
{
const
fileName
=
path
.
basename
(
req
.
url
);
/**
* We are only interested in files that have a name like `pages.foo.bar.chunk.js`
* because those are the ones corresponding to our entry points.
*
* This filters out hot update files that are for example named "pages.foo.bar.[hash].hot-update.js"
*/
if
(
fileName
.
startsWith
(
'
pages.
'
)
&&
fileName
.
endsWith
(
'
.chunk.js
'
))
{
const
chunk
=
fileName
.
replace
(
/
\.
chunk
\.
js$/
,
''
);
this
.
_addToHistory
(
chunk
);
if
(
!
this
.
compiledEntryPoints
.
has
(
chunk
))
{
log
(
`First time we are seeing
${
chunk
}
. Adding to compilation.`
);
this
.
compiledEntryPoints
.
add
(
chunk
);
setTimeout
(()
=>
{
server
.
middleware
.
invalidate
(()
=>
{
if
(
server
.
sockets
)
{
server
.
sockWrite
(
server
.
sockets
,
'
content-changed
'
);
}
});
},
TIMEOUT
);
}
}
next
();
});
}
// private methods
_addToHistory
(
chunk
)
{
if
(
!
this
.
history
[
chunk
])
{
this
.
history
[
chunk
]
=
{
lastVisit
:
null
,
count
:
0
};
}
this
.
history
[
chunk
].
lastVisit
=
Date
.
now
();
this
.
history
[
chunk
].
count
+=
1
;
try
{
fs
.
writeFileSync
(
this
.
historyFilePath
,
JSON
.
stringify
(
this
.
history
),
'
utf8
'
);
}
catch
(
e
)
{
log
(
'
Warning – Could not write to history
'
,
e
.
message
);
}
}
_loadFromHistory
()
{
try
{
this
.
history
=
JSON
.
parse
(
fs
.
readFileSync
(
this
.
historyFilePath
,
'
utf8
'
));
const
entryPoints
=
Object
.
keys
(
this
.
history
);
log
(
`Successfully loaded history containing
${
entryPoints
.
length
}
entry points`
);
/*
TODO: Let's ask a few folks to give us their history file after a milestone of usage
Then we can make smarter decisions on when to throw out rather than rendering everything
Something like top 20/30/40 entries visited in the last 7/10/15 days might be sufficient
*/
this
.
compiledEntryPoints
=
new
Set
([...
this
.
compiledEntryPoints
,
...
entryPoints
]);
}
catch
(
e
)
{
log
(
`No history found...`
);
}
}
}
module
.
exports
=
(
enabled
,
historyFilePath
)
=>
{
log
(
`Status –
${
enabled
?
'
enabled
'
:
'
disabled
'
}
`
);
if
(
enabled
)
{
return
new
IncrementalWebpackCompiler
(
historyFilePath
);
}
return
new
NoopCompiler
();
};
config/helpers/incremental_webpack_compiler/compiler.js
0 → 100644
View file @
8bce1b3f
/* eslint-disable max-classes-per-file */
const
path
=
require
(
'
path
'
);
const
{
History
,
HistoryWithTTL
}
=
require
(
'
./history
'
);
const
log
=
require
(
'
./log
'
);
const
onRequestEntryPoint
=
(
app
,
callback
)
=>
{
app
.
use
((
req
,
res
,
next
)
=>
{
const
fileName
=
path
.
basename
(
req
.
url
);
/**
* We are only interested in files that have a name like `pages.foo.bar.chunk.js`
* because those are the ones corresponding to our entry points.
*
* This filters out hot update files that are for example named "pages.foo.bar.[hash].hot-update.js"
*/
if
(
fileName
.
startsWith
(
'
pages.
'
)
&&
fileName
.
endsWith
(
'
.chunk.js
'
))
{
const
entryPoint
=
fileName
.
replace
(
/
\.
chunk
\.
js$/
,
''
);
callback
(
entryPoint
);
}
next
();
});
};
/**
* The NoopCompiler does nothing, following the null object pattern.
*/
class
NoopCompiler
{
constructor
()
{
this
.
enabled
=
false
;
}
// eslint-disable-next-line class-methods-use-this
filterEntryPoints
(
entryPoints
)
{
return
entryPoints
;
}
// eslint-disable-next-line class-methods-use-this
logStatus
()
{}
// eslint-disable-next-line class-methods-use-this
setupMiddleware
()
{}
}
/**
* The HistoryOnlyCompiler only records which entry points have been requested.
* This is so that if the user disables incremental compilation, history is
* still recorded. If they later enable incremental compilation, that history
* can be used.
*/
class
HistoryOnlyCompiler
extends
NoopCompiler
{
constructor
(
historyFilePath
)
{
super
();
this
.
history
=
new
History
(
historyFilePath
);
}
setupMiddleware
(
app
)
{
onRequestEntryPoint
(
app
,
(
entryPoint
)
=>
{
this
.
history
.
onRequestEntryPoint
(
entryPoint
);
});
}
}
// If we force a recompile immediately, the page reload doesn't seem to work.
// Five seconds seem to work fine and the user can read the message
const
TIMEOUT
=
5000
;
/**
* The IncrementalWebpackCompiler tracks which entry points have been
* requested, and only compiles entry points visited within the last `ttl`
* days.
*/
class
IncrementalWebpackCompiler
{
constructor
(
historyFilePath
,
ttl
)
{
this
.
enabled
=
true
;
this
.
history
=
new
HistoryWithTTL
(
historyFilePath
,
ttl
);
}
filterEntryPoints
(
entrypoints
)
{
return
Object
.
fromEntries
(
Object
.
entries
(
entrypoints
).
map
(([
entryPoint
,
paths
])
=>
{
if
(
this
.
history
.
isRecentlyVisited
(
entryPoint
))
{
return
[
entryPoint
,
paths
];
}
return
[
entryPoint
,
[
'
./webpack_non_compiled_placeholder.js
'
]];
}),
);
}
logStatus
(
totalCount
)
{
log
(
`Currently compiling route entrypoints:
${
this
.
history
.
size
}
of
${
totalCount
}
`
);
}
setupMiddleware
(
app
,
server
)
{
onRequestEntryPoint
(
app
,
(
entryPoint
)
=>
{
const
wasVisitedRecently
=
this
.
history
.
onRequestEntryPoint
(
entryPoint
);
if
(
!
wasVisitedRecently
)
{
log
(
`Have not visited
${
entryPoint
}
recently. Adding to compilation.`
);
setTimeout
(()
=>
{
server
.
middleware
.
invalidate
(()
=>
{
if
(
server
.
sockets
)
{
server
.
sockWrite
(
server
.
sockets
,
'
content-changed
'
);
}
});
},
TIMEOUT
);
}
});
}
}
module
.
exports
=
{
NoopCompiler
,
HistoryOnlyCompiler
,
IncrementalWebpackCompiler
,
};
config/helpers/incremental_webpack_compiler/history.js
0 → 100644
View file @
8bce1b3f
/* eslint-disable max-classes-per-file, no-underscore-dangle */
const
fs
=
require
(
'
fs
'
);
const
log
=
require
(
'
./log
'
);
const
ESSENTIAL_ENTRY_POINTS
=
[
// Login page
'
pages.sessions.new
'
,
// Explore page
'
pages.root
'
,
];
// TODO: Find a way to keep this list up-to-date/relevant.
const
COMMON_ENTRY_POINTS
=
[
...
ESSENTIAL_ENTRY_POINTS
,
'
pages.admin
'
,
'
pages.admin.dashboard
'
,
'
pages.dashboard.groups.index
'
,
'
pages.dashboard.projects.index
'
,
'
pages.groups.new
'
,
'
pages.groups.show
'
,
'
pages.profiles.preferences.show
'
,
'
pages.projects.commit.show
'
,
'
pages.projects.edit
'
,
'
pages.projects.issues.index
'
,
'
pages.projects.issues.new
'
,
'
pages.projects.issues.show
'
,
'
pages.projects.jobs.show
'
,
'
pages.projects.merge_requests.index
'
,
'
pages.projects.merge_requests.show
'
,
'
pages.projects.milestones.index
'
,
'
pages.projects.new
'
,
'
pages.projects.pipelines.index
'
,
'
pages.projects.pipelines.show
'
,
'
pages.projects.settings.ci_cd.show
'
,
'
pages.projects.settings.repository.show
'
,
'
pages.projects.show
'
,
'
pages.users
'
,
];
/**
* The History class is responsible for tracking which entry points have been
* requested, and persisting/loading the history to/from disk.
*/
class
History
{
constructor
(
historyFilePath
)
{
this
.
_historyFilePath
=
historyFilePath
;
this
.
_history
=
{};
this
.
_loadHistoryFile
();
}
onRequestEntryPoint
(
entryPoint
)
{
const
wasVisitedRecently
=
this
.
isRecentlyVisited
(
entryPoint
);
this
.
_addEntryPoint
(
entryPoint
);
this
.
_writeHistoryFile
();
return
wasVisitedRecently
;
}
// eslint-disable-next-line class-methods-use-this
isRecentlyVisited
()
{
return
true
;
}
// eslint-disable-next-line class-methods-use-this
get
size
()
{
return
0
;
}
// Private methods
_addEntryPoint
(
entryPoint
)
{
if
(
!
this
.
_history
[
entryPoint
])
{
this
.
_history
[
entryPoint
]
=
{
lastVisit
:
null
,
count
:
0
};
}
this
.
_history
[
entryPoint
].
lastVisit
=
Date
.
now
();
this
.
_history
[
entryPoint
].
count
+=
1
;
}
_writeHistoryFile
()
{
try
{
fs
.
writeFileSync
(
this
.
_historyFilePath
,
JSON
.
stringify
(
this
.
_history
),
'
utf8
'
);
}
catch
(
error
)
{
log
(
'
Warning – Could not write to history
'
,
error
.
message
);
}
}
_loadHistoryFile
()
{
try
{
fs
.
accessSync
(
this
.
_historyFilePath
);
}
catch
(
e
)
{
// History file doesn't exist; attempt to seed it, and return early
this
.
_seedHistory
();
return
;
}
// History file already exists; attempt to load its contents into memory
try
{
this
.
_history
=
JSON
.
parse
(
fs
.
readFileSync
(
this
.
_historyFilePath
,
'
utf8
'
));
const
historySize
=
Object
.
keys
(
this
.
_history
).
length
;
log
(
`Successfully loaded history containing
${
historySize
}
entry points`
);
}
catch
(
error
)
{
log
(
'
Could not load history
'
,
error
.
message
);
}
}
/**
* Seeds a reasonable set of approximately the most common entry points to
* seed the history. This helps to avoid fresh GDK installs showing the
* compiling overlay too often.
*/
_seedHistory
()
{
log
(
'
Seeding history...
'
);
COMMON_ENTRY_POINTS
.
forEach
((
entryPoint
)
=>
this
.
_addEntryPoint
(
entryPoint
));
this
.
_writeHistoryFile
();
}
}
const
MS_PER_DAY
=
1000
*
60
*
60
*
24
;
/**
* The HistoryWithTTL class adds LRU-like behaviour onto the base History
* behaviour. Entry points visited within the last `ttl` days are considered
* "recent", and therefore should be eagerly compiled.
*/
class
HistoryWithTTL
extends
History
{
constructor
(
historyFilePath
,
ttl
)
{
super
(
historyFilePath
);
this
.
_ttl
=
ttl
;
this
.
_calculateRecentEntryPoints
();
}
onRequestEntryPoint
(
entryPoint
)
{
const
wasVisitedRecently
=
super
.
onRequestEntryPoint
(
entryPoint
);
this
.
_calculateRecentEntryPoints
();
return
wasVisitedRecently
;
}
isRecentlyVisited
(
entryPoint
)
{
return
this
.
_recentEntryPoints
.
has
(
entryPoint
);
}
get
size
()
{
return
this
.
_recentEntryPoints
.
size
;
}
// Private methods
_calculateRecentEntryPoints
()
{
const
oldestVisitAllowed
=
Date
.
now
()
-
MS_PER_DAY
*
this
.
_ttl
;
const
recentEntryPoints
=
Object
.
entries
(
this
.
_history
).
reduce
(
(
acc
,
[
entryPoint
,
{
lastVisit
}])
=>
{
if
(
lastVisit
>
oldestVisitAllowed
)
{
acc
.
push
(
entryPoint
);
}
return
acc
;
},
[],
);
this
.
_recentEntryPoints
=
new
Set
([...
ESSENTIAL_ENTRY_POINTS
,
...
recentEntryPoints
]);
}
}
module
.
exports
=
{
History
,
HistoryWithTTL
,
};
config/helpers/incremental_webpack_compiler/index.js
0 → 100644
View file @
8bce1b3f
const
{
NoopCompiler
,
HistoryOnlyCompiler
,
IncrementalWebpackCompiler
}
=
require
(
'
./compiler
'
);
const
log
=
require
(
'
./log
'
);
module
.
exports
=
(
recordHistory
,
enabled
,
historyFilePath
,
ttl
)
=>
{
if
(
!
recordHistory
)
{
log
(
`Status – disabled`
);
return
new
NoopCompiler
();
}
if
(
enabled
)
{
log
(
`Status – enabled, ttl=
${
ttl
}
`
);
return
new
IncrementalWebpackCompiler
(
historyFilePath
,
ttl
);
}
log
(
`Status – history-only`
);
return
new
HistoryOnlyCompiler
(
historyFilePath
);
};
config/helpers/incremental_webpack_compiler/log.js
0 → 100644
View file @
8bce1b3f
const
log
=
(
msg
,
...
rest
)
=>
console
.
log
(
`IncrementalWebpackCompiler:
${
msg
}
`
,
...
rest
);
module
.
exports
=
log
;
config/webpack.config.js
View file @
8bce1b3f
...
@@ -48,6 +48,8 @@ const INCREMENTAL_COMPILER_ENABLED =
...
@@ -48,6 +48,8 @@ const INCREMENTAL_COMPILER_ENABLED =
IS_DEV_SERVER
&&
IS_DEV_SERVER
&&
process
.
env
.
DEV_SERVER_INCREMENTAL
&&
process
.
env
.
DEV_SERVER_INCREMENTAL
&&
process
.
env
.
DEV_SERVER_INCREMENTAL
!==
'
false
'
;
process
.
env
.
DEV_SERVER_INCREMENTAL
!==
'
false
'
;
const
INCREMENTAL_COMPILER_TTL
=
Number
(
process
.
env
.
DEV_SERVER_INCREMENTAL_TTL
)
||
Infinity
;
const
INCREMENTAL_COMPILER_RECORD_HISTORY
=
IS_DEV_SERVER
&&
!
process
.
env
.
CI
;
const
WEBPACK_REPORT
=
process
.
env
.
WEBPACK_REPORT
&&
process
.
env
.
WEBPACK_REPORT
!==
'
false
'
;
const
WEBPACK_REPORT
=
process
.
env
.
WEBPACK_REPORT
&&
process
.
env
.
WEBPACK_REPORT
!==
'
false
'
;
const
WEBPACK_MEMORY_TEST
=
const
WEBPACK_MEMORY_TEST
=
process
.
env
.
WEBPACK_MEMORY_TEST
&&
process
.
env
.
WEBPACK_MEMORY_TEST
!==
'
false
'
;
process
.
env
.
WEBPACK_MEMORY_TEST
&&
process
.
env
.
WEBPACK_MEMORY_TEST
!==
'
false
'
;
...
@@ -69,8 +71,10 @@ let watchAutoEntries = [];
...
@@ -69,8 +71,10 @@ let watchAutoEntries = [];
const
defaultEntries
=
[
'
./main
'
];
const
defaultEntries
=
[
'
./main
'
];
const
incrementalCompiler
=
createIncrementalWebpackCompiler
(
const
incrementalCompiler
=
createIncrementalWebpackCompiler
(
INCREMENTAL_COMPILER_RECORD_HISTORY
,
INCREMENTAL_COMPILER_ENABLED
,
INCREMENTAL_COMPILER_ENABLED
,
path
.
join
(
CACHE_PATH
,
'
incremental-webpack-compiler-history.json
'
),
path
.
join
(
CACHE_PATH
,
'
incremental-webpack-compiler-history.json
'
),
INCREMENTAL_COMPILER_TTL
,
);
);
function
generateEntries
()
{
function
generateEntries
()
{
...
...
spec/models/user_spec.rb
View file @
8bce1b3f
...
@@ -3389,17 +3389,32 @@ RSpec.describe User do
...
@@ -3389,17 +3389,32 @@ RSpec.describe User do
end
end
describe
'#membership_groups'
do
describe
'#membership_groups'
do
let!
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let!
(
:parent_group
)
{
create
(
:group
)
}
let!
(
:child_group
)
{
create
(
:group
,
parent:
parent_group
)
}
before
do
let_it_be
(
:parent_group
)
do
parent_group
.
add_user
(
user
,
Gitlab
::
Access
::
MAINTAINER
)
create
(
:group
).
tap
do
|
g
|
g
.
add_user
(
user
,
Gitlab
::
Access
::
MAINTAINER
)
end
end
end
let_it_be
(
:child_group
)
{
create
(
:group
,
parent:
parent_group
)
}
let_it_be
(
:other_group
)
{
create
(
:group
)
}
subject
{
user
.
membership_groups
}
subject
{
user
.
membership_groups
}
it
{
is_expected
.
to
contain_exactly
parent_group
,
child_group
}
shared_examples
'returns groups where the user is a member'
do
specify
{
is_expected
.
to
contain_exactly
(
parent_group
,
child_group
)
}
end
it_behaves_like
'returns groups where the user is a member'
context
'when feature flag :linear_user_membership_groups is disabled'
do
before
do
stub_feature_flags
(
linear_user_membership_groups:
false
)
end
it_behaves_like
'returns groups where the user is a member'
end
end
end
describe
'#authorizations_for_projects'
do
describe
'#authorizations_for_projects'
do
...
...
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