app/vidispine/search/VidispineSearch.ts (207 lines of code) (raw):

import { VidispineFieldGroup } from "../field-group/VidispineFieldGroup"; interface SearchValueIF { value: string; noescape?: boolean; minimum?: number; maximum?: number; boost?: number; //a float value } interface SearchFieldIF { name: string; value: SearchValueIF[]; } interface SearchFieldGroupIF { name: string; field: SearchFieldIF[]; } interface SearchOperatorIF { operation: string; field?: SearchFieldIF[]; group?: SearchFieldGroupIF[]; operator?: SearchOperatorIF[]; } enum SearchOrderValue { ascending = "ascending", descending = "descending", } interface SearchOrderIF { field: string; order: SearchOrderValue; } interface SearchRange { start: string; end: string; } interface SearchFacet { count: boolean; //if true, return the raw counts of items for each field value. If false, use the ranges to group them. field: string; range?: SearchRange[]; //if count is false, range should be specified } interface VidispineSearchDocIF { operator?: SearchOperatorIF; field?: SearchFieldIF[]; group?: SearchFieldGroupIF[]; facet?: SearchFacet[]; sort?: SearchOrderIF[]; } class VidispineSearchDoc implements VidispineSearchDocIF { operator?: SearchOperatorIF; field?: SearchFieldIF[]; group?: SearchFieldGroupIF[]; sort?: SearchOrderIF[]; facet?: SearchFacet[]; /** * construct a new SearchDoc, optionally with an "operation" (and/or/not) at the base and a set of groups and fields within * those groups to search for * @param baseOperation * @param withGroups */ constructor( baseOperation?: string, withFields?: Map<string, string[]>, withGroups?: Map<string, Map<string, string[]>> ) { let groupElList: SearchFieldGroupIF[] | undefined; let fieldElList: SearchFieldIF[] | undefined; if (withGroups) { groupElList = Array.from(withGroups, (elem) => ({ name: elem[0], field: Array.from(elem[1], (fieldData) => ({ name: fieldData[0], value: fieldData[1].map((stringVal) => ({ value: stringVal, })), })), })); } if (withFields) { fieldElList = Array.from(withFields, (elem) => ({ name: elem[0], value: elem[1].map((stringVal) => ({ value: stringVal })), })); } if (baseOperation) { this.operator = { operation: baseOperation, field: fieldElList ?? [], group: groupElList ?? [], }; } else { this.group = groupElList ?? []; this.field = fieldElList ?? []; } } /** * see if we already have a matching field group definition for the given name and * return it if so. Otherwise returns undefined * @param groupName */ findMatchingGroup(groupName: string): SearchFieldGroupIF | undefined { if (this.operator) { const potentials = this.operator.group ? this.operator.group.filter((grp) => grp.name === groupName) : []; return potentials.length > 0 ? potentials[0] : undefined; } else { const potentials = this.group ? this.group.filter((grp) => grp.name === groupName) : []; return potentials.length > 0 ? potentials[0] : undefined; } } /** * finds the index in our group list of the given group, or alternatively creates a blank entry and returns it * @param groupName group name to find or create * @returns an array of the group reference [0] and the index [1] */ findOrMakeMatchingGroupIndex( groupName: string ): [SearchFieldGroupIF, number] | undefined { let searchList: SearchFieldGroupIF[] | undefined; if (this.operator && this.operator.group) { searchList = this.operator.group; } else if (this.group) { searchList = this.group; } if (searchList) { for (let i = 0; i < searchList.length; ++i) { if (searchList[i].name === groupName) return [searchList[i], i]; } } const willInsertIndex = this.group ? this.group.length : 0; this.group = this.group ? this.group.concat({ name: groupName, field: [] }) : [{ name: groupName, field: [] }]; return [this.group[willInsertIndex], willInsertIndex]; } /** * adds a facet term to this search * @param field field to facet on * @param count whether to return raw counts or use range buckets * @param ranges if count=false, the range buckets to use * @return this object, for chaining */ addFacet(field: string, count: boolean, ranges?: SearchRange[]) { const newEl: SearchFacet = { field: field, count: count, range: ranges, }; this.facet = this.facet ? this.facet.concat(newEl) : [newEl]; return this; } /** * returns a new VidispineSearchDoc with the given facet added * @param field field to facet on * @param count whether to return raw counts or use range buckets * @param ranges if count=false, the range buckets to use * @return new object with the contents of this one plus the new facet */ withFacet(field: string, count: boolean, ranges?: SearchRange[]) { const newEl: SearchFacet = { field: field, count: count, range: ranges, }; let newObject = Object.assign(new VidispineSearchDoc(), this); newObject.facet = newObject.facet ? newObject.facet.concat(newEl) : [newEl]; return newObject; } /** * returns a new VidispineSearchDoc with the given term added * @param fieldName field name to search for * @param values values to search for. These are combined with a logical OR * @param toGroup add to the given group, optional. * @return the VidispineSearchDoc, for chaining */ withSearchTerm(fieldName: string, values: string[], toGroup?: string) { const newEl: SearchFieldIF = { name: fieldName, value: values.map((stringval) => ({ value: stringval })), }; let newObject = Object.assign(new VidispineSearchDoc(), this); let groupMatch; if (toGroup) { groupMatch = newObject.findOrMakeMatchingGroupIndex(toGroup); console.log("groupMatch", groupMatch); } //TODO: this needs a load of optimising, if we decide to keep it (not currently used) if (groupMatch) { const updatedGroupContent = groupMatch[0].field.concat(newEl); console.log("updatedGroupContent", updatedGroupContent); if (newObject.operator && newObject.operator.group) { newObject.operator.group[groupMatch[1]].field = updatedGroupContent; } else if (newObject.group) { console.log("adding to ", newObject.group[groupMatch[1]].field); newObject.group[groupMatch[1]].field = updatedGroupContent; } else { console.error( "Unexpected problem setting withSearchTerm, this indicates a code bug" ); return this; } } else { if (newObject.operator) { newObject.operator.field = newObject.operator.field ? newObject.operator.field.concat(newEl) : [newEl]; } else { newObject.field = newObject.field ? newObject.field.concat(newEl) : [newEl]; } } return newObject; } /** * returns the values for the field in the given group. Implemented like this for direct * compatibility with VidispineItem in MetadataGroupView * @param fieldName * @param groupName */ getMetadataValuesInGroup( fieldName: string, groupName: string ): string[] | undefined { const group = this.findMatchingGroup(groupName); if (group) { const fieldMatch = group.field.filter( (field) => field.name === fieldName ); const allValues = fieldMatch.map((field) => field.value.map((value) => value.value) ); return ([] as string[]).concat(...allValues); } else { return undefined; } } /** * sets the ordering parameter in the document. Searches are un-ordered initially. * @param fieldName * @param direction * @return the VidispineSearchDoc, for chaining */ setOrdering(fieldName: string, direction: SearchOrderValue) { this.sort = [ { field: fieldName, order: direction, }, ]; return this; } } export type { VidispineSearchDocIF }; export { SearchOrderValue }; export default VidispineSearchDoc;