magnifying-glassSearch

How the search function is built and where it is rooted.

There are currently two search types:

  • Global - the one that searches on all pages where the search is built in.

  • Local - on a specific page and only on the material of that page.

Backend

On the backend you won't find separate module that responsible for search mechanic. Instead, you will find search methods in inside modules, depending on what we are looking for. Local search - it's a regular "findAll" etc. methods which include search queries to DB, depending on various params such as filters, sorters and pagination.

The Global search function is implemented in the following modules:

  • Leads

  • Businesses

  • Users

If talk about Local search, you can find it on this pages:

  • Leads

  • Businesses

  • Users (Super admin and admin roles)

  • Team

Global search method are pretty same in all modules. For example, method from leads.service.ts:

leads.service.ts:
    async search(search: string, user: JwtUserPayload) {
        const sanitizedSearch = sanitizeString(search);

        const userWithMemberships = await this.userRepository.findOne({
            where: { id: user.id },
            relations: ['memberships', 'memberships.team'],
        });

        const memberships = userWithMemberships.memberships.filter(m => m.team).map(m => m.team.id);

        const leadsQuery = this.leadsRepository
            .createQueryBuilder('lead')
            .leftJoinAndSelect('lead.business', 'business')
            .leftJoinAndSelect('lead.team', 'team')
            .leftJoinAndSelect('lead.status', 'status')
            .leftJoin('team.memberships', 'membership')
            .where('lead.isDeleted = false')
            .andWhere(
                new Brackets(qb => {
                    qb.where('lead.firstName ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('lead.lastName ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('lead.state::text ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('status.name::text ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('lead.source ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere("ARRAY_TO_STRING(lead.phoneNumber, ',') ILIKE :search", {
                            search: `%${sanitizedSearch}%`,
                        })
                        .orWhere('lead.email::text ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('lead.description ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('lead.location ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('lead.position ILIKE :search', { search: `%${sanitizedSearch}%` })
                        .orWhere('business.name ILIKE :search', { search: `%${sanitizedSearch}%` });
                })
            );

        if (!user.roles.includes(Role.Admin) && !user.roles.includes(Role.SuperAdmin)) {
            leadsQuery.andWhere('team.id IN (:...memberships)', { memberships });
        }

        const leads = await leadsQuery.getMany();

        return leads;
    }
triangle-exclamation

This utility you will find in api/common/helpers/

triangle-exclamation

"FindAll" method with search query: (same leads service)

circle-exclamation

All Global Search Methods use Guard which will limit the number of requests from a single address. This is done to protect against excessive requests to the server and to avoid unforeseen errors. This protection will send us an error if the number of search queries reaches the limit.

If we talk about Guard , then it is still used not only on search methods. Everything is done with the same purpose.

Controller with Guard:

Guard function:

Here you can configure how many requests can pass during how long:

circle-exclamation

Frontend

This part is made within several global components, which ends with one defined. They lie along the following path:

.../ui/common/components/GlobalSearch/

There you can find one "global" component that stores the input and various modal windows that are responsible for finding and displaying the result.

If we talk about the search on the pages - there will be used only the input that is used in the Global Search.

Let's look at certain features of the search.

A service that processes a search request to the backend: (.../ui/serivces/globalSearchService.js)

Since we have search methods lie each in its own module, then the query here is formed a little in its own way, all understandable.

Search function in the main component:

circle-exclamation

Search Input are written a bit different in Global Search and in the pages.

Global Search component:

One that is used in Leads Page:

The difference is not big, but it has its own specifics depending on page.

circle-exclamation

Last updated