huridocs/uwazi

View on GitHub
app/react/V2/Routes/Settings/ActivityLog/ActivityLogLoader.ts

Summary

Maintainability
A
0 mins
Test Coverage
/* eslint-disable react/jsx-props-no-spreading */
import { LoaderFunction, SetURLSearchParams, createSearchParams, Location } from 'react-router-dom';
import { IncomingHttpHeaders } from 'http';
import _, { isArray, isEqual, isObject } from 'lodash';
import moment from 'moment';
import { searchParamsFromSearchParams } from 'app/utils/routeHelpers';
import { ClientSettings } from 'app/apiResponseTypes';
import * as activityLogAPI from 'V2/api/activityLog';
import type { ActivityLogResponse } from 'V2/api/activityLog';
import { ActivityLogEntryType } from 'shared/types/activityLogEntryType';

const ITEMS_PER_PAGE = 100;

type LogEntry = ActivityLogEntryType & { rowId: string };

interface LoaderData {
  activityLogData: LogEntry[];
  totalPages: number;
  total: number;
  page: number;
  error?: {};
}

const sortingParams = ['method', 'time', 'username', 'url'];

interface ActivityLogSearchParams {
  username?: string;
  search?: string;
  method?: string[];
  from?: string;
  to?: string;
  sort?: string;
  order?: string;
  page?: number;
  limit?: number;
}

const timeFilter = (from?: string, to?: string, dateFormat = 'YYYY-MM-DD') => {
  const fromDate = from && moment(from, dateFormat).toDate().getTime();
  const toDate = to && moment(to, dateFormat).toDate().getTime();
  return { ...(fromDate && { from: fromDate }), ...(toDate && { to: toDate }) };
};

const sortParam = (sort = '', order = '') =>
  sortingParams.includes(sort) ? { prop: sort, asc: +(order === 'asc') } : { prop: 'time', asc: 0 };

const paramOrEmpty = (condition: boolean, param: {}) => (condition ? param : {});

const getQueryParamsBySearchParams = (
  searchParams: ActivityLogSearchParams,
  dateFormat = 'YYYY-MM-DD'
) => {
  const {
    username,
    search,
    method = [],
    from,
    to,
    sort = 'time',
    order = 'desc',
    page = 1,
    limit = ITEMS_PER_PAGE,
  } = searchParams;
  const time = timeFilter(from, to, dateFormat.toUpperCase());
  const sortOptions = sortParam(sort, order);
  const methodList = isArray(method) ? method : [method];
  const params = {
    ...paramOrEmpty(username !== undefined, { username }),
    ...paramOrEmpty(search !== undefined, { search }),
    ...paramOrEmpty(time.from !== undefined || time.to !== undefined, { time }),
    ...paramOrEmpty(methodList[0] !== undefined, { method: methodList }),
    page,
    limit,
    sort: sortOptions,
  };
  return params;
};

const getAppliedFilters = (searchParams: URLSearchParams) => {
  let appliedFilters = searchParamsFromSearchParams(searchParams);
  appliedFilters =
    appliedFilters.method && !isArray(appliedFilters.method)
      ? { ...appliedFilters, method: [appliedFilters.method] }
      : appliedFilters;
  const { from, to, ...rest } = appliedFilters;
  return { ...rest, ...((from || to) && { dateRange: { from, to } }) };
};

const activityLogLoader =
  (headers?: IncomingHttpHeaders, handlerContext?: { settings?: ClientSettings }): LoaderFunction =>
  async ({ request }) => {
    const { settings } = handlerContext || { dateFormat: 'YYYY-MM-DD' };
    const urlSearchParams = new URLSearchParams(request.url.split('?')[1]);
    const searchParams = searchParamsFromSearchParams(urlSearchParams);
    const params = getQueryParamsBySearchParams(searchParams, settings?.dateFormat);
    const activityLogList: ActivityLogResponse = await activityLogAPI.get(params, headers);
    if (activityLogList.message !== undefined) {
      return {
        error: activityLogList.message,
        activityLogData: [],
        totalPages: 0,
        page: 0,
        total: 0,
      };
    }
    const totalPages = Math.ceil(activityLogList.totalRows / params.limit);

    return {
      activityLogData: activityLogList.rows.map(row => ({ ...row, rowId: row._id })),
      totalPages,
      page: params.page,
      total: activityLogList.totalRows,
    };
  };

interface ActivityLogSearch {
  username?: string;
  search?: string;
  page?: number;
  dateRange?: {
    from?: string;
    to?: string;
  };
  sort?: string;
  order?: string;
}

const changedParams = (filterPairs: [string, any][]) => {
  const newFilters = createSearchParams(filterPairs);
  return Array.from(newFilters).filter(([_key, value]) => value !== '');
};

const setSearchValue = (prev: URLSearchParams, key: string, value: any) => {
  if (!isArray(value)) {
    prev.set(key, value);
  } else if (value.length > 0) {
    prev.set(key, value[0]);
    value.splice(0, 1);
    value.forEach(item => {
      prev.append(key, item);
    });
  }
};

const filterPairs = (filters: ActivityLogSearch) => {
  _.flatMap(filters);
  const pairs = _(filters).toPairs().sortBy(0).value();
  const plainFilters: [string, string][] = [];
  pairs.forEach(([key, value]) => {
    if (isArray(value) || !isObject(value)) {
      plainFilters.push([key, value]);
    } else {
      plainFilters.push(..._(value).toPairs().value());
    }
  });
  return _(plainFilters).sortBy(0).value();
};

const updateSearch = (
  filters: ActivityLogSearch,
  searchParams: URLSearchParams,
  setSearchParams: SetURLSearchParams
) => {
  const plainFilters = filterPairs(filters);
  const changedPairs = changedParams(plainFilters);
  if (!isEqual(changedPairs, Array.from(searchParams))) {
    setSearchParams((prev: URLSearchParams) => {
      prev.delete('page');
      plainFilters.forEach(([key, value]) => {
        if (
          value !== undefined &&
          ((isArray(value) && value.length > 0) || (!isArray(value) && value !== ''))
        ) {
          setSearchValue(prev, key, value);
        } else {
          prev.delete(key);
        }
      });
      return prev;
    });
  }
};

const buildPageURL = (appliedFilters: any, pageTo: string | number, location: Location<any>) => {
  const { dateRange, ...updatedParams } = {
    ...appliedFilters,
    page: pageTo,
    limit: appliedFilters.limit || ITEMS_PER_PAGE,
    ...appliedFilters.dateRange,
    dateRange: {},
  };
  const newParams: [string, string][] = [];
  Object.entries(updatedParams).forEach(([key, value]) => {
    if (!isArray(value)) {
      newParams.push([key, value as string]);
    } else {
      value.forEach(item => newParams.push([key, item as string]));
    }
  });
  return `${location.pathname}?${createSearchParams(newParams)}`;
};

export type { LoaderData, ActivityLogSearch, LogEntry };
export {
  activityLogLoader,
  getAppliedFilters,
  ITEMS_PER_PAGE,
  updateSearch,
  filterPairs,
  getQueryParamsBySearchParams,
  buildPageURL,
};