Blob Blame History Raw
import { call, put, takeEvery, takeLatest, select } from "redux-saga/effects";
import * as composer from "../composer";
import {
  FETCHING_INPUTS,
  fetchingInputsSucceeded,
  fetchingFilterNoResults,
  FETCHING_INPUT_DETAILS,
  FETCHING_INPUT_DEPS,
  FETCHING_DEP_DETAILS,
  setSelectedInput,
  setSelectedInputDeps,
  setDepDetails,
} from "../actions/inputs";
import { makeGetBlueprintById, makeGetSelectedDeps } from "../selectors";

function flattenInputs(response) {
  // duplicate inputs exist when more than one build is available
  // flatten duplicate inputs to a single item
  const previousInputs = {};
  const flattened = response.filter((item) => {
    const build = {
      version: item.builds[0].source.version,
      release: item.builds[0].release,
    };
    if (previousInputs.hasOwnProperty(item.name)) {
      // update the previousInput object with this item"s version/release
      // to make the default version/release the latest
      previousInputs[item.name] = Object.assign(previousInputs[item.name], build);
      // and remove this item from the list
      return false;
    }
    delete item.builds;
    item = Object.assign(item, build);

    previousInputs[item.name] = item;
    return true;
  });
  return flattened;
}

function* fetchInputs(action) {
  try {
    const { filter, selectedInputPage, pageSize } = action.payload;
    const wildcardsUsed = filter.value.includes("*");
    const regex = / +|, +/g;
    let filterValue = filter.value.replace(regex, ",");
    const regexStrip = /(^,+)|(,+$)/g;
    filterValue = filterValue.replace(regexStrip, "");
    filterValue = wildcardsUsed ? filterValue : `*${filterValue}*`.replace(/,/g, "*,*");
    // page is displayed in UI starting from 1 but api starts from 0
    const pageIndex = selectedInputPage - 1;
    const response = yield call(composer.listModules, filterValue, pageIndex, pageSize);
    const { total } = response;
    const inputNames = response.modules.map((input) => input.name).join(",");
    const inputs = yield call(composer.getComponentInfo, inputNames);
    const updatedInputs = flattenInputs(inputs).map((input) => {
      const inputData = {
        ui_type: "RPM",
        ...input,
      };
      return inputData;
    });
    yield put(fetchingInputsSucceeded(filter, selectedInputPage, pageSize, updatedInputs, total));
  } catch (error) {
    const { filter, pageSize } = action.payload;
    if (filter.value !== "") {
      yield put(fetchingFilterNoResults(filter, pageSize));
    } else {
      console.log("Error in fetchInputsSaga", error);
    }
  }
}

function flattenInput(response) {
  // each response item is a different build (version, release, arch)
  // flatten the response to a single item with an array of builds
  // only keep the latest release per version
  // for each version, include wildcard options
  const previousBuilds = {};
  const flattened = { ...response[0], builds: [] };
  response.forEach((item) => {
    let previousBuild;
    const build = {
      version: item.builds[0].source.version,
      release: item.builds[0].release,
      arch: [item.builds[0].arch],
    };
    if (previousBuilds.hasOwnProperty(build.version)) {
      // if this item has the same version value as a
      // previousBuild replace the release value (this is assumed to be the latest)
      // then push arch to the array of arch's and filter this item out
      previousBuild = previousBuilds[build.version];
      previousBuild.arch = previousBuild.arch.concat(build.arch);
      previousBuild.release = build.release;
      return false;
    }
    // else push the build to the array of builds
    flattened.builds = [build].concat(flattened.builds);

    previousBuilds[build.version] = build;
    return true;
  });
  flattened.builds = addWildcardVersions(flattened.builds);
  return flattened;
}

function addWildcardVersions(builds) {
  const wildcardBuilds = {};
  builds.forEach((item) => {
    item.depsolveVersion = item.version;

    const parts = item.version.split(".");
    for (let i = 0; i < parts.length; i += 1) {
      const wildcard = parts.slice(0, i).concat("*").join(".");

      if (!(wildcard in wildcardBuilds)) {
        wildcardBuilds[wildcard] = { ...item, version: wildcard };
      }
    }
    wildcardBuilds[item.version] = { ...item };
  });

  return Object.values(wildcardBuilds);
}

// when ComponentDetailsView loads, get component details
function* fetchInputDetails(action) {
  try {
    const { component } = action.payload;
    const response = yield call(composer.getComponentInfo, component.name);
    const updatedResponse = flattenInput(response);
    const componentData = {
      ...component,
      builds: updatedResponse.builds,
      description: updatedResponse.description,
      homepage: updatedResponse.homepage,
      summary: updatedResponse.summary,
    };
    yield put(setSelectedInput(componentData));
  } catch (error) {
    console.log("Error in fetchInputDetails", error);
  }
}

// when ComponentDetailsView loads, get component dependencies
function* fetchInputDeps(action) {
  try {
    const { component } = action.payload;
    const response = yield call(composer.getComponentDependencies, component.name);
    let responseIndex;
    if (response[0].builds) {
      responseIndex = response.findIndex((item) => {
        return item.builds[0].release === component.release && item.builds[0].source.version === component.version;
      });
    } else {
      responseIndex = 0;
    }
    const deps = response[responseIndex].dependencies.filter((item) => item.name !== component.name);
    const updatedDeps = deps.map((dep) => {
      const depData = {
        ui_type: "RPM",
        ...dep,
      };
      delete depData.epoch;
      delete depData.arch;
      return depData;
    });
    yield put(setSelectedInputDeps(updatedDeps));
  } catch (error) {
    console.log("Error in fetchInputDeps", error);
  }
}

// when expanding a dependency list item in ComponentDetailsView
// get additional details to display in expanded section
function* fetchDepDetails(action) {
  try {
    const { component, blueprintId } = action.payload;
    const response = yield call(composer.getComponentDependencies, component.name);
    const deps = response[0].dependencies.filter((item) => item.name !== component.name);
    const updatedDeps = deps.map((dep) => {
      const depData = { ui_type: "RPM", ...dep };
      delete depData.epoch;
      delete depData.arch;
      return depData;
    });
    const getBlueprintById = makeGetBlueprintById();
    const blueprint = yield select(getBlueprintById, blueprintId);
    const { components } = blueprint.present;

    const getSelectedDeps = makeGetSelectedDeps();
    const selectedDeps = yield select(getSelectedDeps, updatedDeps, components);

    const depDetails = {
      ...component,
      description: response[0].description,
      homepage: response[0].homepage,
      summary: response[0].summary,
      dependencies: selectedDeps,
    };
    yield put(setDepDetails(depDetails));
  } catch (error) {
    console.log("Error in fetchDepDetails", error);
  }
}

export default function* () {
  yield takeLatest(FETCHING_INPUTS, fetchInputs);
  yield takeLatest(FETCHING_INPUT_DETAILS, fetchInputDetails);
  yield takeLatest(FETCHING_INPUT_DEPS, fetchInputDeps);
  yield takeEvery(FETCHING_DEP_DETAILS, fetchDepDetails);
}