UNPKG

ee-ts-util

Version:

typescript utilities and functions

136 lines 7.87 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("./common"); const usersInfoAdapter_1 = require("./usersInfoAdapter"); const ramda_1 = require("ramda"); const projectAdapter_1 = require("./projectAdapter"); const Option_1 = require("fp-ts/lib/Option"); const assignmentsAdapter_1 = require("./assignmentsAdapter"); const log4js = require("log4js"); var AssignmentType; (function (AssignmentType) { AssignmentType["PROJECT"] = "Project"; AssignmentType["LEAVE_TYPE"] = "LeaveType"; })(AssignmentType || (AssignmentType = {})); exports.buildFetchTimeEntryAdapter = (baseUrl, token) => exports.buildFetchTimeEntryAdapterWithResultsPerPage(baseUrl, token, 50); // TODO: this adapter is no more either the TimeEntries adapter or a ts-util adapter // we need to refactor this code into an adapter at the backend layer. // TimeEntries Adapter should just fetch time entries and all this orchestration should be done at the backend level. // Making it work now, but this will need refactoring work in it on the future. // author: @jpinho exports.buildFetchTimeEntryAdapterWithResultsPerPage = (baseUrl, token, resultsPerPage) => (from, to) => __awaiter(this, void 0, void 0, function* () { try { const timeEntries = yield common_1.fetchPageData(baseUrl, `/api/v1/time_entries?fields=approvals&from=${from}&to=${to}&per_page=${resultsPerPage}`, token, [], exports.extractDto); const getUsersInfoAdapter = usersInfoAdapter_1.buildGetUsersInfoAdapterWithResultsPerPage(baseUrl, token, resultsPerPage); const usersInfo = yield getUsersInfoAdapter(ramda_1.uniq(timeEntries.map((te) => te.userId))); const uniqueAssignableIds = ramda_1.uniq(timeEntries .filter((te) => te.assignableType !== "LeaveType") .map((te) => te.assignableId)); const fetchProjectInfo = projectAdapter_1.buildFetchProjectInfoAdapter(baseUrl, token); const projects = yield Promise.all(uniqueAssignableIds.map((projectId) => __awaiter(this, void 0, void 0, function* () { return yield fetchProjectInfo(projectId); }))); const fetchAssignmentsForAProjectAdapter = assignmentsAdapter_1.buildFetchAssignmentsAdapter(baseUrl, token); const assignmentsMap = new Map(); yield Promise.all(uniqueAssignableIds.map((assignableId) => __awaiter(this, void 0, void 0, function* () { const assignmentsDtos = yield fetchAssignmentsForAProjectAdapter(assignableId); assignmentsMap.set(assignableId, assignmentsDtos); return; }))); const genuineTimeentries = timeEntries.filter((t) => t.hours > 0); return populateTE(genuineTimeentries, usersInfo, assignmentsMap, projects); } catch (error) { return Promise.reject(error); } }); const populateTE = (timeEntries, usersInfo, assignmentsMap, projects) => timeEntries.map((te) => { const userInfo = usersInfo.find((u) => u.userId === te.userId); if (!userInfo) { throw new Error(`Could not find userId ${te.userId}`); } te.firstName = userInfo.firstName; te.lastName = userInfo.lastName; te.email = userInfo.email; if (te.assignableType !== AssignmentType.LEAVE_TYPE) { const projectInfo = projects.find((project) => project.id === te.assignableId) || projectAdapter_1.UNDEFINED_PROJECT; te.projectName = projectInfo.name; te.assignableName = projectInfo.clientName; te.billable = projectInfo.billable; te.parentId = projectInfo.parentId; te.projectOrPhaseStartDate = projectInfo.startDate; te.projectOrPhaseEndDate = projectInfo.endDate; populateResourceStartAndEndDate(assignmentsMap, te); } else { te.assignableName = te.assignableType; te.billable = false; } return te; }); const populateResourceStartAndEndDate = (assignmentsMap, te) => { const log = log4js.getLogger(); const assignmentsDtos = assignmentsMap.get(te.assignableId); if (assignmentsDtos) { const assignmentsDtoForUser = assignmentsDtos.filter((dto) => dto.user_id === te.userId); if (assignmentsDtoForUser.length === 0) { const errorMsg = `No matching assignments for a user ${te.userId} for project ${te.assignableId}.` + `This might have happened because, consultant is removed from project.` + `Best terminate a resource assigning a end date on project than removing from it. `; log.warn(errorMsg); te.resourceStartDateOnProjectOrPhase = te.projectOrPhaseStartDate; te.resourceEndDateOnProjectOrPhase = te.projectOrPhaseEndDate; } else { te.resourceStartDateOnProjectOrPhase = assignmentsDtoForUser[0].starts_at; te.resourceEndDateOnProjectOrPhase = assignmentsDtoForUser[0].ends_at; } } else { throw new Error(`Assignment not found for ${te.assignableId}`); } }; exports.extractDto = (element) => ({ hours: element.hours, day: new Date(element.date), userId: element.user_id, assignableId: element.assignable_id, assignableType: element.assignable_type, approved: exports.toApprovedOrNot(element.approvals.data), status: element.approvals.data, hourlyBillRate: element.bill_rate, createdAt: element.created_at, updatedAt: element.updated_at, }); // TODO RF : 27/12/2017 : We have a small dillema here (here comes the story): // 1) toApprovedOrNot is clearly an internal function, thus it shouldn't have a unit test. // 1.a) another sign that a unit test for toApprovedOrNot is a smell: it deals with external data // (10KFT, that we don't control). // 2) the other option is to have integration tests covering all the toApprovedOrNot paths, but that's not good either. // 2.a) integration tests are not meant to cover different paths, but to be sure a component "talks" correctly with // the external world. // 2.b) these tests would be very fragile and complex to maintain, since they would depend on very specific // combination of data in a staging environment. // Conclusion 1: This seems to confirm what our team members are already feeling: that we have too much logic // happening in ts-util adaptors. // If we leave ts-util to convert a external domain to EE domain, and implement business logic in the // backend, we solve this problem. // Conclusion 2: Until we refactor the adaptors in ts-util and move domain related logic to the backend, // we have to choose the "less" bad option. // So we are exposing toApprovedOrNot and implementing unit tests for it. exports.toApprovedOrNot = (maybeApprovals) => fromEmpty(maybeApprovals).fold(() => false, (approvals) => { const approved = true; return approvals.reduce((acc, current) => acc && isApproved(current), approved); }); const isApproved = (a) => a.status === "approved"; // This is DUPLICATED in projectAdapter const fromEmpty = (maybeArray) => { return Option_1.fromNullable(maybeArray).fold(() => Option_1.none, (array) => array.length === 0 ? Option_1.none : Option_1.some(array)); }; //# sourceMappingURL=timeEntryAdapter.js.map