Commit d01d509b authored by Tim Zallmann's avatar Tim Zallmann

Fixing Icons

parent e3c7d264
<script> <script>
import getActionIcon from '../../../vue_shared/ci_action_icons'; import getActionIcon from '../../../vue_shared/ci_action_icons';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
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 pointing to the given path.
...@@ -29,6 +30,10 @@ ...@@ -29,6 +30,10 @@
}, },
}, },
components: {
icon,
},
directives: { directives: {
tooltip, tooltip,
}, },
...@@ -50,14 +55,11 @@ ...@@ -50,14 +55,11 @@
:data-method="actionMethod" :data-method="actionMethod"
:title="tooltipText" :title="tooltipText"
:href="link" :href="link"
class="ci-action-icon-container" class="ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass"
data-container="body"> data-container="body">
<icon
<i name="stop"
class="ci-action-icon-wrapper" size="16"/>
:class="cssClass"
v-html="actionIconSvg"
aria-hidden="true"
/>
</a> </a>
</template> </template>
<script> <script>
import getActionIcon from '../../../vue_shared/ci_action_icons'; import getActionIcon from '../../../vue_shared/ci_action_icons';
import icon from '../../../vue_shared/components/icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
/** /**
...@@ -29,12 +30,17 @@ ...@@ -29,12 +30,17 @@
}, },
}, },
components: {
icon,
},
directives: { directives: {
tooltip, tooltip,
}, },
computed: { computed: {
actionIconSvg() { actionIconSvg() {
alert('LA');
return getActionIcon(this.actionIcon); return getActionIcon(this.actionIcon);
}, },
}, },
...@@ -49,7 +55,9 @@ ...@@ -49,7 +55,9 @@
rel="nofollow" rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon" class="ci-action-icon-wrapper js-ci-status-icon"
data-container="body" data-container="body"
v-html="actionIconSvg"
aria-label="Job's action"> aria-label="Job's action">
{{actionIcon}}
<icon
name="retry"/>
</a> </a>
</template> </template>
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* "group": "success", * "group": "success",
* "details_path": "/root/ci-mock/builds/4256", * "details_path": "/root/ci-mock/builds/4256",
* "action": { * "action": {
* "icon": "icon_action_retry", * "icon": "retry",
* "title": "Retry", * "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry", * "path": "/root/ci-mock/builds/4256/retry",
* "method": "post" * "method": "post"
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
* "group": "success", * "group": "success",
* "details_path": "/root/ci-mock/builds/4256", * "details_path": "/root/ci-mock/builds/4256",
* "action": { * "action": {
* "icon": "icon_action_retry", * "icon": "retry",
* "title": "Retry", * "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry", * "path": "/root/ci-mock/builds/4256/retry",
* "method": "post" * "method": "post"
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
*/ */
import Flash from '../../flash'; import Flash from '../../flash';
import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons'; import icon from '../../vue_shared/components/icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -45,6 +45,7 @@ export default { ...@@ -45,6 +45,7 @@ export default {
components: { components: {
loadingIcon, loadingIcon,
icon,
}, },
updated() { updated() {
...@@ -121,10 +122,6 @@ export default { ...@@ -121,10 +122,6 @@ export default {
triggerButtonClass() { triggerButtonClass() {
return `ci-status-icon-${this.stage.status.group}`; return `ci-status-icon-${this.stage.status.group}`;
}, },
svgIcon() {
return borderlessStatusIconEntityMap[this.stage.status.icon];
},
}, },
}; };
</script> </script>
...@@ -145,9 +142,10 @@ export default { ...@@ -145,9 +142,10 @@ export default {
aria-expanded="false"> aria-expanded="false">
<span <span
v-html="svgIcon"
aria-hidden="true" aria-hidden="true"
:aria-label="stage.title"> :aria-label="stage.title">
<icon
:name="stage.status.icon"/>
</span> </span>
<i <i
......
import PipelineStage from '../../pipelines/components/stage.vue'; import PipelineStage from '../../pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue'; import ciIcon from '../../vue_shared/components/ci_icon.vue';
import { statusIconEntityMap } from '../../vue_shared/ci_status_icons'; import icon from '../../vue_shared/components/icon.vue';
export default { export default {
name: 'MRWidgetPipeline', name: 'MRWidgetPipeline',
...@@ -10,6 +10,7 @@ export default { ...@@ -10,6 +10,7 @@ export default {
components: { components: {
'pipeline-stage': PipelineStage, 'pipeline-stage': PipelineStage,
ciIcon, ciIcon,
icon,
}, },
computed: { computed: {
hasPipeline() { hasPipeline() {
...@@ -20,9 +21,6 @@ export default { ...@@ -20,9 +21,6 @@ export default {
return hasCI && !ciStatus; return hasCI && !ciStatus;
}, },
svg() {
return statusIconEntityMap.icon_status_failed;
},
stageText() { stageText() {
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage'; return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
}, },
...@@ -38,8 +36,10 @@ export default { ...@@ -38,8 +36,10 @@ export default {
<template v-if="hasCIError"> <template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
<span <span
v-html="svg" aria-hidden="true">
aria-hidden="true"></span> <icon
name="status_failed"/>
</span>
</div> </div>
<div class="media-body"> <div class="media-body">
Could not connect to the CI server. Please check your settings and try again Could not connect to the CI server. Please check your settings and try again
......
import BORDERLESS_CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
import BORDERLESS_CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
import BORDERLESS_FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
import BORDERLESS_MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
import BORDERLESS_PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
import BORDERLESS_RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
import BORDERLESS_SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
import BORDERLESS_SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
import BORDERLESS_WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
import CANCELED_SVG from 'icons/_icon_status_canceled.svg';
import CREATED_SVG from 'icons/_icon_status_created.svg';
import FAILED_SVG from 'icons/_icon_status_failed.svg';
import MANUAL_SVG from 'icons/_icon_status_manual.svg';
import PENDING_SVG from 'icons/_icon_status_pending.svg';
import RUNNING_SVG from 'icons/_icon_status_running.svg';
import SKIPPED_SVG from 'icons/_icon_status_skipped.svg';
import SUCCESS_SVG from 'icons/_icon_status_success.svg';
import WARNING_SVG from 'icons/_icon_status_warning.svg';
export const borderlessStatusIconEntityMap = {
icon_status_canceled: BORDERLESS_CANCELED_SVG,
icon_status_created: BORDERLESS_CREATED_SVG,
icon_status_failed: BORDERLESS_FAILED_SVG,
icon_status_manual: BORDERLESS_MANUAL_SVG,
icon_status_pending: BORDERLESS_PENDING_SVG,
icon_status_running: BORDERLESS_RUNNING_SVG,
icon_status_skipped: BORDERLESS_SKIPPED_SVG,
icon_status_success: BORDERLESS_SUCCESS_SVG,
icon_status_warning: BORDERLESS_WARNING_SVG,
};
export const statusIconEntityMap = {
icon_status_canceled: CANCELED_SVG,
icon_status_created: CREATED_SVG,
icon_status_failed: FAILED_SVG,
icon_status_manual: MANUAL_SVG,
icon_status_pending: PENDING_SVG,
icon_status_running: RUNNING_SVG,
icon_status_skipped: SKIPPED_SVG,
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
<script> <script>
import { statusIconEntityMap } from '../ci_status_icons'; import icon from '../../vue_shared/components/icon.vue';
/** /**
* Renders CI icon based on API response shared between all places where it is used. * Renders CI icon based on API response shared between all places where it is used.
...@@ -30,11 +30,11 @@ ...@@ -30,11 +30,11 @@
}, },
}, },
computed: { components: {
statusIconSvg() { icon,
return statusIconEntityMap[this.status.icon]; },
},
computed: {
cssClass() { cssClass() {
const status = this.status.group; const status = this.status.group;
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`; return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
...@@ -44,7 +44,8 @@ ...@@ -44,7 +44,8 @@
</script> </script>
<template> <template>
<span <span
:class="cssClass" :class="cssClass">
v-html="statusIconSvg"> <icon
:name="status.icon"/>
</span> </span>
</template> </template>
<script>
/* This is a re-usable vue component for rendering a svg sprite
icon
Sample configuration:
<icon
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
export default {
props: {
name: {
type: String,
required: true,
},
size: {
type: Number,
required: false,
default: 0,
},
cssClass: {
type: String,
required: false,
default: '',
},
},
computed: {
spriteHref() {
return `${gon.sprite_icons}#${this.name}`
},
fullCssClass() {
let classString = '' || this.cssClass;
// if (this.size) classString += `s${this.size}`
return classString;
},
},
};
</script>
<template>
<svg
:class="fullCssClass">
<use v-bind="{'xlink:href':spriteHref}"/>
</svg>
</template>
...@@ -165,8 +165,9 @@ ...@@ -165,8 +165,9 @@
z-index: 300; z-index: 300;
} }
.ci-action-icon-wrapper { .ci-action-icon-wrapper svg {
line-height: 16px; width: 16px;
height: 16px;
} }
} }
......
...@@ -452,7 +452,7 @@ ...@@ -452,7 +452,7 @@
} }
// Action Icons in big pipeline-graph nodes // Action Icons in big pipeline-graph nodes
.ci-action-icon-container .ci-action-icon-wrapper { .ci-action-icon-container.ci-action-icon-wrapper {
height: 30px; height: 30px;
width: 30px; width: 30px;
background: $white-light; background: $white-light;
...@@ -468,8 +468,10 @@ ...@@ -468,8 +468,10 @@
svg { svg {
fill: $gl-text-color-secondary; fill: $gl-text-color-secondary;
position: relative; position: relative;
left: -1px; left: 5px;
top: -1px; top: 2px;
width: 18px;
height: 18px;
} }
&:hover svg { &:hover svg {
......
...@@ -22,7 +22,7 @@ export default { ...@@ -22,7 +22,7 @@ export default {
details_path: '/root/ci-mock/-/jobs/4757', details_path: '/root/ci-mock/-/jobs/4757',
favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
action: { action: {
icon: 'icon_action_retry', icon: 'retry',
title: 'Retry', title: 'Retry',
path: '/root/ci-mock/-/jobs/4757/retry', path: '/root/ci-mock/-/jobs/4757/retry',
method: 'post', method: 'post',
......
...@@ -11,7 +11,7 @@ describe('pipeline graph action component', () => { ...@@ -11,7 +11,7 @@ describe('pipeline graph action component', () => {
tooltipText: 'bar', tooltipText: 'bar',
link: 'foo', link: 'foo',
actionMethod: 'post', actionMethod: 'post',
actionIcon: 'icon_action_cancel', actionIcon: 'cancel',
}, },
}).$mount(); }).$mount();
......
...@@ -14,7 +14,7 @@ describe('pipeline graph job component', () => { ...@@ -14,7 +14,7 @@ describe('pipeline graph job component', () => {
group: 'success', group: 'success',
details_path: '/root/ci-mock/builds/4256', details_path: '/root/ci-mock/builds/4256',
action: { action: {
icon: 'icon_action_retry', icon: 'retry',
title: 'Retry', title: 'Retry',
path: '/root/ci-mock/builds/4256/retry', path: '/root/ci-mock/builds/4256/retry',
method: 'post', method: 'post',
......
...@@ -39,7 +39,7 @@ export default { ...@@ -39,7 +39,7 @@ export default {
"details_path": "/root/ci-mock/builds/4153", "details_path": "/root/ci-mock/builds/4153",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": { "action": {
"icon": "icon_action_retry", "icon": "retry",
"title": "Retry", "title": "Retry",
"path": "/root/ci-mock/builds/4153/retry", "path": "/root/ci-mock/builds/4153/retry",
"method": "post" "method": "post"
...@@ -62,7 +62,7 @@ export default { ...@@ -62,7 +62,7 @@ export default {
"details_path": "/root/ci-mock/builds/4153", "details_path": "/root/ci-mock/builds/4153",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": { "action": {
"icon": "icon_action_retry", "icon": "retry",
"title": "Retry", "title": "Retry",
"path": "/root/ci-mock/builds/4153/retry", "path": "/root/ci-mock/builds/4153/retry",
"method": "post" "method": "post"
...@@ -96,7 +96,7 @@ export default { ...@@ -96,7 +96,7 @@ export default {
"details_path": "/root/ci-mock/builds/4166", "details_path": "/root/ci-mock/builds/4166",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": { "action": {
"icon": "icon_action_retry", "icon": "retry",
"title": "Retry", "title": "Retry",
"path": "/root/ci-mock/builds/4166/retry", "path": "/root/ci-mock/builds/4166/retry",
"method": "post" "method": "post"
...@@ -119,7 +119,7 @@ export default { ...@@ -119,7 +119,7 @@ export default {
"details_path": "/root/ci-mock/builds/4166", "details_path": "/root/ci-mock/builds/4166",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": { "action": {
"icon": "icon_action_retry", "icon": "retry",
"title": "Retry", "title": "Retry",
"path": "/root/ci-mock/builds/4166/retry", "path": "/root/ci-mock/builds/4166/retry",
"method": "post" "method": "post"
...@@ -138,7 +138,7 @@ export default { ...@@ -138,7 +138,7 @@ export default {
"details_path": "/root/ci-mock/builds/4159", "details_path": "/root/ci-mock/builds/4159",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": { "action": {
"icon": "icon_action_retry", "icon": "retry",
"title": "Retry", "title": "Retry",
"path": "/root/ci-mock/builds/4159/retry", "path": "/root/ci-mock/builds/4159/retry",
"method": "post" "method": "post"
...@@ -161,7 +161,7 @@ export default { ...@@ -161,7 +161,7 @@ export default {
"details_path": "/root/ci-mock/builds/4159", "details_path": "/root/ci-mock/builds/4159",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": { "action": {
"icon": "icon_action_retry", "icon": "retry",
"title": "Retry", "title": "Retry",
"path": "/root/ci-mock/builds/4159/retry", "path": "/root/ci-mock/builds/4159/retry",
"method": "post" "method": "post"
......
...@@ -13,7 +13,7 @@ describe('stage column component', () => { ...@@ -13,7 +13,7 @@ describe('stage column component', () => {
group: 'success', group: 'success',
details_path: '/root/ci-mock/builds/4256', details_path: '/root/ci-mock/builds/4256',
action: { action: {
icon: 'icon_action_retry', icon: 'retry',
title: 'Retry', title: 'Retry',
path: '/root/ci-mock/builds/4256/retry', path: '/root/ci-mock/builds/4256/retry',
method: 'post', method: 'post',
......
import Vue from 'vue'; import Vue from 'vue';
import { statusIconEntityMap } from '~/vue_shared/ci_status_icons';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline'; import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline';
import mockData from '../mock_data'; import mockData from '../mock_data';
...@@ -29,14 +28,6 @@ describe('MRWidgetPipeline', () => { ...@@ -29,14 +28,6 @@ describe('MRWidgetPipeline', () => {
}); });
describe('computed', () => { describe('computed', () => {
describe('svg', () => {
it('should have the proper SVG icon', () => {
const vm = createComponent({ pipeline: mockData.pipeline });
expect(vm.svg).toEqual(statusIconEntityMap.icon_status_failed);
});
});
describe('hasPipeline', () => { describe('hasPipeline', () => {
it('should return true when there is a pipeline', () => { it('should return true when there is a pipeline', () => {
expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0); expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0);
...@@ -142,6 +133,7 @@ describe('MRWidgetPipeline', () => { ...@@ -142,6 +133,7 @@ describe('MRWidgetPipeline', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect(el.querySelectorAll('.js-ci-error').length).toEqual(1); expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
expect(el.innerText).toContain('Could not connect to the CI server'); expect(el.innerText).toContain('Could not connect to the CI server');
expect(el.querySelector('.ci-status-icon svg use').getAttribute('xlink:href')).toContain('status_failed');
done(); done();
}); });
}); });
......
import getActionIcon from '~/vue_shared/ci_action_icons';
import cancelSVG from 'icons/_icon_action_cancel.svg';
import retrySVG from 'icons/_icon_action_retry.svg';
import playSVG from 'icons/_icon_action_play.svg';
import stopSVG from 'icons/_icon_action_stop.svg';
describe('getActionIcon', () => {
it('should return an empty string', () => {
expect(getActionIcon()).toEqual('');
});
it('should return cancel svg', () => {
expect(getActionIcon('icon_action_cancel')).toEqual(cancelSVG);
});
it('should return retry svg', () => {
expect(getActionIcon('icon_action_retry')).toEqual(retrySVG);
});
it('should return play svg', () => {
expect(getActionIcon('icon_action_play')).toEqual(playSVG);
});
it('should render stop svg', () => {
expect(getActionIcon('icon_action_stop')).toEqual(stopSVG);
});
});
import { borderlessStatusIconEntityMap, statusIconEntityMap } from '~/vue_shared/ci_status_icons';
describe('CI status icons', () => {
const statuses = [
'icon_status_canceled',
'icon_status_created',
'icon_status_failed',
'icon_status_manual',
'icon_status_pending',
'icon_status_running',
'icon_status_skipped',
'icon_status_success',
'icon_status_warning',
];
it('should have a dictionary for borderless icons', () => {
statuses.forEach((status) => {
expect(borderlessStatusIconEntityMap[status]).toBeDefined();
});
});
it('should have a dictionary for icons', () => {
statuses.forEach((status) => {
expect(statusIconEntityMap[status]).toBeDefined();
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment