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
6b778dbf
Commit
6b778dbf
authored
Sep 27, 2021
by
Thomas Randolph
Committed by
Phil Hughes
Sep 27, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Unify "merging" MR Widgets
parent
a453581b
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
122 additions
and
26 deletions
+122
-26
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
...merge_request_widget/components/states/ready_to_merge.vue
+10
-0
app/assets/javascripts/vue_merge_request_widget/constants.js
app/assets/javascripts/vue_merge_request_widget/constants.js
+40
-0
app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+12
-2
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+45
-5
app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
...javascripts/vue_merge_request_widget/stores/state_maps.js
+3
-7
ee/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
...javascripts/vue_merge_request_widget/stores/state_maps.js
+7
-9
ee/spec/features/merge_request/user_merges_immediately_spec.rb
...ec/features/merge_request/user_merges_immediately_spec.rb
+2
-2
spec/features/merge_request/user_merges_immediately_spec.rb
spec/features/merge_request/user_merges_immediately_spec.rb
+1
-1
spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
...widget/components/states/mr_widget_ready_to_merge_spec.js
+2
-0
No files found.
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
View file @
6b778dbf
...
...
@@ -29,6 +29,7 @@ import {
WARNING
,
MT_MERGE_STRATEGY
,
PIPELINE_FAILED_STATE
,
STATE_MACHINE
,
}
from
'
../../constants
'
;
import
eventHub
from
'
../../event_hub
'
;
import
mergeRequestQueryVariablesMixin
from
'
../../mixins/merge_request_query_variables
'
;
...
...
@@ -47,6 +48,9 @@ const MERGE_FAILED_STATUS = 'failed';
const
MERGE_SUCCESS_STATUS
=
'
success
'
;
const
MERGE_HOOK_VALIDATION_ERROR_STATUS
=
'
hook_validation_error
'
;
const
{
transitions
}
=
STATE_MACHINE
;
const
{
MERGE
,
MERGED
,
MERGE_FAILURE
}
=
transitions
;
export
default
{
name
:
'
ReadyToMerge
'
,
apollo
:
{
...
...
@@ -361,6 +365,7 @@ export default {
}
this
.
isMakingRequest
=
true
;
this
.
mr
.
transitionStateMachine
({
transition
:
MERGE
});
this
.
service
.
merge
(
options
)
.
then
((
res
)
=>
res
.
data
)
...
...
@@ -375,6 +380,7 @@ export default {
this
.
initiateMergePolling
();
}
else
if
(
hasError
)
{
eventHub
.
$emit
(
'
FailedToMerge
'
,
data
.
merge_error
);
this
.
mr
.
transitionStateMachine
({
transition
:
MERGE_FAILURE
});
}
if
(
this
.
glFeatures
.
mergeRequestWidgetGraphql
)
{
...
...
@@ -383,6 +389,7 @@ export default {
})
.
catch
(()
=>
{
this
.
isMakingRequest
=
false
;
this
.
mr
.
transitionStateMachine
({
transition
:
MERGE_FAILURE
});
createFlash
({
message
:
__
(
'
Something went wrong. Please try again.
'
),
});
...
...
@@ -417,6 +424,7 @@ export default {
eventHub
.
$emit
(
'
FetchActionsContent
'
);
MergeRequest
.
hideCloseButton
();
MergeRequest
.
decreaseCounter
();
this
.
mr
.
transitionStateMachine
({
transition
:
MERGED
});
stopPolling
();
refreshUserMergeRequestCounts
();
...
...
@@ -428,6 +436,7 @@ export default {
}
}
else
if
(
data
.
merge_error
)
{
eventHub
.
$emit
(
'
FailedToMerge
'
,
data
.
merge_error
);
this
.
mr
.
transitionStateMachine
({
transition
:
MERGE_FAILURE
});
stopPolling
();
}
else
{
// MR is not merged yet, continue polling until the state becomes 'merged'
...
...
@@ -438,6 +447,7 @@ export default {
createFlash
({
message
:
__
(
'
Something went wrong while merging this merge request. Please try again.
'
),
});
this
.
mr
.
transitionStateMachine
({
transition
:
MERGE_FAILURE
});
stopPolling
();
});
},
...
...
app/assets/javascripts/vue_merge_request_widget/constants.js
View file @
6b778dbf
import
{
s__
}
from
'
~/locale
'
;
import
{
stateToComponentMap
as
classStateMap
,
stateKey
}
from
'
./stores/state_maps
'
;
export
const
SUCCESS
=
'
success
'
;
export
const
WARNING
=
'
warning
'
;
...
...
@@ -52,3 +53,42 @@ export const MERGE_ACTIVE_STATUS_PHRASES = [
emoji
:
'
heart_eyes
'
,
},
];
const
STATE_MACHINE
=
{
states
:
{
IDLE
:
'
IDLE
'
,
MERGING
:
'
MERGING
'
,
},
transitions
:
{
MERGE
:
'
start-merge
'
,
MERGE_FAILURE
:
'
merge-failed
'
,
MERGED
:
'
merge-done
'
,
},
};
const
{
states
,
transitions
}
=
STATE_MACHINE
;
STATE_MACHINE
.
definition
=
{
initial
:
states
.
IDLE
,
states
:
{
[
states
.
IDLE
]:
{
on
:
{
[
transitions
.
MERGE
]:
states
.
MERGING
,
},
},
[
states
.
MERGING
]:
{
on
:
{
[
transitions
.
MERGED
]:
states
.
IDLE
,
[
transitions
.
MERGE_FAILURE
]:
states
.
IDLE
,
},
},
},
};
export
const
stateToTransitionMap
=
{
[
stateKey
.
merging
]:
transitions
.
MERGE
,
[
stateKey
.
merged
]:
transitions
.
MERGED
,
};
export
const
stateToComponentMap
=
{
[
states
.
MERGING
]:
classStateMap
[
stateKey
.
merging
],
};
export
{
STATE_MACHINE
};
app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
6b778dbf
...
...
@@ -4,7 +4,7 @@ import { isEmpty } from 'lodash';
import
MrWidgetApprovals
from
'
ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue
'
;
import
MRWidgetService
from
'
ee_else_ce/vue_merge_request_widget/services/mr_widget_service
'
;
import
MRWidgetStore
from
'
ee_else_ce/vue_merge_request_widget/stores/mr_widget_store
'
;
import
stateMaps
from
'
ee_else_ce/vue_merge_request_widget/stores/state_maps
'
;
import
{
stateToComponentMap
as
classState
}
from
'
ee_else_ce/vue_merge_request_widget/stores/state_maps
'
;
import
createFlash
from
'
~/flash
'
;
import
{
secondsToMilliseconds
}
from
'
~/lib/utils/datetime_utility
'
;
import
notify
from
'
~/lib/utils/notify
'
;
...
...
@@ -39,6 +39,7 @@ import ShaMismatch from './components/states/sha_mismatch.vue';
import
UnresolvedDiscussionsState
from
'
./components/states/unresolved_discussions.vue
'
;
import
WorkInProgressState
from
'
./components/states/work_in_progress.vue
'
;
import
ExtensionsContainer
from
'
./components/extensions/container
'
;
import
{
STATE_MACHINE
,
stateToComponentMap
}
from
'
./constants
'
;
import
eventHub
from
'
./event_hub
'
;
import
mergeRequestQueryVariablesMixin
from
'
./mixins/merge_request_query_variables
'
;
import
getStateQuery
from
'
./queries/get_state.query.graphql
'
;
...
...
@@ -124,7 +125,9 @@ export default {
mr
:
store
,
state
:
store
&&
store
.
state
,
service
:
store
&&
this
.
createService
(
store
),
machineState
:
store
?.
machineValue
||
STATE_MACHINE
.
definition
.
initial
,
loading
:
true
,
recomputeComponentName
:
0
,
};
},
computed
:
{
...
...
@@ -139,7 +142,7 @@ export default {
return
this
.
mr
.
state
!==
'
nothingToMerge
'
;
},
componentName
()
{
return
state
Maps
.
stateToComponentMap
[
this
.
mr
.
state
];
return
state
ToComponentMap
[
this
.
machineState
]
||
classState
[
this
.
mr
.
state
];
},
hasPipelineMustSucceedConflict
()
{
return
!
this
.
mr
.
hasCI
&&
this
.
mr
.
onlyAllowMergeIfPipelineSucceeds
;
...
...
@@ -206,6 +209,11 @@ export default {
},
},
watch
:
{
'
mr.machineValue
'
:
{
handler
(
newValue
)
{
this
.
machineState
=
newValue
;
},
},
state
(
newVal
,
oldVal
)
{
if
(
newVal
!==
oldVal
&&
this
.
shouldRenderMergedPipeline
)
{
// init polling
...
...
@@ -247,6 +255,8 @@ export default {
this
.
mr
=
new
MRWidgetStore
({
...
window
.
gl
.
mrWidgetData
,
...
data
});
}
this
.
machineState
=
this
.
mr
.
machineValue
;
if
(
!
this
.
state
)
{
this
.
state
=
this
.
mr
.
state
;
}
...
...
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
6b778dbf
import
getStateKey
from
'
ee_else_ce/vue_merge_request_widget/stores/get_state_key
'
;
import
{
statusBoxState
}
from
'
~/issuable/components/status_box.vue
'
;
import
{
formatDate
,
getTimeago
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
MTWPS_MERGE_STRATEGY
,
MT_MERGE_STRATEGY
,
MWPS_MERGE_STRATEGY
}
from
'
../constants
'
;
import
{
machine
}
from
'
~/lib/utils/finite_state_machine
'
;
import
{
MTWPS_MERGE_STRATEGY
,
MT_MERGE_STRATEGY
,
MWPS_MERGE_STRATEGY
,
STATE_MACHINE
,
stateToTransitionMap
,
}
from
'
../constants
'
;
import
{
stateKey
}
from
'
./state_maps
'
;
const
{
format
}
=
getTimeago
();
const
{
states
}
=
STATE_MACHINE
;
const
{
IDLE
}
=
states
;
export
default
class
MergeRequestStore
{
constructor
(
data
)
{
this
.
sha
=
data
.
diff_head_sha
;
...
...
@@ -16,6 +26,9 @@ export default class MergeRequestStore {
this
.
apiUnapprovePath
=
data
.
api_unapprove_path
;
this
.
hasApprovalsAvailable
=
data
.
has_approvals_available
;
this
.
stateMachine
=
machine
(
STATE_MACHINE
.
definition
);
this
.
machineValue
=
this
.
stateMachine
.
value
;
this
.
setPaths
(
data
);
this
.
setData
(
data
);
...
...
@@ -215,10 +228,7 @@ export default class MergeRequestStore {
setState
()
{
if
(
this
.
mergeOngoing
)
{
this
.
state
=
'
merging
'
;
return
;
}
if
(
this
.
isOpen
)
{
}
else
if
(
this
.
isOpen
)
{
this
.
state
=
getStateKey
.
call
(
this
);
}
else
{
switch
(
this
.
mergeRequestState
)
{
...
...
@@ -232,6 +242,8 @@ export default class MergeRequestStore {
this
.
state
=
null
;
}
}
this
.
translateStateToMachine
();
}
setPaths
(
data
)
{
...
...
@@ -356,4 +368,32 @@ export default class MergeRequestStore {
(
this
.
onlyAllowMergeIfPipelineSucceeds
&&
this
.
isPipelineFailed
)
);
}
// Because the state machine doesn't yet handle every state and transition,
// some use-cases will need to force a state that can't be reached by
// a known transition. This is undesirable long-term (as it subverts
// the intent of a state machine), but is necessary until the machine
// can handle all possible combinations. (unsafeForce)
transitionStateMachine
({
transition
,
state
,
unsafeForce
=
false
}
=
{})
{
if
(
unsafeForce
&&
state
)
{
this
.
stateMachine
.
value
=
state
;
}
else
{
this
.
stateMachine
.
send
(
transition
);
}
this
.
machineValue
=
this
.
stateMachine
.
value
;
}
translateStateToMachine
()
{
const
transition
=
stateToTransitionMap
[
this
.
state
];
let
transitionOptions
=
{
state
:
IDLE
,
unsafeForce
:
true
,
};
if
(
transition
)
{
transitionOptions
=
{
transition
};
}
this
.
transitionStateMachine
(
transitionOptions
);
}
}
app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
View file @
6b778dbf
const
stateToComponentMap
=
{
export
const
stateToComponentMap
=
{
merged
:
'
mr-widget-merged
'
,
closed
:
'
mr-widget-closed
'
,
merging
:
'
mr-widget-merging
'
,
...
...
@@ -21,7 +21,7 @@ const stateToComponentMap = {
mergeChecksFailed
:
'
mergeChecksFailed
'
,
};
const
statesToShowHelpWidget
=
[
export
const
statesToShowHelpWidget
=
[
'
merging
'
,
'
conflicts
'
,
'
workInProgress
'
,
...
...
@@ -50,11 +50,7 @@ export const stateKey = {
notAllowedToMerge
:
'
notAllowedToMerge
'
,
readyToMerge
:
'
readyToMerge
'
,
rebase
:
'
rebase
'
,
merging
:
'
merging
'
,
merged
:
'
merged
'
,
mergeChecksFailed
:
'
mergeChecksFailed
'
,
};
export
default
{
stateToComponentMap
,
statesToShowHelpWidget
,
};
ee/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
View file @
6b778dbf
import
stateMaps
from
'
~/vue_merge_request_widget/stores/state_maps
'
;
stateMaps
.
stateToComponentMap
.
geoSecondaryNode
=
'
mr-widget-geo-secondary-node
'
;
stateMaps
.
stateToComponentMap
.
policyViolation
=
'
mr-widget-policy-violation
'
;
stateMaps
.
stateToComponentMap
.
jiraAssociationMissing
=
'
mr-widget-jira-association-missing
'
;
import
{
stateToComponentMap
as
ceStateMap
}
from
'
~/vue_merge_request_widget/stores/state_maps
'
;
export
{
statesToShowHelpWidget
}
from
'
~/vue_merge_request_widget/stores/state_maps
'
;
export
const
stateKey
=
{
policyViolation
:
'
policyViolation
'
,
jiraAssociationMissing
:
'
jiraAssociationMissing
'
,
};
export
default
{
stateToComponentMap
:
stateMaps
.
stateToComponentMap
,
statesToShowHelpWidget
:
stateMaps
.
statesToShowHelpWidget
,
export
const
stateToComponentMap
=
{
...
ceStateMap
,
geoSecondaryNode
:
'
mr-widget-geo-secondary-node
'
,
policyViolation
:
'
mr-widget-policy-violation
'
,
jiraAssociationMissing
:
'
mr-widget-jira-association-missing
'
,
};
ee/spec/features/merge_request/user_merges_immediately_spec.rb
View file @
6b778dbf
...
...
@@ -52,7 +52,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do
find
(
':focus'
).
send_keys
:enter
expect
(
merge_button
).
to
have_
no_content
(
'Merge in progress
'
)
expect
(
merge_button
).
to
have_
content
(
'Start merge train
'
)
end
end
...
...
@@ -62,7 +62,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do
click_button
'Merge immediately'
expect
(
merge_button
).
to
have_content
(
'Merge in progress
'
)
expect
(
find
(
'.media-body h4'
)).
to
have_content
(
'Merging!
'
)
end
end
end
...
...
spec/features/merge_request/user_merges_immediately_spec.rb
View file @
6b778dbf
...
...
@@ -36,7 +36,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do
Sidekiq
::
Testing
.
fake!
do
click_button
'Merge immediately'
expect
(
find
(
'.
accept-merge-request.btn-confirm'
)).
to
have_content
(
'Merge in progress
'
)
expect
(
find
(
'.
media-body h4'
)).
to
have_content
(
'Merging!
'
)
wait_for_requests
end
...
...
spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
View file @
6b778dbf
...
...
@@ -45,6 +45,8 @@ const createTestMr = (customConfig) => {
preferredAutoMergeStrategy
:
MWPS_MERGE_STRATEGY
,
availableAutoMergeStrategies
:
[
MWPS_MERGE_STRATEGY
],
mergeImmediatelyDocsPath
:
'
path/to/merge/immediately/docs
'
,
transitionStateMachine
:
()
=>
eventHub
.
$emit
(
'
StateMachineValueChanged
'
,
{
value
:
'
value
'
}),
translateStateToMachine
:
()
=>
this
.
transitionStateMachine
(),
};
Object
.
assign
(
mr
,
customConfig
.
mr
);
...
...
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