Skip to content

Commit 688685c

Browse files
committed
feat(v-quicksight-generative-qna): add new component v-quicksight-generative-qna
1 parent cbf798b commit 688685c

File tree

8 files changed

+317
-3
lines changed

8 files changed

+317
-3
lines changed

docs/.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export default defineConfig({
2727
{ text: 'v-quicksight-dashboard', link: '/v-quicksight-dashboard' },
2828
{ text: 'v-quicksight-visual', link: '/v-quicksight-visual' },
2929
{ text: 'v-quicksight-console', link: '/v-quicksight-console' },
30-
{ text: 'v-quicksight-search', link: '/v-quicksight-search' }
30+
{ text: 'v-quicksight-search', link: '/v-quicksight-search' },
31+
{ text: 'v-quicksight-generative-qna', link: '/v-quicksight-generative-qna' },
3132
]
3233
}
3334
],
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# v-quicksight-generative-qna
2+
3+
<script setup>
4+
import GenerativeQnAOne from "@demos/GenerativeQnAOne.vue"
5+
</script>
6+
7+
<ClientOnly>
8+
<!-- <GenerativeQnAOne /> -->
9+
</ClientOnly>
10+
11+
::: code-group
12+
13+
<<< ../src/demos/GenerativeQnAOne.vue
14+
15+
:::
16+
17+
## Props
18+
19+
All of the following properties are passed with their default values from the `amazon-quicksight-embedding-sdk` one by one:
20+
21+
- [Common `ContentOptions` for All Embedding Experiences
22+
](https://github.com/awslabs/amazon-quicksight-embedding-sdk#common-properties-of-contentoptions-for-all-embedding-experiences)
23+
- [Common `FrameOptions` for All Embedding Experiences
24+
](https://github.com/awslabs/amazon-quicksight-embedding-sdk#common-properties-of-frameoptions-for-all-embedding-experiences)
25+
- [Visual Embedding ContentOptions](https://github.com/awslabs/amazon-quicksight-embedding-sdk#contentoptions-4)
26+
- [Visual Embedding FrameOptions](https://github.com/awslabs/amazon-quicksight-embedding-sdk#frameoptions-4)
27+
28+
**Additional and important properties**
29+
30+
| **property** | **type** | **required** | **default** | **description** |
31+
|--------------|--------------|--------------|--------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
32+
| `container` | `string` | no | The default container created by the component itself. | The container where the `iframe` for embedding is created. |
33+
| `id` | `string` | no | `v-quicksight-visual-${nanoid(6)}` | The `id` for the created `iframe` container element (parent) |
34+
| `question` | `string` | no | `undefined` | The question to ask or a nullish value if the search should be closed. This property is watched for changes. |
35+
36+
## Events
37+
38+
**`change`**
39+
40+
Emits a generic event containing the changes as well as optionally the meta data of the frame.
41+
42+
```ts
43+
type VQuicksightChange = {
44+
changeEvent: SimpleChangeEvent;
45+
metadata?: ExperienceFrameMetadata;
46+
}
47+
```
48+
49+
**`message`**
50+
51+
Emits a generic event containing the message as well as optionally the meta data of the frame.
52+
53+
```ts
54+
type VQuicksightMessage = {
55+
messageEvent: SimpleMessageEvent;
56+
experienceMetadata?: ExperienceFrameMetadata;
57+
}
58+
```
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<script setup lang="ts">
2+
import type { VQuicksightGenerativeQnAContentOptions, VQuicksightFrameOptions } from '../types'
3+
import type {
4+
GenerativeQnAContentOptions,
5+
GenerativeQnAExperience,
6+
EmbeddingContext,
7+
ExperienceFrameMetadata,
8+
FrameOptions,
9+
EmbeddingEvents
10+
} from 'amazon-quicksight-embedding-sdk'
11+
import { nanoid } from 'nanoid'
12+
import type { Ref } from 'vue'
13+
import { computed, inject, ref, watch } from 'vue'
14+
import { EmbeddingContextInjectionKey } from '../symbols'
15+
16+
const props = withDefaults(
17+
defineProps<
18+
VQuicksightFrameOptions &
19+
VQuicksightGenerativeQnAContentOptions & {
20+
container?: string | HTMLElement // override from FrameOptions and make it optional
21+
id?: string
22+
question?: string
23+
}
24+
>(),
25+
{
26+
width: '100%',
27+
height: '100%',
28+
withIframePlaceholder: false,
29+
showTopicName: true,
30+
showPinboard: true,
31+
allowTopicSelection: true,
32+
allowFullscreen: true
33+
}
34+
)
35+
36+
const emit = defineEmits<{
37+
(e: 'change', data: { changeEvent: EmbeddingEvents; metadata?: ExperienceFrameMetadata }): void
38+
(
39+
e: 'message',
40+
data: { messageEvent: EmbeddingEvents; experienceMetadata?: ExperienceFrameMetadata }
41+
): void
42+
}>()
43+
44+
const embeddingContext = inject<Ref<EmbeddingContext>>(EmbeddingContextInjectionKey)
45+
46+
const qnaFrame = ref<GenerativeQnAExperience>()
47+
48+
const containerId = computed(() => props.id || `v-quicksight-generative-qna-${nanoid(6)}`)
49+
const frameOptions = computed<FrameOptions>(() => {
50+
return {
51+
url: props.url,
52+
container: props.container || `#${containerId.value}`,
53+
height: props.height,
54+
width: props.width,
55+
className: props.className,
56+
withIframePlaceholder: props.withIframePlaceholder,
57+
onChange: (changeEvent, metadata) => {
58+
emit('change', { changeEvent, metadata })
59+
}
60+
}
61+
})
62+
const contentOptions = computed<GenerativeQnAContentOptions>(() => {
63+
return {
64+
showTopicName: props.showTopicName,
65+
showPinboard: props.showPinboard,
66+
allowTopicSelection: props.allowTopicSelection,
67+
allowFullscreen: props.allowFullscreen,
68+
searchPlaceholderText: props.searchPlaceholderText,
69+
panelOptions: props.panelOptions,
70+
themeOptions: props.themeOptions,
71+
onMessage: async (messageEvent, experienceMetadata) => {
72+
emit('message', { messageEvent, experienceMetadata })
73+
}
74+
}
75+
})
76+
77+
async function embedQnA(ctx: EmbeddingContext) {
78+
qnaFrame.value = await ctx.embedGenerativeQnA(frameOptions.value, contentOptions.value)
79+
}
80+
81+
async function setQuestion(frame: GenerativeQnAExperience, question?: string) {
82+
if (question) {
83+
return await frame.setQuestion(question)
84+
} else {
85+
return await frame.close()
86+
}
87+
}
88+
89+
watch(
90+
() => embeddingContext?.value,
91+
(newValue, oldValue) => {
92+
if (newValue && !oldValue) {
93+
embedQnA(newValue)
94+
}
95+
},
96+
{ immediate: true }
97+
)
98+
99+
watch(
100+
() => props.question,
101+
(newValue) => {
102+
if (qnaFrame.value) {
103+
setQuestion(qnaFrame.value, newValue)
104+
}
105+
},
106+
{ immediate: true }
107+
)
108+
</script>
109+
110+
<template>
111+
<div class="v-quicksight-generative-qna" :id="containerId"></div>
112+
</template>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { mount } from '@vue/test-utils'
2+
import { describe, expect, it, vi } from 'vitest'
3+
import { nextTick, ref } from 'vue'
4+
5+
import { EmbeddingContextInjectionKey } from '../../symbols'
6+
import VQuicksightGenerativeQnA from '../VQuicksightGenerativeQnA.vue'
7+
8+
const frameOptions = {
9+
container: '#custom-el',
10+
width: '300px',
11+
height: '700px',
12+
withIframePlaceholder: true,
13+
className: 'my-custom-class'
14+
}
15+
const contentOptions = {
16+
showTopicName: false,
17+
showPinboard: false,
18+
allowTopicSelection: false,
19+
allowFullscreen: false,
20+
searchPlaceholderText: ''
21+
}
22+
23+
describe('VQuicksightGenerativeQnA', () => {
24+
function setupComponent(props: Record<string, any> = {}) {
25+
const qnaFrame = {
26+
close: vi.fn().mockResolvedValue({ success: true }),
27+
setQuestion: vi.fn().mockResolvedValue({ success: true })
28+
}
29+
const embeddingContext = {
30+
embedGenerativeQnA: vi.fn().mockResolvedValue(qnaFrame)
31+
}
32+
33+
const component = mount(VQuicksightGenerativeQnA, {
34+
global: {
35+
provide: {
36+
[EmbeddingContextInjectionKey]: ref(embeddingContext)
37+
}
38+
},
39+
props: {
40+
...props,
41+
url: 'https://my-embed-url.example.org',
42+
id: 'v-quicksight-qna-1'
43+
}
44+
})
45+
return { component, qnaFrame, embeddingContext }
46+
}
47+
48+
it('renders as expected', () => {
49+
const { component } = setupComponent()
50+
expect(component.element).toMatchSnapshot()
51+
})
52+
53+
it('should call embedQSearchBar with default parameters', async () => {
54+
const { embeddingContext } = setupComponent()
55+
await nextTick()
56+
expect(embeddingContext.embedGenerativeQnA).toHaveBeenCalledOnce()
57+
expect(embeddingContext.embedGenerativeQnA).toHaveBeenCalledWith(
58+
{
59+
className: undefined,
60+
container: '#v-quicksight-qna-1',
61+
height: '100%',
62+
onChange: expect.anything(),
63+
url: 'https://my-embed-url.example.org',
64+
width: '100%',
65+
withIframePlaceholder: false
66+
},
67+
{
68+
showTopicName: true,
69+
showPinboard: true,
70+
allowTopicSelection: true,
71+
allowFullscreen: true,
72+
onMessage: expect.anything()
73+
}
74+
)
75+
})
76+
77+
it('should call embedQSearchBar with customized parameters', () => {
78+
const { embeddingContext } = setupComponent({ ...frameOptions, ...contentOptions })
79+
expect(embeddingContext.embedGenerativeQnA).toHaveBeenCalledOnce()
80+
expect(embeddingContext.embedGenerativeQnA).toHaveBeenCalledWith(
81+
{
82+
...frameOptions,
83+
onChange: expect.anything(),
84+
url: 'https://my-embed-url.example.org'
85+
},
86+
{
87+
...contentOptions,
88+
onMessage: expect.anything()
89+
}
90+
)
91+
})
92+
93+
it('should call setQuestion when the passed question name changes', async () => {
94+
const { qnaFrame, component } = setupComponent()
95+
await nextTick() // wait until first setup is finished
96+
await component.setProps({ url: '', question: 'How many apples were sold?' })
97+
expect(qnaFrame.setQuestion).toHaveBeenCalledOnce()
98+
expect(qnaFrame.setQuestion).toHaveBeenCalledWith('How many apples were sold?')
99+
})
100+
101+
it('should call close when the question changes to a nullish value', async () => {
102+
const { qnaFrame, component } = setupComponent({ question: 'How many apples were sold?' })
103+
await nextTick() // wait until first setup is finished
104+
await component.setProps({ url: '', question: '' })
105+
expect(qnaFrame.close).toHaveBeenCalled()
106+
})
107+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`VQuicksightGenerativeQnA > renders as expected 1`] = `
4+
<div
5+
class="v-quicksight-generative-qna"
6+
id="v-quicksight-qna-1"
7+
/>
8+
`;

src/components/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import VQuicksightDashboard from './VQuicksightDashboard.vue'
55
import VQuicksightVisual from './VQuicksightVisual.vue'
66
import VQuicksightConsole from './VQuicksightConsole.vue'
77
import VQuicksightSearch from './VQuicksightSearch.vue'
8+
import VQuicksightGenerativeQnA from '@/components/VQuicksightGenerativeQnA.vue'
89

910
function install(app: App) {
1011
app.component('v-quicksight', VQuicksight)
1112
app.component('v-quicksight-dashboard', VQuicksightDashboard)
1213
app.component('v-quicksight-visual', VQuicksightVisual)
1314
app.component('v-quicksight-console', VQuicksightConsole)
1415
app.component('v-quicksight-search', VQuicksightSearch)
16+
app.component('v-quicksight-generative-qna', VQuicksightGenerativeQnA)
1517
}
1618

1719
export default install
@@ -22,5 +24,6 @@ export {
2224
VQuicksightDashboard,
2325
VQuicksightVisual,
2426
VQuicksightConsole,
25-
VQuicksightSearch
27+
VQuicksightSearch,
28+
VQuicksightGenerativeQnA
2629
}

src/demos/GenerativeQnAOne.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
4+
const question = ref('How many apples were sold?')
5+
6+
const quickSightEmbedUrl = ref(
7+
'https://eu-central-1.quicksight.aws.amazon.com/embed/12345/dashboards/67890?code=...'
8+
)
9+
10+
function log($event: any) {
11+
console.log('change', $event)
12+
}
13+
</script>
14+
15+
<template>
16+
<v-quicksight>
17+
<v-quicksight-generative-qna
18+
class="report-dashboard"
19+
:url="quickSightEmbedUrl"
20+
:question="question"
21+
@change="log" />
22+
</v-quicksight>
23+
</template>

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type {
33
FrameOptions,
44
VisualContentOptions,
55
ConsoleContentOptions,
6-
QSearchContentOptions
6+
QSearchContentOptions,
7+
GenerativeQnAContentOptions
78
} from 'amazon-quicksight-embedding-sdk'
89
export type { ThemeConfiguration } from '@aws-sdk/client-quicksight/dist-types'
910

@@ -12,3 +13,4 @@ export type VQuicksightDashboardContentOptions = Omit<DashboardContentOptions, '
1213
export type VQuicksightVisualContentOptions = Omit<VisualContentOptions, 'onMessage'>
1314
export type VQuicksightConsoleContentOptions = Omit<ConsoleContentOptions, 'onMessage'>
1415
export type VQuicksightSearchContentOptions = Omit<QSearchContentOptions, 'onMessage'>
16+
export type VQuicksightGenerativeQnAContentOptions = Omit<GenerativeQnAContentOptions, 'onMessage'>

0 commit comments

Comments
 (0)