Skip to content

Commit

Permalink
Merge pull request #88 from unimal-jp/feature/add-ab-testing
Browse files Browse the repository at this point in the history
Add A/B testing feature
  • Loading branch information
akoarum authored Jul 19, 2023
2 parents d08280d + d26328b commit 4f82bd9
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 23 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export default {}
- `filter-mode` ('or' | 'and')
- `range-to` (string)
- `range-from` (string)
- `session-id` (string)
- `pattern-name` ('a' | 'b')
- `wrapper` (string | Vue)
- `item` (string | Vue)
- `loading` (Vue)
Expand Down Expand Up @@ -180,6 +182,10 @@ export default {}
</script>
```

#### props

- `pattern-name` ('a' | 'b')

#### use Show Loading

Specify a component name for `loading` prop.
Expand Down Expand Up @@ -289,3 +295,42 @@ Specify a component name for `loading` prop.
export default {}
</script>
```

### Instance Methods

The following two instance methods are provided.

- `$spearly` : API Client for get content list, form submission, etc.
- `$spearlyAnalytics` : Used to send pageView (impressions) and conversions required for A/B Testing

### A/B Testing analytics

> **Warning**
> A/B Testing does not support SSR and SSG, Only SPA can use this feature.
> We are working on it now, so please wait for a while.
#### Impression

If you are using the SpearlyContent component, you do not need to do anything special. The component will send the impression for you.

If you wish to send your own, you can do so with the following code:

```js
await this.$spearlyAnalytics.pageView({
contentId: CONTENT_ID,
patternName: 'a' or 'b',
})
```

#### Conversion

If you are using A/B testing, you can count conversions by using the conversion method as follows

