1
0
Fork 0

RatingService: Convert to TypeScript

This was an adventure, and was also used as a peer programming exercise to
introduce TypeScript to other programmers in the office.

This class has far too many dependencies, which made this difficult.  The
approach was to create .d.ts files for dependencies and wait on moving those
over for now, otherwise the task will never get done.

The RatingServicePublic trait was left as such for the time being; I was
able to work around a bug that was making it difficult to mix it into a
prototype.

There were no logic changes; this was just type refactoring.
master
Mike Gerwitz 2019-10-23 12:09:27 -04:00
parent 8f7afd22e5
commit 767a248e44
18 changed files with 959 additions and 123 deletions

View File

@ -0,0 +1,39 @@
/**
* Representation of actions to be performed by the client
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Action to be performed by the client
*
* TODO: More specific types
*/
export interface ClientAction
{
/** Action to be performed */
action: string;
/** Action arguments */
[P: string]: any;
}
/** Set of actions */
export type ClientActions = ClientAction[];

27
src/program/Program.d.ts vendored 100644
View File

@ -0,0 +1,27 @@
/**
* Contains Program base class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export declare abstract class Program
{
readonly ineligibleLockCount: number;
getId(): string;
}

93
src/quote/BaseQuote.d.ts vendored 100644
View File

@ -0,0 +1,93 @@
/**
* Contains program Quote class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @todo Use ``document'' terminology in place of ``quote''
*/
import { Program } from "../program/Program";
import { Quote, QuoteId } from "./Quote";
export declare class BaseQuote implements Quote
{
/**
* Retrieve Program associated with quote
*
* @return quote program
*/
getProgram(): Program;
/**
* Returns the program id associated with the quote
*
* @return program id
*/
getProgramId(): string;
/**
* Returns the quote id
*
* The quote id is immutable. A different quote id would represent a
* different quote, therefore a new object should be created with the
* desired quote id.
*
* @return quote id
*/
getId(): QuoteId;
/**
* Returns the id of the current step
*
* @return id of current step
*/
getCurrentStepId(): number;
/**
* Sets an explicit lock, providing a reason for doing so
*
* @param reason - lock reason
* @param step - step that user may not navigate prior
*
* @return self
*/
setExplicitLock( reason: string, step: number ): this;
/**
* Set the date that the premium was calculated as a Unix timestamp
*
* @param timestamp - Unix timestamp representing premium date
*
* @return self
*/
setLastPremiumDate( timestamp: UnixTimestamp ): this;
/**
* Retrieve the last time the premium was calculated
*
* @return last calculated time or 0
*/
getLastPremiumDate(): UnixTimestamp;
}

33
src/quote/Quote.d.ts vendored 100644
View File

@ -0,0 +1,33 @@
/**
* Generic interface to represent quotes
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @todo Use ``document'' terminology in place of ``quote''
*/
import { Program } from "../program/Program";
import { QuoteId } from "../document/Document";
export declare interface Quote
{
// TODO: the easejs interface is empty; is this actually needed?
}
export { QuoteId };

66
src/server/Server.d.ts vendored 100644
View File

@ -0,0 +1,66 @@
/**
* General server actions
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClientActions } from "../client/action/ClientAction";
import { ServerSideQuote } from "./quote/ServerSideQuote";
import { UserRequest } from "./request/UserRequest";
/**
* General server actions
*/
export declare class Server
{
/**
* Send response to user
*
* @param request - request to respond to
* @param quote - quote associated with request
* @param data - data with which to reply
* @param actions - optional client actions
*
* @return self
*/
sendResponse(
request: UserRequest,
quote: ServerSideQuote,
data: Record<string, any>,
actions?: ClientActions,
): this;
/**
* Send response to user
*
* @param request - request to respond to
* @param message - message to display to user
* @param actions - optional client actions
* @param btn_caption - optional caption for acknowledgement button
*
* @return self
*/
sendError(
request: UserRequest,
message: string,
actions?: ClientActions,
btn_caption?: string,
): this;
}

