vue-instantsearch icon indicating copy to clipboard operation
vue-instantsearch copied to clipboard

ais-instant-search doesn't support insightsMiddleware?

Open profhouse opened this issue 3 years ago • 20 comments

Bug 🐞

What is the current behavior?

ais-instant-search doesn't support insightsMiddleware

https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/#connect-instantsearch-with-the-insights-middleware

What is the expected behavior?

Support middlewares.

Does this happen only in specific situations?

What is the proposed solution?

Create a new props: insightsMiddleware

What is the version you are using?

3.4.2

profhouse avatar Dec 01 '20 12:12 profhouse

That's right, today we haven't yet made an api in Vue InstantSearch for making this happen, however what you can do is the following for now: https://codesandbox.io/s/unruffled-solomon-ycuxr?file=/src/Insights.js

  1. make a custom widget using createWidgetMixin
  2. use this.instantSearchInstance for adding the middleware

Haroenv avatar Dec 01 '20 15:12 Haroenv

@profhouse Did you end up getting this working? The sandbox link I couldn't get running successfully with insights.

jonathan-bird avatar Feb 10 '21 08:02 jonathan-bird

@jonathan-bird One detail that I forgot to put in that sandbox is loading the insights client, I've now done that and it works for me (I see insights events): https://codesandbox.io/s/unruffled-solomon-ycuxr?file=/public/index.html

I've added this script to the public html before the vue scripts are loaded:

    <script>
      var ALGOLIA_INSIGHTS_SRC =
        'https://cdn.jsdelivr.net/npm/[email protected]';

      !(function (e, a, t, n, s, i, c) {
        (e.AlgoliaAnalyticsObject = s),
          (e[s] =
            e[s] ||
            function () {
              (e[s].queue = e[s].queue || []).push(arguments);
            }),
          (i = a.createElement(t)),
          (c = a.getElementsByTagName(t)[0]),
          (i.async = 1),
          (i.src = n),
          c.parentNode.insertBefore(i, c);
      })(window, document, 'script', ALGOLIA_INSIGHTS_SRC, 'aa');
    </script>

Haroenv avatar Feb 10 '21 09:02 Haroenv

That sandbox was great help thanks. I was just having a play with your sandbox and copied a lot, including api ID / key and still the same issue.

I decided to try the CURL command manually and got the response: {"status":422,"message":"Cannot process more than 20 objects in an event"}

I finally got a 200 response when I changed "perPage" to 20 from my default 40. Hmmm...

I think I'm on to something here! Although it's an automated insight "view" event type API call so I'm not sure how I can do both. I would've thought it'd just split automatically since it's not manually sent.

jonathan-bird avatar Feb 10 '21 13:02 jonathan-bird

I ended up getting it completely working now with my per page = 40. I just catch the event and modify the payload to split into chunks of 20.

import { createWidgetMixin } from 'vue-instantsearch';
import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';
import aa from 'search-insights';

aa('init', {
    appId: 'xxx',
    apiKey: 'xxx',
    userToken: window.gaDefaultConfig.user_id.toString()
});

function splitArrayByCount(array, chunk = 20) {
    let i,
        j,
        tempArray = [];

    for (i = 0,j = array.length; i < j; i+=chunk) {
        tempArray.push(array.slice(i, i + chunk));
    }

    return tempArray;
}

export default {
    mixins: [
        createWidgetMixin({ connector: true })
    ],
    created() {
        const insightsMiddleware = createInsightsMiddleware({
            insightsClient: aa,
            onEvent: (event, aa) => {
                const { insightsMethod, payload, widgetType, eventType } = event;
                const newObjectIDPayload = splitArrayByCount(payload.objectIDs);
                
                // Send the event to Algolia in chunks of 20
                for (var i = 0; i < newObjectIDPayload.length; i++) {
                    let tempPayLoad = payload;
                    tempPayLoad.objectIDs = newObjectIDPayload[i];

                    aa(insightsMethod, tempPayLoad);
                }
            }
        });

        this.instantSearchInstance.use(insightsMiddleware);
    },
    render() {
        return null;
    },
};

