UNPKG

@openfin/search-api

Version:

A search API framework for OpenFin.

278 lines (214 loc) 9.48 kB
# Workspace Search The Search Extension is a framework for searching, fetching, aggregating and actioning data through application interopability. ## Getting Started There are two options for using this package. Import functions from NPM package and bundle: ```js import { create, subscribe } from '@openfin/search-api'; ``` Or import the script from our CDN and use the `fin.Search` global namespace. ```html <script src="https://cdn.openfin.co/search-api/index.js" /> <script> const create = fin.Search.create; const subscribe = fin.Search.subscribe; </script> ``` ## API Reference More in depth API documentation can be found [here](https://cdn.openfin.co/search-api/docs/index.html). ## Examples ### Searching for Data ```js // It is recommended to bundle and import search API functions. // However, the global `fin.Search` namespace can be used as an alternative. import { subscribe } from '@openfin/search-api'; // Subscribe to a search topic. const searchTopic = await subscribe({ topic: 'foo' }); /* Search for data with query `bar`. This will return a generator which can be called multiple times while there are pending responses from search data providers. */ const generator = searchTopic.search({ query: 'bar' }); while (true) { const { value, done } = await generator.next(); // Implementation specific rendering logic. render(results); // If done is true, all search providers have responded with their data. if (done) { break; } } // When done with search request, call close. generator.close(); ``` ### Registering a Search Provider ```js // It is recommended to bundle and import search API functions. // However, the global `fin.Search` namespace can be used as an alternative. import { subscribe } from '@openfin/search-api'; const openWindowAction = 'Open Window'; // Example search handler that returns results from a backend query. async function getSearchResults({ query }) { const res = await fetch('/search?q=' + encodeURIComponent(query)); if (!res.ok) { throw new Error('uh oh something went wrong'); } const json = await res.json(); /* Return initial search results. All results must have the `name` and `description` field at the very least for rendering purposes. */ return { results: json.map((myResult) => ({ name: myResult.nameAttribute, shortDescription: myResult.shortDescriptionAttribute, description: myResult.descriptionAttribute, actions: [openWindowAction], // Dispatchable actions for this search result. data: myResult.customMetadata })) }; } // Example function for actioning a result from `getSearchResults`. function openWindowForSearchResult(result) { const dispatchedAction = result.action; // `result.action` is set to the action that was dispatched. if (dispatchedAction !== openWindowAction) return; window.open(result.data.url); } // Subscribe to a search topic. const searchTopic = await subscribe({ topic: 'foo' }); /* The `name` and `onUserInput` attributes are required for a data provider. The `onUserInput` function will be called back when the topic is searched on. (ex. `searchTopic.search("my query")`) The `onResultDispatch` function will be called back when a search result is dispatched. (ex. `searchTopic.dispatch("Provider Name", searchResult, "Open Window")`) */ const provider = { name: 'bar', onUserInput: getSearchResults, onResultDispatch: openWindowForSearchResult }; // Register the search data provider. await searchTopic.register(provider); ``` ### Actioning Search Results ```js // It is recommended to bundle and import search API functions. // However, the global `fin.Search` namespace can be used as an alternative. import { subscribe } from '@openfin/search-api'; // Subscribe to a search topic. const searchTopic = await subscribe({ topic: 'foo' }); // Get search results. const generator = searchTopic.search({ query: 'bar' }); const { value } = await generator.next(); /* Dispatches the first search result in the first response back to the respective provider, such that the provider can action the result. */ const firstSearchProviderResponse = value[0]; const firstSearchProviderName = firstSearchProviderResponse.provider.name; const firstSearchResult = firstSearchProviderResponse.results[0]; const firstSearchAction = firstSearchResult.actions[0]; await searchTopic.dispatch(firstSearchProviderName, firstSearchResult, firstSearchAction); // Omitting will default to first action in `result.actions`. ``` ### Control Search Topic Subscriptions ```js // It is recommended to bundle and import search API functions. // However, the global `fin.Search` namespace can be used as an alternative. import { create } from "@openfin/search-api"; const searchTopic = await create({ topic: "foo" }); // Only hosts in the list can subscribe to the search topic. const allowedHosts = ["www.vendor.com"]; searchTopic.onSubscription(identity => { // Get the URL of the subscribing identity. const info = await fin.View.wrapSync(identity).getInfo(); const url = new URL(info.url); return allowedHosts.includes(url.host); }); ``` ### List Search Providers ```js // It is recommended to bundle and import search API functions. // However, the global `fin.Search` namespace can be used as an alternative. import { create } from '@openfin/search-api'; // Subscribe or create a search topic. const searchTopic = await create({ topic: 'foo' }); // Returns a list of provider info objects. const info = await searchTopic.getAllProviderInfo(); for (let provider of info) { console.log(`Provider Name: ${provider.name}, Openfin Identity: ${provider.identity}`); } ``` ### Searching Specific Providers ```js // It is recommended to bundle and import search API functions. // However, the global `fin.Search` namespace can be used as an alternative. import { create } from '@openfin/search-api'; // Subscribe or create a search topic. const searchTopic = await create({ topic: 'foo' }); // Only searches against providers in the provider name list. searchTopic.searchProviders(['Provider Name 1', 'Provider Name 2'], 'bar'); ``` ### Pushing Search Results For simple use cases, the Search Extension allows search providers to register an async handler function that returns a set of search results. Internally the Search Extension uses a pull architecture, allowing the search requester to pull in an initial set of results from all search providers listening in on a search topic. ```js // A simple search handler that returns an initial set of search results. async function getSearchResults({ query }) { const res = await fetch('/search?q=' + encodeURIComponent(query)); if (!res.ok) { throw new Error('uh oh something went wrong'); } const json = await res.json(); // These results will be pulled in by the search requester. return json.map((myResult) => ({ name: myResult.nameAttribute, shortDescription: myResult.shortDescriptionAttribute, description: myResult.descriptionAttribute, actions: [openWindowAction], data: myResult.customMetadata })); } ``` For a more complex use case, like a long running query, it might be desired to push new or updated search results to the search requester after a long period of time. For said use case, you can use the search listener response object as highlighted in the example below. ```js /* An advanced search handler that pushes new or updated search results as long as the search request has not been closed. */ async function getSearchResults(request, response) { /* ID of the search request. Can be used to tie related search providers together. */ const id = request.id; // The search query. const query = request.query; /* ▼ PUSH ARCHITECTURE ▼ */ /* Open the response stream, notifying the search requester that there are new or updated search results that have yet to be pushed by the current provider. */ response.open(); const myLongRunningQuery = makeMyLongRunningQuery(query); // On new or updated search results push them to the search requester. const onNewResults = (myResults) => { // Map the new results. const newResults = myResults.map((myResult) => ({ key: myResult.keyAttribute, name: myResult.nameAttribute, shortDescription: myResult.shortDescriptionAttribute, description: myResult.descriptionAttribute, actions: [openWindowAction], data: myResult.customMetadata })); /* Push the new results to the search requester. If the `key` attribute matches a previously pushed search result, the old result will be updated with the new result's content. */ response.respond(newResults); }; myLongRunningQuery.onNewResults(onNewResults); /* Remove the listener and close the long running query if the request has been closed by the search requester. */ request.onClose(() => { myLongRunningQuery.close(); }); /** * Upon query completion, close the response. This notifies * the requester that the current search provider is done sending results. */ myLongRunningQuery.onQueryDone(() => { response.close(); }); } ```