View File

@ -28,6 +28,8 @@ const {
ReplSetServers: ReplSetServers,
} = require( 'mongodb/lib/mongodb' );
const easejs = require( 'easejs' );
const regex_base = /^\/quote\/([a-z0-9-]+)\/?(?:\/(\d+)\/?(?:\/(.*))?|\/(program.js))?$/;
const regex_step = /^step\/(\d+)\/?(?:\/(post|visit))?$/;
@ -83,7 +85,7 @@ const {
ExportService,
},
RatingService,
RatingService: { RatingService },
RatingServicePublish,
TokenedService,
},
@ -136,7 +138,7 @@ exports.init = function( logger, enc_service, conf )
server.init( server_cache, exports.rater );
// TODO: temporary proof-of-concept
rating_service = RatingService.use(
rating_service = easejs( RatingService ).use(
RatingServicePublish( amqplib, exports.post_rate_publish, logger )
)(
logger, dao, server, exports.rater

146
src/server/db/MongoServerDao.d.ts vendored 100644
View File

@ -0,0 +1,146 @@
/**
* Mongo DB DAO for program server
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClassificationData, WorksheetData } from "../rater/Rater";
import { PositiveInteger } from "../../numeric";
import { QuoteId } from "../../document/Document";
import { ServerSideQuote } from "../quote/ServerSideQuote";
/** Success or failure callback */
type Callback = ( quote: ServerSideQuote ) => void;
/**
* MongoDB-backed data store
*/
export declare class MongoServerDao
{
/**
* Saves a quote to the database
*
* A full save will include all metadata.
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
* @param save_data - quote data to save (optional)
*/
saveQuote(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
save_data: Record<string, any>,
): this;
/**
* Merges bucket data with the existing bucket (rather than overwriting the
* entire bucket)
*
* @param quote - quote to save
* @param data - bucket data
* @param success - successful callback
* @param failure - failure callback
*/
mergeBucket(
quote: ServerSideQuote,
data: Record<string, any>,
success: Callback,
failure: Callback,
): this;
/**
* Save quote classification data
*
* @param quote - quote to save
* @param classes - classification data
* @param success - successful callback
* @param failure - failure callback
*/
saveQuoteClasses(
quote: ServerSideQuote,
classes: ClassificationData,
success: Callback,
failure: Callback,
): this;
/**
* Saves the quote state to the database
*
* The quote state includes the current step, the top visited step and the
* explicit lock message.
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
*/
saveQuoteState(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
): this;
/**
* Saves the quote lock state to the database
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
*/
saveQuoteLockState(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
): this
/**
* Save worksheet data
*
* @param qid - quote identifier
* @param data - worksheet data
* @param callback - callback
*/
setWorksheets(
qid: QuoteId,
data: WorksheetData,
callback: NodeCallback<void>,
): this;
/**
* Retrieve worksheet data
*
* @param qid - quote identifier
* @param supplier - supplier id
* @param index - worksheet index
* @param callback - callback
*/
getWorksheet(
qid: QuoteId,
supplier: string,
index: PositiveInteger,
callback: ( data: WorksheetData | null ) => void,
): this;
}

45
src/server/log/PriorityLog.d.ts vendored 100644
View File

@ -0,0 +1,45 @@
/**
* Priority log typescript type definitions
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export declare interface PriorityLog
{
readonly PRIORITY_ERROR: number;
readonly PRIORITY_IMPORTANT: number;
readonly PRIORITY_DB: number;
readonly PRIORITY_INFO: number;
readonly PRIORITY_SOCKET: number;
/**
* Write to the log at the given priority
*
* If the priority is less than or equal to the set priority for this
* object, it will be logged. Otherwise, the message will be ignored.
*
* The first argument should be the priority. The remaining arguments should
* be provided in a sprintf()-style fashion
*
* @param priority - logging priority
* @param ...args - sprintf-style aruments
*
* @return self
*/
log( priority: number, ...args: Array<string|number> ): this;
}

View File

@ -0,0 +1,46 @@
/**
* Augments a quote with additional data for use by the quote server
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @todo Use ``document'' terminology in place of ``quote''
*/
import { Program } from "../../program/Program";
import { BaseQuote } from "../../quote/BaseQuote";
export declare class ServerSideQuote extends BaseQuote
{
/**
* Last rated date, if any
*
* @return last rated date
*/
getRatedDate(): UnixTimestamp;
/**
* Set the timestamp of the first time quote was rated
*
* @param timestamp - Unix timestamp representing first rated date
*
* @return self
*/
setRatedDate( timestamp: UnixTimestamp ): this;
}

View File

@ -0,0 +1,35 @@
/**
* Rating process manager
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Rater } from "./Rater";
export declare class ProcessManager
{
/**
* Returns the rater associated with the given id
*
* @param id - rater id
*
* @return requested rater
*/
byId( id: string ): Rater;
}