jonathan-bird avatar Feb 11 '21 00:02 jonathan-bird

@Haroenv are you able to add a click event into your demo? I keep finding that I get the error saying that it should be added "insightsClient" but as I recall this is the old way of doing things and now you use middleware which is what we've done.

Error in v-on handler: "Error: The `insightsClient` option has not been provided to `instantsearch`.

And that's with:

@click="insights('clickedObjectIDsAfterSearch', {
  eventName: 'Add to cart',
  objectIDs: [item.objectID]
})"

I can pass the window.aa to the <ais-instant-search> and it works to send them, but it just says this approach is deprecated.

jonathan-bird avatar Feb 11 '21 01:02 jonathan-bird

Thanks for your workaround with splitting the events, I didn't know the insights api added that restriction.

For your last question, I think this is because the approach hasn't yet been updated in the documentation, sorry for that! It's meant to be using the sendEvent method, not the insights method. Unfortunately Vue InstantSearch hasn't been updated to take advantage of this. For the time being, a custom widget like this is the best approach before we make the sendEvent changes here:

<template>
  <div
    v-if="state"
    :class="suit()"
  >
    <slot
      :items="items"
      :send-event="state.sendEvent"
    >
      <ol :class="suit('list')">
        <li
          v-for="(item, itemIndex) in items"
          :key="item.objectID"
          :class="suit('item')"
        >
          <slot
            name="item"
            :item="item"
            :index="itemIndex"
            :send-event="state.sendEvent"
          >objectID: {{ item.objectID }}, index: {{ itemIndex }}</slot>
        </li>
      </ol>
    </slot>
  </div>
</template>

<script>
import { connectHitsWithInsights } from 'instantsearch.js/es/connectors';
import { createWidgetMixin, createSuitMixin } from 'vue-instantsearch';

export default {
  name: 'AisHits',
  mixins: [
    createWidgetMixin({ connector: connectHitsWithInsights }),
    createSuitMixin({ name: 'Hits' }),
  ],
  props: {
    escapeHTML: {
      type: Boolean,
      default: true,
    },
    transformItems: {
      type: Function,
      default(items) {
        return items;
      },
    },
  },
  computed: {
    items() {
      return this.state.hits;
    },
    widgetParams() {
      return {
        escapeHTML: this.escapeHTML,
        transformItems: this.transformItems,
      };
    },
  },
};
</script>

and then use sendEvent in the click handler sendEvent('click'/'convert', [item], "Add to cart")

Thanks again for digging here and noticing some flaws @jonathan-bird, we will improve this experience soon

Haroenv avatar Feb 11 '21 09:02 Haroenv

Thanks heaps @Haroenv, I'll give that a go.

I ended up getting a workaround using a click event and then using the instance, so it ended up like this:

window.instantSearchInstance.sendEventToInsights({
    eventType: 'conversion',
    insightsMethod: 'convertedObjectIDs',
    payload: {
        eventName: 'Add to cart',
        index: 'sync_stock_products',
        objectIDs: [productItem.dataset.objectId],
        queryID: productItem.dataset.queryId
    }
});

Where all of the productItem is just a data attribute on the element and I just pull it in for the click event.

Your approach is obviously the proper solution so I'll give that a go next week. I've at least got it live and collecting data now, so I'm very excited to start testing the personalisation, reordering & rules.

jonathan-bird avatar Feb 11 '21 10:02 jonathan-bird

@Haroenv Do you know if the insights middleware does anything to the query params upon first page load?

I've had everything working for a few days well but noticed that when this.instantSearchInstance.use(insightsMiddleware); is set, my ?page=X query param gets removed. When I comment that out, it works again. Other query params (like query, brand etc all work). I use this method for state & parsing/creating URL which has been working for a year now perfectly until insights got added and can get it working but only when commenting that use() line: https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/vue/

Interesting that it looks to affect this method:

