BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
3593b83a
Unverified
Commit
3593b83a
authored
May 15, 2018
by
Filipa Lacerda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Handles action icons requests in a contained way and shows a loading icon to the user
parent
bf806712
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
171 additions
and
113 deletions
+171
-113
action_component.vue
...vascripts/pipelines/components/graph/action_component.vue
+43
-24
dropdown_job_component.vue
...pts/pipelines/components/graph/dropdown_job_component.vue
+6
-7
graph_component.vue
...avascripts/pipelines/components/graph/graph_component.vue
+5
-6
job_component.vue
.../javascripts/pipelines/components/graph/job_component.vue
+6
-6
stage_column_component.vue
...pts/pipelines/components/graph/stage_column_component.vue
+6
-7
pipelines_table_row.vue
.../javascripts/pipelines/components/pipelines_table_row.vue
+1
-0
stage.vue
app/assets/javascripts/pipelines/components/stage.vue
+17
-0
pipeline_details_bundle.js
app/assets/javascripts/pipelines/pipeline_details_bundle.js
+8
-22
action_component_spec.js
spec/javascripts/pipelines/graph/action_component_spec.js
+30
-41
stage_spec.js
spec/javascripts/pipelines/stage_spec.js
+49
-0
No files found.
app/assets/javascripts/pipelines/components/graph/action_component.vue
View file @
3593b83a
<
script
>
import
$
from
'jquery'
;
import
tooltip
from
'../../../vue_shared/directives/tooltip'
;
import
Icon
from
'../../../vue_shared/components/icon.vue'
;
import
{
dasherize
}
from
'../../../lib/utils/text_utility'
;
import
eventHub
from
'../../event_hub'
;
import
axios
from
'~/lib/utils/axios_utils'
;
import
{
dasherize
}
from
'~/lib/utils/text_utility'
;
import
{
__
}
from
'~/locale'
;
import
createFlash
from
'~/flash'
;
import
tooltip
from
'~/vue_shared/directives/tooltip'
;
import
LoadingIcon
from
'~/vue_shared/components/loading_icon.vue'
;
import
Icon
from
'~/vue_shared/components/icon.vue'
;
/**
* Renders either a cancel, retry or play icon pointing to the given path.
* Renders either a cancel, retry or play icon button and handles the post request
*
* Used in:
* - mr widget mini pipeline graph: `mr_widget_pipeline.vue`
* - pipelines table
* - pipelines table in merge request page
* - pipelines table in commit page
* - pipelines detail page in big graph
*/
export
default
{
components
:
{
Icon
,
LoadingIcon
,
},
directives
:
{
...
...
@@ -32,16 +44,10 @@ export default {
required
:
true
,
},
requestFinishedFor
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
isDisabled
:
false
,
linkRequested
:
''
,
isLoading
:
false
,
};
},
...
...
@@ -51,19 +57,28 @@ export default {
return
`
${
actionIconDash
}
js-icon-
${
actionIconDash
}
`
;
},
},
watch
:
{
requestFinishedFor
()
{
if
(
this
.
requestFinishedFor
===
this
.
linkRequested
)
{
this
.
isDisabled
=
false
;
}
},
},
methods
:
{
/**
* The request should not be handled here.
* However due to this component being used in several
* different apps it avoids repetition & complexity.
*
*/
onClickAction
()
{
$
(
this
.
$el
).
tooltip
(
'hide'
);
eventHub
.
$emit
(
'postAction'
,
this
.
link
);
this
.
linkRequested
=
this
.
link
;
this
.
isDisabled
=
true
;
this
.
isLoading
=
true
;
axios
.
post
(
`
${
this
.
link
}
.json`
)
.
then
(()
=>
{
this
.
isLoading
=
false
;
this
.
$emit
(
'pipelineActionRequestComplete'
);
})
.
catch
(()
=>
{
this
.
isLoading
=
false
;
createFlash
(
__
(
'An error occurred while making the request.'
));
});
},
},
};
...
...
@@ -78,8 +93,12 @@ export default {
btn-transparent ci-action-icon-container ci-action-icon-wrapper"
:class=
"cssClass"
data-container=
"body"
:disabled=
"is
Disabled
"
:disabled=
"is
Loading
"
>
<icon
:name=
"actionIcon"
/>
<icon
v-if=
"!isLoading"
:name=
"actionIcon"
/>
<loading-icon
v-else
/>
</button>
</
template
>
app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
View file @
3593b83a
...
...
@@ -42,11 +42,6 @@ export default {
type
:
Object
,
required
:
true
,
},
requestFinishedFor
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
...
@@ -76,11 +71,15 @@ export default {
e
.
stopPropagation
();
});
},
pipelineActionRequestComplete
()
{
this
.
$emit
(
'pipelineActionRequestComplete'
);
},
},
};
</
script
>
<
template
>
<div
class=
"ci-job-dropdown-container"
>
<div
class=
"ci-job-dropdown-container
dropdown
"
>
<button
v-tooltip
type=
"button"
...
...
@@ -110,7 +109,7 @@ export default {
<job-component
:job=
"item"
css-class-job-name=
"mini-pipeline-graph-dropdown-item"
:request-finished-for=
"requestFinishedFor
"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete
"
/>
</li>
</ul>
...
...
app/assets/javascripts/pipelines/components/graph/graph_component.vue
View file @
3593b83a
...
...
@@ -16,11 +16,6 @@ export default {
type
:
Object
,
required
:
true
,
},
requestFinishedFor
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
...
@@ -51,6 +46,10 @@ export default {
return
className
;
},
refreshPipelineGraph
()
{
this
.
$emit
(
'refreshPipelineGraph'
);
},
},
};
</
script
>
...
...
@@ -74,7 +73,7 @@ export default {
:key=
"stage.name"
:stage-connector-class=
"stageConnectorClass(index, stage)"
:is-first-column=
"isFirstColumn(index)"
:request-finished-for=
"requestFinishedFor
"
@
refreshPipelineGraph=
"refreshPipelineGraph
"
/>
</ul>
</div>
...
...
app/assets/javascripts/pipelines/components/graph/job_component.vue
View file @
3593b83a
...
...
@@ -46,11 +46,6 @@ export default {
required
:
false
,
default
:
''
,
},
requestFinishedFor
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
status
()
{
...
...
@@ -84,6 +79,11 @@ export default {
return
this
.
job
.
status
&&
this
.
job
.
status
.
action
&&
this
.
job
.
status
.
action
.
path
;
},
},
methods
:
{
pipelineActionRequestComplete
()
{
this
.
$emit
(
'pipelineActionRequestComplete'
);
},
},
};
</
script
>
<
template
>
...
...
@@ -126,7 +126,7 @@ export default {
:tooltip-text=
"status.action.title"
:link=
"status.action.path"
:action-icon=
"status.action.icon"
:request-finished-for=
"requestFinishedFor
"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete
"
/>
</div>
</
template
>
app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
View file @
3593b83a
...
...
@@ -29,12 +29,6 @@ export default {
required
:
false
,
default
:
''
,
},
requestFinishedFor
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
methods
:
{
...
...
@@ -49,6 +43,10 @@ export default {
buildConnnectorClass
(
index
)
{
return
index
===
0
&&
!
this
.
isFirstColumn
?
'left-connector'
:
''
;
},
pipelineActionRequestComplete
()
{
this
.
$emit
(
'refreshPipelineGraph'
);
},
},
};
</
script
>
...
...
@@ -75,12 +73,13 @@ export default {
v-if=
"job.size === 1"
:job=
"job"
css-class-job-name=
"build-content"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
<dropdown-job-component
v-if=
"job.size > 1"
:job=
"job"
:request-finished-for=
"requestFinishedFor
"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete
"
/>
</li>
...
...
app/assets/javascripts/pipelines/components/pipelines_table_row.vue
View file @
3593b83a
...
...
@@ -297,6 +297,7 @@
v-for=
"(stage, index) in pipeline.details.stages"
:key=
"index"
>
<pipeline-stage
type=
"PIPELINES_TABLE"
:stage=
"stage"
:update-dropdown=
"updateGraphDropdown"
/>
...
...
app/assets/javascripts/pipelines/components/stage.vue
View file @
3593b83a
...
...
@@ -44,6 +44,12 @@ export default {
required
:
false
,
default
:
false
,
},
type
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
...
...
@@ -133,6 +139,16 @@ export default {
isDropdownOpen
()
{
return
this
.
$el
.
classList
.
contains
(
'open'
);
},
pipelineActionRequestComplete
()
{
if
(
this
.
type
===
'PIPELINES_TABLE'
)
{
// warn the table to update
eventHub
.
$emit
(
'clickedDropdown'
);
}
else
{
// refresh the content
this
.
fetchJobs
();
}
},
},
};
</
script
>
...
...
@@ -188,6 +204,7 @@ export default {
<job-component
:job=
"job"
css-class-job-name=
"mini-pipeline-graph-dropdown-item"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
</li>
</ul>
...
...
app/assets/javascripts/pipelines/pipeline_details_bundle.js
View file @
3593b83a
...
...
@@ -25,30 +25,14 @@ export default () => {
data
()
{
return
{
mediator
,
requestFinishedFor
:
null
,
};
},
created
()
{
eventHub
.
$on
(
'postAction'
,
this
.
postAction
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'postAction'
,
this
.
postAction
);
},
methods
:
{
postAction
(
action
)
{
// Click was made, reset this variable
this
.
requestFinishedFor
=
null
;
this
.
mediator
.
service
.
postAction
(
action
)
.
then
(()
=>
{
this
.
mediator
.
refreshPipeline
();
this
.
requestFinishedFor
=
action
;
})
.
catch
(()
=>
{
this
.
requestFinishedFor
=
action
;
Flash
(
__
(
'An error occurred while making the request.'
));
});
requestRefreshPipelineGraph
()
{
// When an action is clicked
// (wether in the dropdown or in the main nodes, we refresh the big graph)
this
.
mediator
.
refreshPipeline
()
.
catch
(()
=>
Flash
(
__
(
'An error occurred while making the request.'
)));
},
},
render
(
createElement
)
{
...
...
@@ -56,7 +40,9 @@ export default () => {
props
:
{
isLoading
:
this
.
mediator
.
state
.
isLoading
,
pipeline
:
this
.
mediator
.
store
.
state
.
pipeline
,
requestFinishedFor
:
this
.
requestFinishedFor
,
},
on
:
{
refreshPipelineGraph
:
this
.
requestRefreshPipelineGraph
,
},
});
},
...
...
spec/javascripts/pipelines/graph/action_component_spec.js
View file @
3593b83a
import
Vue
from
'vue'
;
import
MockAdapter
from
'axios-mock-adapter'
;
import
axios
from
'~/lib/utils/axios_utils'
;
import
actionComponent
from
'~/pipelines/components/graph/action_component.vue'
;
import
eventHub
from
'~/pipelines/event_hub'
;
import
mountComponent
from
'../../helpers/vue_mount_component_helper'
;
describe
(
'pipeline graph action component'
,
()
=>
{
let
component
;
let
mock
;
beforeEach
(
done
=>
{
const
ActionComponent
=
Vue
.
extend
(
actionComponent
);
mock
=
new
MockAdapter
(
axios
);
mock
.
onPost
(
'foo.json'
).
reply
(
200
);
component
=
mountComponent
(
ActionComponent
,
{
tooltipText
:
'bar'
,
link
:
'foo'
,
...
...
@@ -18,15 +24,10 @@ describe('pipeline graph action component', () => {
});
afterEach
(()
=>
{
mock
.
restore
();
component
.
$destroy
();
});
it
(
'should emit an event with the provided link'
,
()
=>
{
eventHub
.
$on
(
'postAction'
,
link
=>
{
expect
(
link
).
toEqual
(
'foo'
);
});
});
it
(
'should render the provided title as a bootstrap tooltip'
,
()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'data-original-title'
)).
toEqual
(
'bar'
);
});
...
...
@@ -34,10 +35,12 @@ describe('pipeline graph action component', () => {
it
(
'should update bootstrap tooltip when title changes'
,
done
=>
{
component
.
tooltipText
=
'changed'
;
setTimeout
(()
=>
{
component
.
$nextTick
()
.
then
(()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'data-original-title'
)).
toBe
(
'changed'
);
done
();
});
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'should render an svg'
,
()
=>
{
...
...
@@ -45,44 +48,30 @@ describe('pipeline graph action component', () => {
expect
(
component
.
$el
.
querySelector
(
'svg'
)).
toBeDefined
();
});
it
(
'disables the button when clicked'
,
done
=>
{
component
.
$el
.
click
();
component
.
$nextTick
(()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'disabled'
)).
toEqual
(
'disabled'
);
done
();
});
});
it
(
're-enabled the button when `requestFinishedFor` matches `linkRequested`'
,
done
=>
{
component
.
$el
.
click
();
it
(
'renders a loading icon while component is loading'
,
done
=>
{
component
.
isLoading
=
true
;
component
.
$nextTick
()
component
.
$nextTick
()
.
then
(()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'disabled'
)).
toEqual
(
'disabled'
);
component
.
requestFinishedFor
=
'foo'
;
})
.
then
(()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'disabled'
)).
toBeNull
();
expect
(
component
.
$el
.
querySelector
(
'.fa-spin'
)).
not
.
toBeNull
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'does not re-enable the button when `requestFinishedFor` does not matches `linkRequested`'
,
done
=>
{
component
.
$el
.
click
();
describe
(
'on click'
,
()
=>
{
it
(
'emits `pipelineActionRequestComplete` after a successfull request'
,
done
=>
{
spyOn
(
component
,
'$emit'
);
component
.
$nextTick
()
.
then
(()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'disabled'
)).
toEqual
(
'disabled'
);
component
.
requestFinishedFor
=
'bar'
;
})
.
then
(()
=>
{
expect
(
component
.
$el
.
getAttribute
(
'disabled'
)).
toEqual
(
'disabled'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
component
.
$el
.
click
();
expect
(
component
.
isLoading
).
toEqual
(
true
);
component
.
$nextTick
()
.
then
(()
=>
{
expect
(
component
.
$emit
).
toHaveBeenCalledWith
(
'pipelineActionRequestComplete'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
spec/javascripts/pipelines/stage_spec.js
View file @
3593b83a
...
...
@@ -102,4 +102,53 @@ describe('Pipelines stage component', () => {
});
});
});
describe
(
'pipelineActionRequestComplete'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
'path.json'
).
reply
(
200
,
stageReply
);
mock
.
onPost
(
`
${
stageReply
.
latest_statuses
[
0
].
status
.
action
.
path
}
.json`
).
reply
(
200
);
});
describe
(
'within pipeline table'
,
()
=>
{
it
(
'emits `clickedDropdown` event when `pipelineActionRequestComplete` is triggered'
,
done
=>
{
spyOn
(
eventHub
,
'$emit'
);
component
.
type
=
'PIPELINES_TABLE'
;
component
.
$el
.
querySelector
(
'button'
).
click
();
setTimeout
(()
=>
{
component
.
$el
.
querySelector
(
'.js-ci-action'
).
click
();
component
.
$nextTick
()
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'clickedDropdown'
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledTimes
(
2
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
},
0
);
});
});
describe
(
'without a type'
,
()
=>
{
it
(
'fetches dropdown content again'
,
done
=>
{
spyOn
(
component
,
'fetchJobs'
).
and
.
callThrough
();
component
.
$el
.
querySelector
(
'button'
).
click
();
expect
(
component
.
fetchJobs
).
toHaveBeenCalledTimes
(
1
);
setTimeout
(()
=>
{
component
.
$el
.
querySelector
(
'.js-ci-action'
).
click
();
component
.
$nextTick
()
.
then
(()
=>
{
expect
(
component
.
fetchJobs
).
toHaveBeenCalledTimes
(
2
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
},
0
);
});
});
});
});
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