View File

@ -69,6 +69,9 @@ const _signum = {
*
* Handles formatting and sending requests to the rating process; and
* processing replies.
*
* TODO: Rename this class and provide a more generic interface. The caller
* does not care that this sends data to another process for rating.
*/
module.exports = Class( 'ProcessManager',
{

121
src/server/rater/Rater.d.ts vendored 100644
View File

@ -0,0 +1,121 @@
/**
* Contains Rater interface
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClientActions } from "../../client/action/ClientAction";
import { ServerSideQuote } from "../quote/ServerSideQuote";
import { UserSession } from "../request/UserSession";
/** Result of rating */
export interface RateResult
{
/** Whether all suppliers were not able to provide rates */
_unavailable_all: '0' | '1';
/** Result data */
[P: string]: any;
}
/**
* Result of rating with an individual supplier
*
* This gets combined into a single RateResult prefixed with each supplier
* id and an underscore.
*/
export interface SupplierRateResult
{
/** Rating worksheet data */
__worksheet?: WorksheetData,
/** Classification system results */
__classes?: ClassificationData,
/** Basic profiling data */
__perf: PerformanceData,
/** Ineligible message, if any */
ineligible: string,
/** Submit message, if any */
submit: string,
/** Final premium */
premium: number,
/** Rating data */
[P: string]: any;
}
/** Basic profiling data */
export interface PerformanceData
{
/** Timestamp of beginning of rating */
start: UnixTimestampMillis;
/** Timestamp of end of rating */
end: UnixTimestampMillis;
/** Total rating time */
total: Milliseconds;
}
/**
* Worksheet data from rater
*
* These data come from the compiled raters.
*
* TODO: Fill in a schema here
*/
export type WorksheetData = Record<string, any>;
/** Classification results */
export type ClassificationData = Record<string, boolean>;
/**
* Represents a rater that will generate a quote from a given set of values
*/
export interface Rater
{
/**
* Asynchronously performs rating
*
* @param quote - quote to perform rating on
* @param session - user session
* @param indv - individual supplier to rate (otherwise empty)
* @param success - continuation when rating is successful
* @param failure - continuation when rating fails
*
* @return self
*/
rate(
quote: ServerSideQuote,
session: UserSession,
indv: string,
success: ( rate_data: RateResult, actions: ClientActions ) => void,
failure: ( message: string ) => void,
): this;
}

View File

@ -0,0 +1,36 @@
/**
* User request abstraction
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { UserSession } from "./UserSession";
/**
* Representation of request from user
*/
export declare class UserRequest
{
/**
* Retrieve the current session
*
* @return current session
*/
getSession(): UserSession;
}

View File

@ -0,0 +1,28 @@
/**
* UserResponse class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Manipulates response to user request
*/
export declare class UserResponse
{
}

View File

@ -0,0 +1,34 @@
/**
* UserSession class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Session management
*/
export declare class UserSession
{
/**
* Whether the user is logged in as an internal user
*
* @return true if internal user, otherwise false
*/
isInternal(): boolean;
}

View File

@ -19,33 +19,60 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class;
import { ClientActions } from "../../client/action/ClientAction";
import { MongoServerDao } from "../db/MongoServerDao";
import { PositiveInteger } from "../../numeric";
import { PriorityLog } from "../log/PriorityLog";
import { ProcessManager } from "../rater/ProcessManager";
import { Program } from "../../program/Program";
import { QuoteId } from "../../quote/Quote";
import { ClassificationData, RateResult, WorksheetData } from "../rater/Rater";
import { Server } from "../Server";
import { ServerSideQuote } from "../quote/ServerSideQuote";
import { UserRequest } from "../request/UserRequest";
import { UserResponse } from "../request/UserResponse";
type RequestCallback = () => void;
/**
* XXX: Half-assed, quick refactoring to extract from Server class; this is not
* yet complete!
* Handle rating requests
*
* XXX: This class was extracted from Server and needs additional
* refactoring, testing, and cleanup.
*
* TODO: Logging should be implemented by observers
*/
module.exports = Class( 'RatingService',
export class RatingService
{
logger: null,
dao: null,
_server: null,
_raters: null,
/**
* Initialize rating service
*
* @param _logger - logging system
* @param _dao - database connection
* @param _server - server actions
* @param _rater_manager - rating manager
*/
constructor(
private readonly _logger: PriorityLog,
private readonly _dao: MongoServerDao,
private readonly _server: Server,
private readonly _rater_manager: ProcessManager,
) {}
__construct: function( logger, dao, server, raters )
/**
* TODO: Remove once traits subtypes are converted to TS
*
* This works around an easejs bug where prototype constructors are not
* properly invoked. Note that technically the constructor above is
* invoked twice by easejs: once with no arguments, and again when
* calling this method with the proper arguments.
*/
__construct()
{
this._logger = logger;
this._dao = dao;
this._server = server;
this._raters = raters;
},
(<any>RatingService ).apply( this, arguments );
}
/**
@ -54,15 +81,21 @@ module.exports = Class( 'RatingService',
* Note that the continuation will be called after all data saving is
* complete; the request will be sent back to the client before then.
*
* @param {UserRequest} request user request to satisfy
* @param {UserResponse} response pending response
* @param {Quote} quote quote to export
* @param {string} cmd applicable of command request
* @param {Function} callback continuation after saving is complete
* @param request - user request to satisfy
* @param _response - pending response
* @param quote - quote to export
* @param cmd - applicable of command request
* @param callback - continuation after saving is complete
*
* @return Server self to allow for method chaining
*/
'public request': function( request, response, quote, cmd, callback )
request(
request: UserRequest,
_response: UserResponse,
quote: ServerSideQuote,
cmd: string,
callback: RequestCallback
)
{
// cmd represents a request for a single rater
if ( !cmd && this._isQuoteValid( quote ) )
@ -89,12 +122,12 @@ module.exports = Class( 'RatingService',
}
return this;
},
}
_getProgramRater: function( program, quote )
private _getProgramRater( program: Program, quote: ServerSideQuote )
{
var rater = this._raters.byId( program.getId() );
var rater = this._rater_manager.byId( program.getId() );
// if a rater could not be found, we can't do any rating
if ( rater === null )
@ -107,10 +140,20 @@ module.exports = Class( 'RatingService',
}
return rater;
},
}
_isQuoteValid: function( quote )
/**
* Whether quote is still valid
*
* TODO: This class shouldn't be making this determination, and this
* method is nondeterministic.
*
* @param quote - quote to check
*
* @return whether quote is still valid
*/
private _isQuoteValid( quote: ServerSideQuote ): boolean
{
// quotes are valid for 30 days
var re_date = Math.round( ( ( new Date() ).getTime() / 1000 ) -
@ -129,14 +172,19 @@ module.exports = Class( 'RatingService',
}
return false;
},
}
_performRating: function( request, program, quote, indv, c )
private _performRating(
request: UserRequest,
program: Program,
quote: ServerSideQuote,
indv: string,
c: RequestCallback,
)
{
var _self = this;
var rater = this._getProgramRater( program, quote );
var rater = this._getProgramRater( program );
if ( !rater )
{
this._server.sendError( request, 'Unable to perform rating.' );
@ -150,49 +198,51 @@ module.exports = Class( 'RatingService',
);
rater.rate( quote, request.getSession(), indv,
function( rate_data, actions )
( rate_data: RateResult, actions: ClientActions ) =>
{
actions = actions || [];
_self.postProcessRaterData(
this.postProcessRaterData(
request, rate_data, actions, program, quote
);
const class_dest = {};
const cleaned = _self._cleanRateData(
const cleaned = this._cleanRateData(
rate_data,
class_dest
);
// TODO: move me during refactoring
_self._dao.saveQuoteClasses( quote, class_dest );
this._dao.saveQuoteClasses(
quote, class_dest, () => {}, () => {}
);
// save all data server-side (important: do after
// post-processing); async
_self._saveRatingData( quote, rate_data, indv, function()
this._saveRatingData( quote, rate_data, indv, function()
{
// we're done
c();
} );
// no need to wait for the save; send the response
_self._server.sendResponse( request, quote, {
this._server.sendResponse( request, quote, {
data: cleaned,
initialRatedDate: quote.getRatedDate(),
lastRatedDate: quote.getLastPremiumDate()
}, actions );
},
function( message )
( message: string ) =>
{
_self._sendRatingError( request, quote, program,
this._sendRatingError( request, quote, program,
Error( message )
);
c();
}
);
},
}
/**
@ -202,32 +252,32 @@ module.exports = Class( 'RatingService',
* this is to allow us to reference the data (e.g. for reporting) even if
* the client does not save it.
*
* @param {Quote} quote quote to save data to
* @param {Object} data rating data
*
* @return {undefined}
* @param quote - quote to save data to
* @param data - rating data
* @param indv - individual supplier, or empty
* @param c - callback
*/
_saveRatingData: function( quote, data, indv, c )
private _saveRatingData(
quote: ServerSideQuote,
data: RateResult,
indv: string,
c: RequestCallback
): void
{
// only update the last premium calc date on the initial request
if ( !indv )
{
var cur_date = Math.round(
var cur_date = <UnixTimestamp>Math.round(
( new Date() ).getTime() / 1000
);
quote.setLastPremiumDate( cur_date );
quote.setRatedDate( cur_date );
function done()
{
c();
}
// save the last prem status (we pass an empty object as the save
// data argument to ensure that we do not save the actual bucket
// data, which may cause a race condition with the below merge call)
this._dao.saveQuote( quote, done, done, {} );
this._dao.saveQuote( quote, c, c, {} );
}
else
{
@ -239,24 +289,26 @@ module.exports = Class( 'RatingService',
// user a rate (if this save fails, it's likely we have bigger problems
// anyway); this can also be done concurrently with the above request
// since it only modifies a portion of the bucket
this._dao.mergeBucket( quote, data );
},
this._dao.mergeBucket( quote, data, () => {}, () => {} );
}
/**
* Process rater data returned from a rater
*
* @param {UserRequest} request user request to satisfy
* @param {Object} data rating data returned
* @param {Array} actions actions to send to client
* @param {Program} program program used to perform rating
* @param {Quote} quote quote used for rating
*
* @return {undefined}
* @param _request - user request to satisfy
* @param data - rating data returned
* @param actions - actions to send to client
* @param program - program used to perform rating
* @param quote - quote used for rating
*/
'virtual protected postProcessRaterData': function(
request, data, actions, program, quote
)
protected postProcessRaterData(
_request: UserRequest,
data: RateResult,
actions: ClientActions,
program: Program,
quote: ServerSideQuote,
): void
{
var meta = data._cmpdata || {};
@ -284,18 +336,18 @@ module.exports = Class( 'RatingService',
// have a race condition with async. rating (the /visit request may
// be made while we're rating, and when we come back we would then
// update the step id with a prior, incorrect step)
this._dao.saveQuoteLockState( quote );
this._dao.saveQuoteLockState( quote, () => {}, () => {} );
}
// if any have been deferred, instruct the client to request them
// individually
if ( Array.isArray( meta.deferred ) && ( meta.deferred.length > 0 ) )
{
var torate = [];
var torate: string[] = [];
meta.deferred.forEach( function( alias )
meta.deferred.forEach( ( alias: string ) =>
{
actions.push( { action: 'indvRate', id: alias } );
actions.push( { action: 'indvRate', after: alias } );
torate.push( alias );
} );
@ -308,17 +360,29 @@ module.exports = Class( 'RatingService',
torate.join( ',' )
);
}
},
}
_sendRatingError: function( request, quote, program, err )
/**
* Send rating error to user and log
*
* @param request - user request to satisfy
* @param quote - problem quote
* @param err - error
*/
private _sendRatingError(
request: UserRequest,
quote: ServerSideQuote,
program: Program,
err: Error,
): void
{
// well that's no good
this._logger.log( this._logger.PRIORITY_ERROR,
"Rating for quote %d (program %s) failed: %s",
quote.getId(),
program.getId(),
err.message + '\n-!' + err.stack.replace( /\n/g, '\n-!' )
err.message + '\n-!' + ( err.stack || "" ).replace( /\n/g, '\n-!' )
);
this._server.sendError( request,
@ -328,18 +392,25 @@ module.exports = Class( 'RatingService',
// show details for internal users
( ( request.getSession().isInternal() )
? '<br /><br />[Internal] ' + err.message + '<br /><br />' +
'<hr />' + err.stack.replace( /\n/g, '<br />' )
'<hr />' + ( err.stack || "" ).replace( /\n/g, '<br />' )
: ''
)
);
},
}
_processWorksheetData: function( qid, data )
/**
* Process and save worksheet data from rating results
*
* @param qid - quote id
* @param data - rating result
*/
private _processWorksheetData( qid: QuoteId, data: RateResult ): void
{
// TODO: this should be done earlier on, so that this is not necessary
var wre = /^(.+)___worksheet$/,
worksheets = {};
const wre = /^(.+)___worksheet$/;
const worksheets: Record<string, WorksheetData> = {};
// extract worksheets for each supplier
for ( var field in data )
@ -354,33 +425,44 @@ module.exports = Class( 'RatingService',
}
}
var _self = this;
this._dao.setWorksheets( qid, worksheets, function( err )
this._dao.setWorksheets( qid, worksheets, ( err: Error | null ) =>
{
if ( err )
{
_self._logger.log( this._logger.PRIORITY_ERROR,
this._logger.log( this._logger.PRIORITY_ERROR,
"Failed to save rating worksheets for quote %d",
quote.getId(),
err.message + '\n-!' + err.stack.replace( /\n/g, '\n-!' )
qid,
err.message + '\n-!' + ( err.stack || "" ).replace( /\n/g, '\n-!' )
);
}
} );
},
}
serveWorksheet: function( request, quote, supplier, index )
/**
* Serve worksheet data to user
*
* @param request - user request to satisfy
* @param quote - quote from which to look up worksheet data
* @param supplier - supplier name
* @param index - worksheet index
*/
serveWorksheet(
request: UserRequest,
quote: ServerSideQuote,
supplier: string,
index: PositiveInteger,
): void
{
var qid = quote.getId(),
_self = this;
var qid = quote.getId();
this._dao.getWorksheet( qid, supplier, index, function( data )
this._dao.getWorksheet( qid, supplier, index, data =>
{
_self._server.sendResponse( request, quote, {
this._server.sendResponse( request, quote, {
data: data
} );
} );
},
}
/**
@ -389,15 +471,18 @@ module.exports = Class( 'RatingService',
* There are certain data saved server-side that there is no use serving to
* the client.
*
* @param {Object} data rate data
* @param data - rate data
* @param classes - classification data
*
* @return {Object} modified rate data
* @return modified rate data
*/
'private _cleanRateData': function( data, classes )
private _cleanRateData(
data: RateResult,
classes: ClassificationData
): RateResult
{
classes = classes || {};
var result = {};
// forceful cast because the below loop will copy everything
const result = <RateResult>{};
// clear class data
for ( var key in data )
@ -415,6 +500,5 @@ module.exports = Class( 'RatingService',
}
return result;
},
} );
}
}

View File

@ -21,8 +21,8 @@
'use strict';
const { Trait } = require( 'easejs' );
const RatingService = require( './RatingService' );
const { Interface, Trait } = require( 'easejs' );
const { RatingService } = require( './RatingService' );
/**
@ -51,7 +51,8 @@ const RatingService = require( './RatingService' );
* See the body of `#_sendMessage' for their values.
*/
module.exports = Trait( 'RatingServicePublish' )
.extend( RatingService,
.implement( Interface( { 'postProcessRaterData': [] } ) )
.extend(
{
/**
* AMQP library (amqplib API)
@ -75,7 +76,7 @@ module.exports = Trait( 'RatingServicePublish' )
*
* @type {DebugLog}
*/
'private _logger': null,
'private _log': null,
/**
@ -87,9 +88,9 @@ module.exports = Trait( 'RatingServicePublish' )
*/
__mixin( amqp, conf, logger )
{
this._amqp = amqp;
this._conf = conf;
this._logger = logger;
this._amqp = amqp;
this._conf = conf;
this._log = logger;
},
@ -104,7 +105,7 @@ module.exports = Trait( 'RatingServicePublish' )
*
* @return {undefined}
*/
'override protected postProcessRaterData'(
'abstract override postProcessRaterData'(
request, data, actions, program, quote
)
{
@ -127,13 +128,13 @@ module.exports = Trait( 'RatingServicePublish' )
quote
);
} )
.then( () => this._logger.log(
this._logger.PRIORITY_INFO,
.then( () => this._log.log(
this._log.PRIORITY_INFO,
"Published quote " + quote.getId() +
" to post-rate exchange '" + exchange + "'"
) )
.catch( e => this._logger.log(
this._logger.PRIORITY_ERROR,
.catch( e => this._log.log(
this._log.PRIORITY_ERROR,
"Post-rate exchange publish failure for quote " +
quote.getId() + ": " + e.message
) );

View File

@ -19,10 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict'
import { expect } from 'chai';
import { RatingService as Sut } from "../../../src/server/service/RatingService";
const { expect } = require( 'chai' );
const Sut = require( '../../../' ).server.service.RatingService;
const RatingServiceStub = require( '../../../' ).test.server.service.RatingServiceStub;
describe( 'RatingService', () =>
@ -49,15 +48,13 @@ describe( 'RatingService', () =>
done();
};
const sut = Sut.extend(
const sut = new class extends Sut
{
'override postProcessRaterData'(
request, data, actions, program, quote
)
postProcessRaterData()
{
processed = true;
}
} )( logger, dao, server, raters );
}( logger, dao, server, raters );
sut.request( request, response, quote, 'something', () => {} );
} );
@ -87,9 +84,9 @@ describe( 'RatingService', () =>
quote.getRatedDate = () => initial_date;
const sut = Sut( logger, dao, server, raters );
const sut = new Sut( logger, dao, server, raters );
server.sendResponse = ( request, quote, resp, actions ) =>
server.sendResponse = ( _request: any, _quote: any, resp: any, _actions: any ) =>
{
expect( getLastPremiumDateCallCount ).to.equal( 2 );
expect( resp.initialRatedDate ).to.equal( initial_date );
@ -98,7 +95,7 @@ describe( 'RatingService', () =>
done();
};
sut.request( request, response, quote, null, () => {} );
sut.request( request, response, quote, "", () => {} );
} );
} );