stateToRoute(uiState) {
    const indexUiState = uiState[indexName] || {};

    return {
        query: indexUiState.query,
        page: indexUiState.page,
        brands: indexUiState.refinementList && indexUiState.refinementList.brand,
        hierarchicalMenu: indexUiState.hierarchicalMenu,
        range: indexUiState.range
    };
},

It seems to parse from the parseURL method correctly, but then when it comes through to this stateToRoute, there is no page params. Very strange, which is why I wonder if the middleware is removing it.

I can replicate the issue by adding (I've set insightsClient to null for the sake of this to show it's not the insightsClient):

export default {
    mixins: [
        createWidgetMixin({ connector: true })
    ],
    created() {
        const insightsMiddleware = createInsightsMiddleware({
            insightsClient: null
        });

        this.instantSearchInstance.use(insightsMiddleware);
    },
    render() {
        return null;
    },
};

The behaviour doesn't seem to happen when I don't set the insightsClient, but does when I set it to null or to the window.aa, or if I just comment out the instance using the middleware:

export default {
    mixins: [
        createWidgetMixin({ connector: true })
    ],
    created() {
        const insightsMiddleware = createInsightsMiddleware();

        // this.instantSearchInstance.use(insightsMiddleware);
    },
    render() {
        return null;
    },
};

jonathan-bird avatar Feb 17 '21 07:02 jonathan-bird

This is very odd, and not something I have seen before @jonathan-bird, do you have this behaviour too when you create an isolated sandbox or repository?

Haroenv avatar Feb 17 '21 09:02 Haroenv

Hmm I don’t know how I can create it in a sandbox since it’s affected by the URL so I don’t know if It’ll work that way. Do you know if there is a way to set query Params in the sandbox?

I can share my code file with you privately though if you’d like.

jonathan-bird avatar Feb 17 '21 09:02 jonathan-bird

the url can be set by putting a link in the body and clicking on it, or via the url bar in the sandbox

a starter is here: https://codesandbox.io/s/github/algolia/create-instantsearch-app/tree/templates/vue-instantsearch

otherwise privately is also fine, as long as it's a minimal and easy to set up repository

Haroenv avatar Feb 17 '21 10:02 Haroenv

@Haroenv just figured it out! Give me 10 mins and I'll have a sandbox setup for you, thanks!

jonathan-bird avatar Feb 17 '21 10:02 jonathan-bird

@Haroenv https://codesandbox.io/s/vue-instantsearch-broken-page-query-param-5z5qg?file=/src/Insights.js

To replicate, add "?page=5" to the URL, you will see it removes it.

Go to insights.js, comment out the this.instantSearchInstance.use(insightsMiddleware);, save, then add it again and you will see ?page=5 stays in the URL.

jonathan-bird avatar Feb 17 '21 10:02 jonathan-bird

Let me know if you have any workarounds or find the cause of it, I may need to turn insights off tomorrow if I can’t find a way to make It play nice together as this will ruin our site being crawled as we have 15k products so Google usually crawls a few thousand pages a day, can’t have any weird redirects.

Thanks for your help!

jonathan-bird avatar Feb 17 '21 11:02 jonathan-bird

I haven't yet found a reason unfortunately, but I'm looking into it @jonathan-bird

Haroenv avatar Feb 17 '21 11:02 Haroenv

I have found the source of the bug @jonathan-bird, making a PR now

Haroenv avatar Feb 17 '21 12:02 Haroenv

I've proposed a fix in https://github.com/algolia/instantsearch.js/pull/4655 @jonathan-bird, for the mean time you can use patch-package to apply my fix

Haroenv avatar Feb 17 '21 13:02 Haroenv

if you use the latest version of InstantSearch.js (4.14.2) (yarn upgrade will give it) this bug is fixed, thanks for noticing it @jonathan-bird !

Haroenv avatar Feb 17 '21 14:02 Haroenv

@Haroenv You are an absolute lifesaver!! I'll send you a virtual bottle of wine from Australia :D

jonathan-bird avatar Feb 17 '21 23:02 jonathan-bird