runner_search_utils.js 5.32 KB
Newer Older
1 2
import { queryToObject, setUrlParams } from '~/lib/utils/url_utility';
import {
3 4 5 6 7 8
  filterToQueryObject,
  processFilters,
  urlQueryToFilter,
  prepareTokens,
} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import {
9 10
  PARAM_KEY_STATUS,
  PARAM_KEY_RUNNER_TYPE,
11 12
  PARAM_KEY_TAG,
  PARAM_KEY_SEARCH,
13
  PARAM_KEY_SORT,
Miguel Rincon's avatar
Miguel Rincon committed
14 15 16
  PARAM_KEY_PAGE,
  PARAM_KEY_AFTER,
  PARAM_KEY_BEFORE,
17
  DEFAULT_SORT,
Miguel Rincon's avatar
Miguel Rincon committed
18
  RUNNER_PAGE_SIZE,
19
  STATUS_NEVER_CONTACTED,
20
} from './constants';
21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
/**
 * The filters and sorting of the runners are built around
 * an object called "search" that contains the current state
 * of search in the UI. For example:
 *
 * ```
 * const search = {
 *   // The current tab
 *   runnerType: 'INSTANCE_TYPE',
 *
 *   // Filters in the search bar
 *   filters: [
 *     { type: 'status', value: { data: 'ACTIVE', operator: '=' } },
 *     { type: 'filtered-search-term', value: { data: '' } },
 *   ],
 *
 *   // Current sorting value
 *   sort: 'CREATED_DESC',
 *
 *   // Pagination information
 *   pagination: { page: 1 },
 * };
 * ```
 *
 * An object in this format can be used to generate URLs
 * with the search parameters or by runner components
 * a input using a v-model.
 *
 * @module runner_search_utils
 */

/**
 * Validates a search value
 * @param {Object} search
 * @returns {boolean} True if the value follows the search format.
 */
export const searchValidator = ({ runnerType, filters, sort }) => {
  return (
    (runnerType === null || typeof runnerType === 'string') &&
    Array.isArray(filters) &&
    typeof sort === 'string'
  );
};

Miguel Rincon's avatar
Miguel Rincon committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
const getPaginationFromParams = (params) => {
  const page = parseInt(params[PARAM_KEY_PAGE], 10);
  const after = params[PARAM_KEY_AFTER];
  const before = params[PARAM_KEY_BEFORE];

  if (page && (before || after)) {
    return {
      page,
      before,
      after,
    };
  }
  return {
    page: 1,
  };
};

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
// Outdated URL parameters
const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';

/**
 * Returns an updated URL for old (or deprecated) admin runner URLs.
 *
 * Use for redirecting users to currently used URLs.
 *
 * @param {String?} URL
 * @returns Updated URL if outdated, `null` otherwise
 */
export const updateOutdatedUrl = (url = window.location.href) => {
  const urlObj = new URL(url);
  const query = urlObj.search;

  const params = queryToObject(query, { gatherArrays: true });

  const runnerType = params[PARAM_KEY_STATUS]?.[0] || null;
  if (runnerType === STATUS_NOT_CONNECTED) {
    const updatedParams = {
      [PARAM_KEY_STATUS]: [STATUS_NEVER_CONTACTED],
    };
    return setUrlParams(updatedParams, url, false, true, true);
  }
  return null;
};

110 111 112 113 114
/**
 * Takes a URL query and transforms it into a "search" object
 * @param {String?} query
 * @returns {Object} A search object
 */
115 116
export const fromUrlQueryToSearch = (query = window.location.search) => {
  const params = queryToObject(query, { gatherArrays: true });
117
  const runnerType = params[PARAM_KEY_RUNNER_TYPE]?.[0] || null;
118 119

  return {
120
    runnerType,
121 122
    filters: prepareTokens(
      urlQueryToFilter(query, {
123
        filterNamesAllowList: [PARAM_KEY_STATUS, PARAM_KEY_TAG],
124 125 126
        filteredSearchTermKey: PARAM_KEY_SEARCH,
      }),
    ),
127
    sort: params[PARAM_KEY_SORT] || DEFAULT_SORT,
Miguel Rincon's avatar
Miguel Rincon committed
128
    pagination: getPaginationFromParams(params),
129 130 131
  };
};

132 133 134 135 136 137 138
/**
 * Takes a "search" object and transforms it into a URL.
 *
 * @param {Object} search
 * @param {String} url
 * @returns {String} New URL for the page
 */
Miguel Rincon's avatar
Miguel Rincon committed
139
export const fromSearchToUrl = (
140
  { runnerType = null, filters = [], sort = null, pagination = {} },
Miguel Rincon's avatar
Miguel Rincon committed
141 142
  url = window.location.href,
) => {
143 144 145 146
  const filterParams = {
    // Defaults
    [PARAM_KEY_STATUS]: [],
    [PARAM_KEY_RUNNER_TYPE]: [],
147
    [PARAM_KEY_TAG]: [],
148 149 150 151
    // Current filters
    ...filterToQueryObject(processFilters(filters), {
      filteredSearchTermKey: PARAM_KEY_SEARCH,
    }),
152 153
  };

154 155 156 157
  if (runnerType) {
    filterParams[PARAM_KEY_RUNNER_TYPE] = [runnerType];
  }

158 159 160 161
  if (!filterParams[PARAM_KEY_SEARCH]) {
    filterParams[PARAM_KEY_SEARCH] = null;
  }

162 163 164 165 166 167 168 169 170
  const isDefaultSort = sort !== DEFAULT_SORT;
  const isFirstPage = pagination?.page === 1;
  const otherParams = {
    // Sorting & Pagination
    [PARAM_KEY_SORT]: isDefaultSort ? sort : null,
    [PARAM_KEY_PAGE]: isFirstPage ? null : pagination.page,
    [PARAM_KEY_BEFORE]: isFirstPage ? null : pagination.before,
    [PARAM_KEY_AFTER]: isFirstPage ? null : pagination.after,
  };
Miguel Rincon's avatar
Miguel Rincon committed
171

172
  return setUrlParams({ ...filterParams, ...otherParams }, url, false, true, true);
173 174
};

175 176 177 178 179 180 181 182 183 184 185 186
/**
 * Takes a "search" object and transforms it into variables for runner a GraphQL query.
 *
 * @param {Object} search
 * @returns {Object} Hash of filter values
 */
export const fromSearchToVariables = ({
  runnerType = null,
  filters = [],
  sort = null,
  pagination = {},
} = {}) => {
187 188
  const variables = {};

189 190 191 192 193
  const queryObj = filterToQueryObject(processFilters(filters), {
    filteredSearchTermKey: PARAM_KEY_SEARCH,
  });

  [variables.status] = queryObj[PARAM_KEY_STATUS] || [];
194
  variables.search = queryObj[PARAM_KEY_SEARCH];
195 196
  variables.tagList = queryObj[PARAM_KEY_TAG];

197 198 199
  if (runnerType) {
    variables.type = runnerType;
  }
200 201 202 203
  if (sort) {
    variables.sort = sort;
  }

Miguel Rincon's avatar
Miguel Rincon committed
204 205 206 207 208 209 210 211
  if (pagination.before) {
    variables.before = pagination.before;
    variables.last = RUNNER_PAGE_SIZE;
  } else {
    variables.after = pagination.after;
    variables.first = RUNNER_PAGE_SIZE;
  }

212 213
  return variables;
};