Commit bb829cc8 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ph/341047/widgetExtensionLevel3' into 'master'

Added support for level 3 in widget extensions

See merge request gitlab-org/gitlab!80486
parents 6c58928f 73132d91
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
import { import {
GlButton, GlButton,
GlLoadingIcon, GlLoadingIcon,
GlLink,
GlBadge,
GlSafeHtmlDirective, GlSafeHtmlDirective,
GlTooltipDirective, GlTooltipDirective,
GlIntersectionObserver, GlIntersectionObserver,
...@@ -17,6 +15,7 @@ import Poll from '~/lib/utils/poll'; ...@@ -17,6 +15,7 @@ import Poll from '~/lib/utils/poll';
import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants'; import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
import StatusIcon from './status_icon.vue'; import StatusIcon from './status_icon.vue';
import Actions from './actions.vue'; import Actions from './actions.vue';
import ChildContent from './child_content.vue';
import { generateText } from './utils'; import { generateText } from './utils';
export const LOADING_STATES = { export const LOADING_STATES = {
...@@ -30,12 +29,11 @@ export default { ...@@ -30,12 +29,11 @@ export default {
components: { components: {
GlButton, GlButton,
GlLoadingIcon, GlLoadingIcon,
GlLink,
GlBadge,
GlIntersectionObserver, GlIntersectionObserver,
SmartVirtualList, SmartVirtualList,
StatusIcon, StatusIcon,
Actions, Actions,
ChildContent,
}, },
directives: { directives: {
SafeHtml: GlSafeHtmlDirective, SafeHtml: GlSafeHtmlDirective,
...@@ -196,9 +194,6 @@ export default { ...@@ -196,9 +194,6 @@ export default {
Sentry.captureException(e); Sentry.captureException(e);
}); });
}, },
isArray(arr) {
return Array.isArray(arr);
},
appear(index) { appear(index) {
if (index === this.fullData.length - 1) { if (index === this.fullData.length - 1) {
this.showFade = false; this.showFade = false;
...@@ -299,60 +294,14 @@ export default { ...@@ -299,60 +294,14 @@ export default {
class="gl-py-3 gl-pl-7" class="gl-py-3 gl-pl-7"
data-testid="extension-list-item" data-testid="extension-list-item"
> >
<div class="gl-w-full"> <gl-intersection-observer
<div v-if="data.header" class="gl-mb-2"> :options="{ rootMargin: '100px', thresholds: 0.1 }"
<template v-if="isArray(data.header)"> class="gl-w-full"
<component @appear="appear(index)"
:is="headerI === 0 ? 'strong' : 'span'" @disappear="disappear(index)"
v-for="(header, headerI) in data.header" >
:key="headerI" <child-content :data="data" :widget-label="widgetLabel" :level="2" />
v-safe-html="generateText(header)" </gl-intersection-observer>
class="gl-display-block"
/>
</template>
<strong v-else v-safe-html="generateText(data.header)"></strong>
</div>
<div class="gl-display-flex">
<status-icon
v-if="data.icon"
:icon-name="data.icon.name"
:size="12"
class="gl-pl-0"
/>
<gl-intersection-observer
:options="{ rootMargin: '100px', thresholds: 0.1 }"
class="gl-w-full"
@appear="appear(index)"
@disappear="disappear(index)"
>
<div class="gl-flex-wrap gl-display-flex gl-w-full">
<div class="gl-mr-4 gl-display-flex gl-align-items-center">
<p v-safe-html="generateText(data.text)" class="gl-m-0"></p>
</div>
<div v-if="data.link">
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
</div>
<div v-if="data.supportingText">
<p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p>
</div>
<gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
{{ data.badge.text }}
</gl-badge>
<actions
:widget="$options.label || $options.name"
:tertiary-buttons="data.actions"
class="gl-ml-auto"
/>
</div>
<p
v-if="data.subtext"
v-safe-html="generateText(data.subtext)"
class="gl-m-0 gl-font-sm"
></p>
</gl-intersection-observer>
</div>
</div>
</li> </li>
</smart-virtual-list> </smart-virtual-list>
<div <div
......
<script>
import { GlBadge, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
import StatusIcon from './status_icon.vue';
import Actions from './actions.vue';
import { generateText } from './utils';
export default {
name: 'ChildContent',
components: {
GlBadge,
GlLink,
StatusIcon,
Actions,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
},
props: {
data: {
type: Object,
required: true,
},
widgetLabel: {
type: String,
required: true,
},
level: {
type: Number,
required: true,
},
},
methods: {
isArray(arr) {
return Array.isArray(arr);
},
generateText,
},
};
</script>
<template>
<div :class="{ 'gl-pl-6': level === 3 }" class="gl-w-full">
<div v-if="data.header" class="gl-mb-2">
<template v-if="isArray(data.header)">
<component
:is="headerI === 0 ? 'strong' : 'span'"
v-for="(header, headerI) in data.header"
:key="headerI"
v-safe-html="generateText(header)"
class="gl-display-block"
/>
</template>
<strong v-else v-safe-html="generateText(data.header)"></strong>
</div>
<div class="gl-display-flex">
<status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" />
<div class="gl-w-full">
<div class="gl-flex-wrap gl-display-flex gl-w-full">
<div class="gl-mr-4 gl-display-flex gl-align-items-center">
<p v-safe-html="generateText(data.text)" class="gl-m-0"></p>
</div>
<div v-if="data.link">
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
</div>
<div v-if="data.supportingText">
<p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p>
</div>
<gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
{{ data.badge.text }}
</gl-badge>
<actions :widget="widgetLabel" :tertiary-buttons="data.actions" class="gl-ml-auto" />
</div>
<p
v-if="data.subtext"
v-safe-html="generateText(data.subtext)"
class="gl-m-0 gl-font-sm"
></p>
</div>
</div>
<template v-if="data.children && level === 2">
<ul class="gl-m-0 gl-p-0 gl-list-style-none">
<li>
<child-content
v-for="childData in data.children"
:key="childData.id"
:data="childData"
:widget-label="widgetLabel"
:level="3"
data-testid="child-content"
/>
</li>
</ul>
</template>
</div>
</template>
...@@ -88,6 +88,16 @@ export default { ...@@ -88,6 +88,16 @@ export default {
// text: 'Link text', // Required: Text to be used inside the link // text: 'Link text', // Required: Text to be used inside the link
// }, // },
actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }], actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }],
children: [
{
id: `child-${issue.id}`,
header: 'New',
text: '%{critical_start}1 Critical%{critical_end}',
icon: {
name: EXTENSION_ICONS.error,
},
},
],
})); }));
}); });
}, },
......
...@@ -128,6 +128,7 @@ mentioned below: ...@@ -128,6 +128,7 @@ mentioned below:
variant: '', // Optional: GitLab UI badge variant, defaults to info variant: '', // Optional: GitLab UI badge variant, defaults to info
}, },
actions: [], // Optional: Action button for row actions: [], // Optional: Action button for row
children: [], // Optional: Child content to render, structure matches the same structure
} }
``` ```
......
import { shallowMount } from '@vue/test-utils';
import ChildContent from '~/vue_merge_request_widget/components/extensions/child_content.vue';
let wrapper;
const mockData = () => ({
header: 'Test header',
text: 'Test content',
icon: {
name: 'error',
},
});
function factory(propsData) {
wrapper = shallowMount(ChildContent, {
propsData: {
...propsData,
widgetLabel: 'Test',
},
});
}
describe('MR widget extension child content', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders child components', () => {
factory({
data: {
...mockData(),
children: [mockData()],
},
level: 2,
});
expect(wrapper.find('[data-testid="child-content"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="child-content"]').props('level')).toBe(3);
});
});
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