```js
const handleSubmit = async () => {
await this.$spearlyAnalytics.conversion({
contentId: CONTENT_ID,
patternName: 'a' or 'b',
})
}
```
60 changes: 45 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
},
"dependencies": {
"@nuxt/types": "^2.15.7",
"@spearly/sdk-js": "^1.1.1",
"@spearly/sdk-js": "^1.3.0",
"@types/node-fetch": "^2.5.12",
"@vue/compiler-sfc": "^3.2.2",
"node-fetch": "^2.6.1",
Expand Down
20 changes: 17 additions & 3 deletions src/components/spearly-content-list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<component :is="loading" />
</template>
<component :is="wrapper" v-else>
<component :is="item" v-for="content in contents" :key="content.publicUid">
<component :is="item" v-for="content in contents" :key="content.attributes.publicUid">
<slot :content="content" />
</component>
</component>
Expand All @@ -25,7 +25,7 @@ export type Props = {
wrapper?: string
item?: string
loading?: string
} & Omit<GetParams, 'order' | 'orderBy' | 'orderDirection'>
} & Omit<GetParams, 'order' | 'orderBy' | 'orderDirection' | 'distinctId'>
export type Data = {
contents: Content[]
Expand All @@ -35,7 +35,17 @@ export type Data = {
totalContentsCount: number
}
export default Vue.extend<Data, unknown, unknown, Props>({
export type Computed = {
paging: {
limit?: number
offset?: number
next: number
matchingContentsCount: number
totalContentsCount: number
}
}
export default Vue.extend<Data, Computed, unknown, Props>({
props: {
id: { type: String, required: true },
limit: { type: Number },
Expand All @@ -50,6 +60,8 @@ export default Vue.extend<Data, unknown, unknown, Props>({
filters: { type: Object as PropType<GetParams['filters']> },
rangeFrom: { type: Date },
rangeTo: { type: Date },
sessionId: { type: String },
patternName: { type: String as PropType<'a' | 'b'> },
wrapper: { type: [String], default: 'div' },
item: { type: [String], default: 'div' },
loading: { type: [String] },
Expand Down Expand Up @@ -77,6 +89,8 @@ export default Vue.extend<Data, unknown, unknown, Props>({
if (this.filters) params.filters = this.filters
if (this.rangeFrom) params.rangeFrom = this.rangeFrom
if (this.rangeTo) params.rangeTo = this.rangeTo
if (this.sessionId) params.sessionId = this.sessionId
if (this.patternName) params.patternName = this.patternName
const res = await this.$spearly.getList(this.id, Object.keys(params).length ? params : undefined)
this.contents = res.data
this.isLoaded = true
Expand Down
21 changes: 19 additions & 2 deletions src/components/spearly-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@

<script lang="ts">
import Vue from 'vue'
import { Content } from '@spearly/sdk-js'
import type { PropType } from 'vue'
import type { Content, GetContentParams } from '@spearly/sdk-js'
export type Props = {
id: string
previewToken?: string
loading?: string
patternName?: 'a' | 'b'
}
export type Data = {
Expand All @@ -35,12 +37,14 @@ export default Vue.extend<Data, unknown, unknown, Props>({
id: { type: String, required: true },
loading: { type: String },
previewToken: { type: String },
patternName: { type: String as PropType<'a' | 'b'> },
},
data() {
return {
content: {
attributes: {
publicUid: '',
patternName: 'a',
createdAt: null,
updatedAt: null,
publishedAt: null,
Expand All @@ -60,7 +64,12 @@ export default Vue.extend<Data, unknown, unknown, Props>({
},
async fetch() {
if (!this.$props.previewToken) {
const res = await this.$spearly.getContent(this.$props.id)
const params: GetContentParams = {}
if (this.$props.patternName) {
params.patternName = this.$props.patternName
}
const res = await this.$spearly.getContent(this.$props.id, params)
this.content = res
this.isLoaded = true
} else {
Expand All @@ -69,6 +78,14 @@ export default Vue.extend<Data, unknown, unknown, Props>({
this.isLoaded = true
}
},
mounted() {
if (!this.previewToken) {
this.$spearlyAnalytics.pageView({
contentId: this.id,
patternName: this.content.attributes.patternName,
})
}
},
destroyed() {
this.isLoaded = false
},
Expand Down
4 changes: 3 additions & 1 deletion src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Vue from 'vue'
import { Plugin } from '@nuxt/types'
import { SpearlyContentList, SpearlyContent, SpearlyForm } from './components'
import { SpearlyApiClient, Content, Form, GetParams } from '@spearly/sdk-js'
import { SpearlyApiClient, SpearlyAnalytics, Content, Form, GetParams } from '@spearly/sdk-js'

export type ListData = {
contents: Content[]
Expand Down Expand Up @@ -31,6 +31,7 @@ export type ListProps = { order?: string; orderBy?: string } & Omit<GetParams, '
const plugin: Plugin = function (_, inject) {
const opt = JSON.parse('<%= serialize(options) %>')
const apiClient = new SpearlyApiClient(opt.options.apiKey)
const analytics = new SpearlyAnalytics()

Vue.component<ListData, { $spearly: SpearlyApiClient }, unknown, ListProps>(
// eslint-disable-next-line vue/component-definition-name-casing
Expand All @@ -56,6 +57,7 @@ const plugin: Plugin = function (_, inject) {
)

inject('spearly', apiClient)
inject('spearlyAnalytics', analytics)
}

export default plugin
1 change: 1 addition & 0 deletions src/specs/components/_mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const createContentMock = (index = 0): Content => ({
nextContent: null,
previousContent: null,
contentAlias: `content_${index}`,
patternName: 'b',
fields: {
data: [
{
Expand Down
16 changes: 16 additions & 0 deletions src/specs/components/spearly-content.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import SpearlyContent from '../../components/spearly-content.vue'

describe('SpearlyContent', () => {
let wrapper: Wrapper<SpearlyContent>
let pageViewMock: jest.Mock

beforeEach(() => {
pageViewMock = jest.fn()
wrapper = shallowMount(SpearlyContent, {
propsData: {
id: 'CONTENT_ID',
},
mocks: {
$spearlyAnalytics: {
pageView: pageViewMock,
},
},
})
})

Expand All @@ -17,4 +24,13 @@ describe('SpearlyContent', () => {
expect(wrapper.element).toMatchSnapshot()
})
})

describe('analytics', () => {
it('send a pageView', () => {
expect(pageViewMock).toHaveBeenCalledWith({
contentId: 'CONTENT_ID',
patternName: 'a',
})
})
})
})
Loading

0 comments on commit 4f82bd9

Please sign in to comment.