@openfin/search-api
v2.0.1
Published
A search API framework for OpenFin.
Downloads
1,318
Maintainers
Readme
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:
import { create, subscribe } from '@openfin/search-api';
Or import the script from our CDN and use the fin.Search
global namespace.
<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.
Examples
Searching for Data
// 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
// 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
// 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
// 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
// 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
// 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.
// 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.
/* 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();
});
}