Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion data-access/package-lock.json

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

1 change: 1 addition & 0 deletions data-access/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@apollo/server": "^4.6.0",
"@apollo/server-plugin-response-cache": "^4.1.2",
"@apollo/utils.keyvaluecache": "^3.0.0",
"@as-integrations/azure-functions": "^0.2.0",
"@azure/arm-maps": "^3.1.0-beta.1",
"@azure/cognitiveservices-contentmoderator": "^5.0.1",
"@azure/functions": "^4.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ beforeAll(() => {
let cognitiveSearch;

beforeEach(() => {
const searchKey = process.env.SEARCH_API_KEY;
const endpoint = process.env.SEARCH_API_ENDPOINT;
cognitiveSearch = new AzCognitiveSearch(searchKey, endpoint);
cognitiveSearch = new AzCognitiveSearch(endpoint);
});

test.skip('Initialize cognitive search object', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { DefaultAzureCredential, DefaultAzureCredentialOptions, TokenCredential } from "@azure/identity";
import { SearchIndexClient, SearchClient, SearchIndex, SearchDocumentsResult, AzureKeyCredential } from "@azure/search-documents";
import { CognitiveSearchBase } from "../services-seedwork-cognitive-search-interfaces";
import { DefaultAzureCredential, DefaultAzureCredentialOptions, TokenCredential } from '@azure/identity';
import { SearchIndexClient, SearchClient, SearchIndex, SearchDocumentsResult, AzureKeyCredential } from '@azure/search-documents';
import { CognitiveSearchBase } from '../services-seedwork-cognitive-search-interfaces';

export class AzCognitiveSearch implements CognitiveSearchBase {
private client: SearchIndexClient;
private searchClients: Map<string, SearchClient<unknown>> = new Map<string, SearchClient<unknown>>();

tryGetEnvVar(envVar: string): string {
const value = process.env[envVar];
Expand All @@ -13,9 +14,9 @@ export class AzCognitiveSearch implements CognitiveSearchBase {
return value;
}

constructor(searchKey: string, endpoint: string) {
constructor(endpoint: string) {
let credentials: TokenCredential;
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
credentials = new DefaultAzureCredential();
} else if (process.env.MANAGED_IDENTITY_CLIENT_ID !== undefined) {
credentials = new DefaultAzureCredential({ ManangedIdentityClientId: process.env.MANAGED_IDENTITY_CLIENT_ID } as DefaultAzureCredentialOptions);
Expand All @@ -25,30 +26,39 @@ export class AzCognitiveSearch implements CognitiveSearchBase {
this.client = new SearchIndexClient(endpoint, credentials);
}

private getSearchClient(indexName: string): SearchClient<unknown> {
let client = this.searchClients.get(indexName);
if (!client) {
client = this.client.getSearchClient(indexName);
this.searchClients.set(indexName, client);
}
return client;
}

// check if index exists
async indexExists(indexName: string): Promise<boolean> {
const indexes = await this.client.listIndexesNames();
for await (const name of indexes) {
if (name === indexName) {
return true;
}
}
return false;
return this.searchClients.has(indexName);
}

async createIndexIfNotExists(indexName: string, indexDefinition: SearchIndex): Promise<void> {
const indexExists = await this.indexExists(indexName);
async createIndexIfNotExists(indexDefinition: SearchIndex): Promise<void> {
const indexExists = this.indexExists(indexDefinition.name);
if (!indexExists) {
await this.client.createIndex(indexDefinition);
console.log(`Index ${indexName} created`);
try {
await this.client.createIndex(indexDefinition);
this.searchClients.set(indexDefinition.name, this.client.getSearchClient(indexDefinition.name));
console.log(`Index ${indexDefinition.name} created`);
} catch (error) {
throw new Error(`Failed to create index ${indexDefinition.name}: ${error.message}`);
}
}
}

async createOrUpdateIndexDefinition(indexName: string, indexDefinition: SearchIndex): Promise<void> {
try {
const indexExists = await this.indexExists(indexName);
const indexExists = this.indexExists(indexName);
if (!indexExists) {
await this.client.createIndex(indexDefinition);
this.searchClients.set(indexDefinition.name, this.client.getSearchClient(indexDefinition.name));
} else {
await this.client.createOrUpdateIndex(indexDefinition);
console.log(`Index ${indexName} updated`);
Expand All @@ -59,23 +69,36 @@ export class AzCognitiveSearch implements CognitiveSearchBase {
}

async search(indexName: string, searchText: string, options?: any): Promise<SearchDocumentsResult<Pick<unknown, never>>> {
let searchClient = this.client.getSearchClient(indexName);
const result = await searchClient.search(searchText, options);
console.log("search result", result);
const startTime = new Date();
const result = await this.getSearchClient(indexName).search(searchText, options);
console.log(`SearchLibrary took ${new Date().getTime() - startTime.getTime()}ms`);
return result;
}

async deleteDocument(indexName: string, document: any): Promise<void> {
await this.client.getSearchClient(indexName).deleteDocuments([document]);
try {
await this.searchClients.get(indexName).deleteDocuments([document]);
} catch (error) {
throw new Error(`Failed to delete document from index ${indexName}: ${error.message}`);
}
}

async indexDocument(indexName: string, document: any): Promise<void> {
const searchClient = this.client.getSearchClient(indexName);
searchClient.mergeOrUploadDocuments([document]);
try {
await this.searchClients.get(indexName).mergeOrUploadDocuments([document]);
} catch (error) {
throw new Error(`Failed to index document in index ${indexName}: ${error.message}`);
}
}

async deleteIndex(indexName: string): Promise<void> {
return this.client.deleteIndex(indexName);
try {
await this.client.deleteIndex(indexName);
this.searchClients.delete(indexName);
console.log(`Index ${indexName} deleted`);
} catch (error) {
throw new Error(`Failed to delete index ${indexName}: ${error.message}`);
}
}

// async updateIndex(indexName: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ export interface IMemoryCognitiveSearchCollection<DocumentType extends BaseDocum
deleteDocument(document: DocumentType): Promise<void>;
}
export interface IMemoryCognitiveSearch {
createIndexIfNotExists(indexName: string, indexDefinition: SearchIndex): Promise<void>;
createIndexIfNotExists(indexDefinition: SearchIndex): Promise<void>;
createOrUpdateIndexDefinition(indexName: string, indexDefinition: SearchIndex): Promise<void>;
deleteDocument(indexName: string, document: any): Promise<void>;
indexDocument(indexName: string, document: any): Promise<void>;
search(indexName: string, searchText: string, options?: any): Promise<any>;
indexExists(indexName: string): Promise<boolean>;
logSearchCollectionIndexMap(): void;

}


Expand Down Expand Up @@ -45,14 +46,19 @@ export class MemoryCognitiveSearch implements IMemoryCognitiveSearch, CognitiveS
this.searchCollectionIndexMap = new Map<string, MemoryCognitiveSearchCollection<any>>();
this.searchCollectionIndexDefinitionMap = new Map<string, SearchIndex>();
}
initializeSearchClients(): Promise<void> {
throw new Error("Method not implemented.");
}

deleteIndex(indexName: string): Promise<void> {
throw new Error("Method not implemented.");
}

async createIndexIfNotExists(indexName: string, indexDefinition: SearchIndex): Promise<void> {
if (this.searchCollectionIndexMap.has(indexName)) return;
this.createNewIndex(indexName, indexDefinition);
async createIndexIfNotExists(indexDefinition: SearchIndex): Promise<void> {
if (this.searchCollectionIndexMap.has(indexDefinition.name)) {
return;
}
this.createNewIndex(indexDefinition.name, indexDefinition);
}
private createNewIndex(indexName: string, indexDefinition: SearchIndex) {
this.searchCollectionIndexDefinitionMap.set(indexName, indexDefinition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export { SearchDocumentsResult, SearchIndex, GeographyPoint } from '@azure/searc


export interface CognitiveSearchBase {
createIndexIfNotExists(indexName: string, indexDefinition: SearchIndex): Promise<void>;
createIndexIfNotExists(indexDefinition: SearchIndex): Promise<void>;
createOrUpdateIndexDefinition(indexName: string, indexDefinition: SearchIndex): Promise<void>;
deleteDocument(indexName: string, document: any): Promise<void>;
indexDocument(indexName: string, document: any): Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export class ServiceTicketV1SearchApiImpl extends CognitiveSearchDataSource<AppC
}
}


return filterStrings.join(' and ');
}

Expand Down Expand Up @@ -85,9 +84,7 @@ export class ServiceTicketV1SearchApiImpl extends CognitiveSearchDataSource<AppC
async serviceTicketsSearch(input: ServiceTicketsSearchInput, memberId: string): Promise<SearchDocumentsResult<Pick<unknown, never>>> {
let searchString = input.searchString.trim();

console.log(`Resolver>Query>serviceTicketsSearch: ${searchString}`);
let filterString = this.getFilterString(input.options.filter, memberId);
console.log('filterString: ', filterString);

let searchResults: SearchDocumentsResult<Pick<unknown, never>>;
await this.withSearch(async (_passport, search) => {
Expand All @@ -103,7 +100,6 @@ export class ServiceTicketV1SearchApiImpl extends CognitiveSearchDataSource<AppC
});
});

console.log(`Resolver>Query>serviceTicketsSearch ${JSON.stringify(searchResults)}`);
return searchResults;
}

Expand All @@ -113,9 +109,7 @@ export class ServiceTicketV1SearchApiImpl extends CognitiveSearchDataSource<AppC
searchString = input.searchString.trim();
}

console.log(`Resolver>Query>serviceTicketsSearchAdmin: ${searchString}`);
let filterString = this.getFilterStringAdmin(input ? input.options.filter : null, communityId);
console.log('filterString: ', filterString);

let searchResults: SearchDocumentsResult<Pick<unknown, never>>;
await this.withSearch(async (_passport, search) => {
Expand All @@ -129,7 +123,6 @@ export class ServiceTicketV1SearchApiImpl extends CognitiveSearchDataSource<AppC
});
});

console.log(`Resolver>Query>serviceTicketsSearchAdmin ${JSON.stringify(searchResults)}`);
return searchResults;
}

Expand All @@ -152,17 +145,17 @@ export class ServiceTicketV1SearchApiImpl extends CognitiveSearchDataSource<AppC
};
}

