Newer
Older
const {
BaseKonnector,
log,
errors,
addData,
hydrateAndFilter,
const moment = require('moment')
require('moment-timezone')
moment.locale('fr') // set the language
moment.tz.setDefault('Europe/Paris') // set the timezone
const Sentry = require('@sentry/node')
// eslint-disable-next-line
const Tracing = require('@sentry/tracing') // Needed for tracking performance in Sentry
const { version } = require('../package.json')
const { isDev } = require('./helpers/env')
process.env.COZY_JOB_MANUAL_EXECUTION === 'true' ? true : false
doctype: 'com.grandlyon.egl.day',
keys: ['year', 'month', 'day'],
doctype: 'com.grandlyon.egl.month',
keys: ['year', 'month'],
doctype: 'com.grandlyon.egl.year',
keys: ['year'],
},
}
module.exports = new BaseKonnector(start)
/**
* Sentry
*/
Sentry.init({
dsn:
'https://3f97baf46c2b44c2bd9e0c371abe3e05@grandlyon.errors.cozycloud.cc/2',
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
release: version,
environment: isDev() ? 'development' : 'production',
debug: isDev(),
integrations: [
// enable HTTP calls tracing
new Sentry.Integrations.Http({ tracing: true }),
],
})
// The start function is run by the BaseKonnector instance only when it got all the account
// information (fields). When you run this connector yourself in 'standalone' mode or 'dev' mode,
// the account information come from ./konnector-dev-config.json file
async function start(fields, cozyParameters) {
const transaction = Sentry.startTransaction({
op: 'konnector',
name: 'EGL Konnector',
})
transaction.startChild({ op: 'Konnector starting' })
// Local debug data
// const baseUrl = fields.eglBaseURL
// const apiAuthKey = fields.eglAPIAuthKey
const baseUrl = cozyParameters.secret.eglBaseURL
const apiAuthKey = cozyParameters.secret.eglAPIAuthKey
log('info', 'Authenticating ...')
const response = await authenticate(
fields.login,
fields.password,
baseUrl,
apiAuthKey
const eglData = await getData(response, baseUrl, apiAuthKey)
const processedLoadData = await processData(
eglData,
rangeDate.day.doctype,
rangeDate.day.keys
)
log('debug', 'Aggregate egl load data for month and year')
await aggregateMonthAndYearData(processedLoadData)
transaction.setStatus(Tracing.SpanStatus.Ok)
transaction.finish()
const errorMessage = `EGL konnector encountered an error. Response data: ${JSON.stringify(
error.message
)}`
Sentry.captureMessage(errorMessage, {
tags: {
section: 'start',
},
})
transaction.setStatus(Tracing.SpanStatus.Aborted)
transaction.finish()
await Sentry.flush()
throw error
/**
* Parse data
* Remove existing data from DB using hydrateAndFilter
* Store filtered data
* Return the list of filtered data
*/
async function processData(data, doctype, filterKeys) {
// Remove data for existing days into the DB
const filteredData = await hydrateAndFilter(data, doctype, {
keys: filterKeys,
})
log('debug', 'processData - data filtered')
await storeData(filteredData, doctype, filterKeys)
return filteredData
* Aggregate data from daily data to monthly and yearly data
// Sum year and month values into object with year or year-month as keys
if (data && data.length !== 0) {
const monthDataKey = element.year + '-' + element.month
if (monthDataKey in monthData) {
monthData[monthDataKey] += element.load
} else {
monthData[monthDataKey] = element.load
}
const yearDataKey = element.year
if (yearDataKey in yearData) {
yearData[yearDataKey] += element.load
} else {
yearData[yearDataKey] = element.load
}
})
// Aggregation for Month data
const aggregatedMonthData = await buildAggregatedData(
'com.grandlyon.egl.month'
)
await storeData(aggregatedMonthData, 'com.grandlyon.egl.month', [
'year',
'month',
])
// Aggregation for Year data
const aggregatedYearData = await buildAggregatedData(
'com.grandlyon.egl.year'
)
await storeData(aggregatedYearData, 'com.grandlyon.egl.year', ['year'])
async function buildAggregatedData(data, doctype) {
log('info', 'entering buildAggregatedData')
let aggregatedData = []
const data = await buildDataFromKey(doctype, key, value)
const oldValue = await resetInProgressAggregatedData(data, doctype)
log('info', 'Data load + old value is ' + data.load + ' + ' + oldValue)
data.load += oldValue
aggregatedData.push(data)
async function authenticate(login, password, baseUrl, apiAuthKey) {
method: 'post',
url: baseUrl + '/connect.aspx',
'Content-Type': 'application/x-www-form-urlencoded',
const resp = await axios(authRequest)
if (resp.data.codeRetour === 100) {
return resp.data
} else if (resp.data.codeRetour === -4) {
throw new Error(errors.LOGIN_FAILED)
const errorMessage = `Authentication failed. Response data: ${resp?.data?.libelleRetour}`
log('error', errorMessage)
throw new Error(errors.VENDOR_DOWN)
Sentry.captureException(error, {
extra: {
compte: login,
},
}
}
async function getData(response, baseUrl, apiAuthKey) {
const dataRequest = {
method: 'post',
url: baseUrl + '/getAllAgregatsByAbonnement.aspx',
'Content-Type': 'application/x-www-form-urlencoded',
token: response.resultatRetour.token,
num_abt: response.resultatRetour.num_abt,
date_debut: startDate,
// Sort data by date
const resp = await axios(dataRequest)
resp.data.resultatRetour.sort(function(a, b) {
return new Date(a.DateReleve) - new Date(b.DateReleve)
log(
'error',
`Get data failed. codeRetour -2. ${resp.data.libelleRetour}`
log(
'error',
`Get data failed. codeRetour -1. ${resp.data.libelleRetour}`
log(
'error',
`Get data failed. ${resp.data.codeRetour}. ${resp.data.libelleRetour}`
log('debug', error.message)
Sentry.captureException(error, {
extra: {
start: startDate,
end: endDate,
},
if (axios.isAxiosError(error)) {
throw new Error(errors.VENDOR_DOWN)
}
throw error
log('info', 'origin response size is: ' + response.resultatRetour.length)
// Store first value as reference for index processing
// Create copy of data without first value
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
log('info', 'filtered size is: ' + data.length)
try {
return data.map(value => {
const time = moment(value.DateReleve, moment.ISO_8601)
const processedLoad = value.ValeurIndex - refValue.ValeurIndex
if (processedLoad < 0) {
const errorMessage = `Processing load error for day ${parseInt(
time.format('D')
)}/${parseInt(time.format('M'))}/${parseInt(
time.format('YYYY')
)}, value is: ${processedLoad}`
log('debug', errorMessage)
throw errors.VENDOR_DOWN
}
// Change index ref value
refValue = value
return {
load: processedLoad,
year: parseInt(time.format('YYYY')),
month: parseInt(time.format('M')),
day: parseInt(time.format('D')),
hour: 0,
minute: 0,
type: value.TypeAgregat,
}
})
} catch (error) {
log('debug', error.message)
Sentry.captureException(error, {
tags: {
section: 'format',
},
})
throw error
}
/**
* Save data in the right doctype db and prevent duplicated keys
*/
async function storeData(data, doctype, filterKeys) {
log('debug', 'Store into ' + doctype)
log('debug', 'Store into keys : ' + filterKeys)
const filteredDocuments = await hydrateAndFilter(data, doctype, {
keys: filterKeys,
})
return await addData(filteredDocuments, doctype)
* For year doctype: key = 'YYYY'
* For month doctype: key = 'YYYY-MM'
let year, month, day, hour
if (doctype === 'com.grandlyon.egl.year') {
year = key
month = 1
day = 0
hour = 0
} else if (doctype === 'com.grandlyon.egl.month') {
const split = key.split('-')
year = split[0]
month = split[1]
day = 0
hour = 0
const split = key.split('-')
year = split[0]
month = split[1]
day = split[2]
hour = split[3]
}
return {
load: Math.round(value * 10000) / 10000,
year: parseInt(year),
month: parseInt(month),
day: parseInt(day),
hour: parseInt(hour),
/**
* Function handling special case.
* The temporary aggregated data need to be remove in order for the most recent one te be saved.
* { load: 76.712, month: 2020, ... } need to be replace by
// /!\ Warning: cannot use mongo queries because not supported for dev by cozy-konnectors-libs
log('debug', 'Remove aggregated data for ' + doctype)
const result = await cozyClient.data.findAll(doctype)
let filtered = []
if (doctype === 'com.grandlyon.egl.year') {
// Yearly case
filtered = result.filter(function(el) {
return el.year == data.year
})
} else if (doctype === 'com.grandlyon.egl.month') {
return el.year == data.year && el.month == data.month
})
filtered = result.filter(function(el) {
return (
el.year == data.year &&
el.month == data.month &&
el.day == data.day &&
el.hour == data.hour
sum += doc.load
log('debug', 'Removing this entry for ' + doc.load)
await cozyClient.data.delete(doctype, doc)