async reIndexServiceTickets():Promise<SearchDocumentsResult<Pick<unknown, never>>> {
async reIndexServiceTickets(): Promise<SearchDocumentsResult<Pick<unknown, never>>> {
// drop index, create a brand new index
await this.withSearch(async (_passport, searchService) => {
await searchService.deleteIndex(ServiceTicketIndexSpec.name);
await searchService.createIndexIfNotExists(ServiceTicketIndexSpec.name, ServiceTicketIndexSpec);
await searchService.createIndexIfNotExists(ServiceTicketIndexSpec);
});

const context = await ReadOnlyDomainExecutionContext();
const ids = await this.context.applicationServices.service.dataApi.getAllIds();

await ServiceTicketV1UnitOfWork.withTransaction(context, ReadOnlyInfrastructureContext() ,async (repo) => {
await ServiceTicketV1UnitOfWork.withTransaction(context, ReadOnlyInfrastructureContext(), async (repo) => {
const searchDocs: Partial<ServiceTicketIndexDocument>[] = [];

// loop through ids, get objects, convert to domain objects, convert to index document, update search index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export class PropertySearchApiImpl extends CognitiveSearchDataSource<AppContext>
}
}

console.log('filterStrings: ', filterStrings.join(' and '));

return filterStrings.join(' and ');
}
Expand All @@ -115,26 +114,17 @@ export class PropertySearchApiImpl extends CognitiveSearchDataSource<AppContext>
}

async propertiesSearch(input: PropertiesSearchInput): Promise<PropertySearchResult> {

let searchResults: SearchDocumentsResult<Pick<unknown, never>>;
await this.withSearch(async (_passport, searchService) => {
// for the first time, create the index
const indexExists = await searchService.indexExists(PropertyListingIndexSpec.name);
if (!indexExists) {
console.log(`Index ${PropertyListingIndexSpec.name} does not exist, creating it...`);
await searchService.createIndexIfNotExists(PropertyListingIndexSpec.name, PropertyListingIndexSpec);
return undefined;
}
await searchService.createIndexIfNotExists(PropertyListingIndexSpec);

let searchString = '';
if (!input.options.filter?.position) {
searchString = input.searchString.trim();
}

console.log(`Resolver>Query>propertiesSearch: ${searchString}`);

let filterString = this.getFilterString(input.options.filter);
console.log('filterString: ', filterString);


searchResults = await searchService.search('property-listings', searchString, {
queryType: 'full',
Expand All @@ -148,7 +138,6 @@ export class PropertySearchApiImpl extends CognitiveSearchDataSource<AppContext>
});
});

console.log(`Resolver>Query>propertiesSearch ${JSON.stringify(searchResults)}`);
const results = this.convertToGraphqlResponse(searchResults, input);
return results;
}
Expand Down
Loading
Loading