Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • web-et-numerique/factory/llle_project/egl-konnector
1 result
Show changes
Commits on Source (13)
node_modules
build
\ No newline at end of file
{
"extends": ["cozy-app", "plugin:prettier/recommended"],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"parser": "flow"
}
]
}
}
...@@ -21,7 +21,7 @@ include: ...@@ -21,7 +21,7 @@ include:
- template: Security/SAST.gitlab-ci.yml - template: Security/SAST.gitlab-ci.yml
unit-test: unit-test:
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:16.14.2-alpine3.14 image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:20.12.0-alpine3.19
stage: test stage: test
before_script: before_script:
- apk add git - apk add git
...@@ -36,7 +36,7 @@ unit-test: ...@@ -36,7 +36,7 @@ unit-test:
- junit.xml - junit.xml
build-dev: build-dev:
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:16.14.2-alpine3.14 image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:20.12.0-alpine3.19
stage: build stage: build
before_script: before_script:
- apk add git - apk add git
...@@ -45,15 +45,15 @@ build-dev: ...@@ -45,15 +45,15 @@ build-dev:
- yarn - yarn
- yarn build - yarn build
- git config --global user.name build-token - git config --global user.name build-token
- git config --global user.email "$GIT_USER" - git config --global user.email build-token
- git config --global user.password "$GIT_PWD" - git config --global user.password "$BUILD_TOKEN"
- git config user.email "$GIT_USER" - git config user.email build-token
- git remote set-url origin https://"$GIT_USER":"$GIT_PWD"@forge.grandlyon.com/web-et-numerique/factory/llle_project/egl-konnector.git - git remote set-url origin https://build-token:"$BUILD_TOKEN"@forge.grandlyon.com/web-et-numerique/factory/llle_project/egl-konnector.git
- git config --global credential.helper store - git config --global credential.helper store
- yarn deploy-dev - yarn deploy-dev
build: build:
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:16.14.2-alpine3.14 image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:20.12.0-alpine3.19
stage: build stage: build
before_script: before_script:
- apk add git - apk add git
...@@ -62,10 +62,10 @@ build: ...@@ -62,10 +62,10 @@ build:
- yarn - yarn
- yarn build - yarn build
- git config --global user.name build-token - git config --global user.name build-token
- git config --global user.email "$GIT_USER" - git config --global user.email build-token
- git config --global user.password "$GIT_PWD" - git config --global user.password "$BUILD_TOKEN"
- git config user.email "$GIT_USER" - git config user.email build-token
- git remote set-url origin https://"$GIT_USER":"$GIT_PWD"@forge.grandlyon.com/web-et-numerique/factory/llle_project/egl-konnector.git - git remote set-url origin https://build-token:"$BUILD_TOKEN"@forge.grandlyon.com/web-et-numerique/factory/llle_project/egl-konnector.git
- git config --global credential.helper store - git config --global credential.helper store
- yarn deploy - yarn deploy
only: only:
......
v20.12.0
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
"tabWidth": 2, "tabWidth": 2,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "avoid" "arrowParens": "avoid"
} }
\ No newline at end of file
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.3.0](https://forge.grandlyon.com/web-et-numerique/llle_project/egl-konnector/compare/v1.2.5...v1.3.0) (2024-09-30)
### Features
* **SAU:** Add the instance name as a Sentry tag ([e15e17c](https://forge.grandlyon.com/web-et-numerique/llle_project/egl-konnector/commit/e15e17c89cb55c56ef0de4b8b68b3341d712e651))
* update prices ([f8c4c15](https://forge.grandlyon.com/web-et-numerique/llle_project/egl-konnector/commit/f8c4c15632e9f112585bae983426731f04bcc61c))
### Bug Fixes
* **data:** aborts if no data ([28ef855](https://forge.grandlyon.com/web-et-numerique/llle_project/egl-konnector/commit/28ef855892e5c9e49cb1e09ceea5c1899a49a192))
### [1.2.5](https://forge.grandlyon.com/web-et-numerique/llle_project/egl-konnector/compare/v1.2.4...v1.2.5) (2024-02-28) ### [1.2.5](https://forge.grandlyon.com/web-et-numerique/llle_project/egl-konnector/compare/v1.2.4...v1.2.5) (2024-02-28)
......
{
"presets": [
[
"cozy-app",
{
"node": true,
"react": false,
"lib": true
}
]
]
}
{ {
"COZY_URL": "http://cozy.tools:8080/", "COZY_URL": "http://cozy.tools:8080/",
"fields": { "fields": {
"eglBaseURL": "",
"eglAPIAuthKey": "",
"login": 1234567, "login": 1234567,
"password": "" "password": ""
},
"COZY_PARAMETERS": {
"secret": {
"eglBaseURL": "",
"eglAPIAuthKey": "",
"boBaseUrl": "https://ecolyo-agent-rec.apps.grandlyon.com/"
}
} }
} }
\ No newline at end of file
{ {
"version": "1.2.5", "version": "1.3.0",
"name": "EGL", "name": "EGL",
"type": "konnector", "type": "konnector",
"language": "node", "language": "node",
......
{ {
"name": "egl", "name": "egl",
"version": "1.2.5", "version": "1.3.0",
"description": "", "description": "",
"repository": { "repository": {
"type": "git", "type": "git",
...@@ -10,58 +10,33 @@ ...@@ -10,58 +10,33 @@
"author": "Grand Lyon", "author": "Grand Lyon",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./src/index.js", "main": "./src/index.js",
"eslintConfig": {
"extends": [
"cozy-app"
]
},
"eslintIgnore": [
"build"
],
"husky": {
"hooks": {
"pre-commit": "yarn lint"
}
},
"scripts": { "scripts": {
"start": "node ./src/index.js", "start": "node ./src/index.js",
"dev": "cozy-konnector-dev", "dev": "cozy-konnector-dev",
"standalone": "cozy-konnector-standalone", "standalone": "cozy-konnector-standalone",
"lint": "eslint src --fix",
"pretest": "npm run clean", "pretest": "npm run clean",
"check": "konitor check .",
"test": "jest", "test": "jest",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"clean": "rm -rf ./data", "clean": "rm -rf ./data",
"build": "webpack", "build": "webpack",
"lint": "eslint --fix .",
"deploy": "git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build}", "deploy": "git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build}",
"deploy-dev": "git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build-dev}", "deploy-dev": "git-directory-deploy --directory build/ --branch ${DEPLOY_BRANCH:-build-dev}",
"cozyPublish": "cozy-app-publish --token $REGISTRY_TOKEN --build-commit $(git rev-parse ${DEPLOY_BRANCH:-build})", "cozyPublish": "cozy-app-publish --token $REGISTRY_TOKEN --build-commit $(git rev-parse ${DEPLOY_BRANCH:-build})",
"travisDeployKey": "./bin/generate_travis_deploy_key" "travisDeployKey": "./bin/generate_travis_deploy_key"
}, },
"dependencies": { "dependencies": {
"@sentry/node": "7.30.0", "@sentry/node": "8.8.0",
"@sentry/tracing": "7.30.0", "@sentry/tracing": "7.114.0",
"axios": "1.2.2", "axios": "1.7.2",
"cozy-konnector-libs": "4.56.4", "cozy-konnector-libs": "^5.11.0",
"jest": "^29.7.0",
"luxon": "^3.4.3" "luxon": "^3.4.3"
}, },
"devDependencies": { "devDependencies": {
"copy-webpack-plugin": "6.1.1", "cozy-jobs-cli": "^2.4.2",
"cozy-app-publish": "0.25.0", "cozy-konnector-build": "^1.7.0",
"cozy-jobs-cli": "1.20.2", "jest": "^29.7.0",
"cozy-konnector-build": "1.4.4",
"eslint": "5.16.0",
"eslint-config-cozy-app": "1.6.0",
"eslint-plugin-prettier": "3.0.1",
"git-directory-deploy": "1.5.1",
"husky": "4.3.0",
"jest-junit": "^16.0.0", "jest-junit": "^16.0.0",
"konitor": "0.10.2", "standard-version": "^9.5.0"
"standard-version": "^9.5.0",
"svgo": "1.3.2",
"webpack": "5.75.0",
"webpack-cli": "5.0.1"
} }
} }
...@@ -14,7 +14,7 @@ function format(data) { ...@@ -14,7 +14,7 @@ function format(data) {
log('info', 'origin response size is: ' + data.length) log('info', 'origin response size is: ' + data.length)
/** Filter loads where value is 0 */ /** Filter loads where value is 0 */
const filteredData = data.filter(value => value.ValeurIndex !== 0) const filteredData = data.filter(value => value.ValeurIndex !== 0)
let formattedLoads = [] const formattedLoads = []
for (let i = 1; i < filteredData.length; i++) { for (let i = 1; i < filteredData.length; i++) {
const previousValue = filteredData[i - 1] const previousValue = filteredData[i - 1]
const currentValue = filteredData[i] const currentValue = filteredData[i]
......
const { format } = require('../../src/helpers/format') const { format } = require('./format')
const mockCaptureMessage = jest.fn() const mockCaptureMessage = jest.fn()
jest.mock('@sentry/node', () => ({ jest.mock('@sentry/node', () => ({
......
const { log } = require('cozy-konnector-libs')
const axios = require('axios').default
const Sentry = require('@sentry/node')
const { DateTime } = require('luxon')
require('../types/types')
/**
* @param {string} boBaseUrl
*/
async function getPrices(boBaseUrl) {
const boWaterPricesUrl = new URL('/api/common/prices/1', boBaseUrl).href
try {
/** @type {Price[]} */
const prices = (await axios.get(boWaterPricesUrl)).data
return prices
} catch (error) {
log('error', 'Could not fetch BO prices')
Sentry.captureException(error, {
tags: {
section: 'getPrices',
},
})
return null
}
}
/**
* Apply the given prices to the given data array and return the result.
* @param {FormattedData[]} data
* @param {Price[]} fluidPrices
*/
async function applyPrices(data, fluidPrices) {
// Sort prices by descending start date
fluidPrices.sort(
(a, b) =>
DateTime.fromISO(b.startDate, { zone: 'UTC' }).toMillis() -
DateTime.fromISO(a.startDate, { zone: 'UTC' }).toMillis()
)
return data.map(load => {
// Select the first price that is before the load date
const loadDate = DateTime.fromObject(
{
year: load.year,
month: load.month,
day: load.day,
},
{ zone: 'UTC' }
)
const fluidPrice = fluidPrices.find(p => {
const startDate = DateTime.fromISO(p.startDate, { zone: 'UTC' })
return loadDate >= startDate
})
if (!fluidPrice) return load
return { ...load, price: fluidPrice.price * load.load }
})
}
module.exports = { getPrices, applyPrices }
const axios = require('axios')
const { getPrices, applyPrices } = require('../../src/helpers/prices')
jest.mock('axios')
jest.mock('cozy-konnector-libs', () => ({
log: jest.fn(),
}))
describe('getPrices', () => {
const boBaseUrl = 'https://example.com'
it('should return null when axios throws an error', async () => {
// Mock axios to throw an error
axios.get.mockRejectedValue(new Error('Network Error'))
const result = await getPrices(boBaseUrl)
expect(result).toBeNull()
})
})
describe('applyPrices', () => {
it('should apply prices to data', async () => {
const data = [
{ year: 2024, month: 1, day: 31, hour: 0, minute: 0, load: 10 },
{ year: 2024, month: 2, day: 1, hour: 0, minute: 0, load: 10 },
{ year: 2024, month: 2, day: 2, hour: 0, minute: 0, load: 10 },
]
const prices = [
{ startDate: '2024-01-31T00:00:00Z', price: 1 },
{ startDate: '2024-02-01T00:00:00Z', price: 2 },
{ startDate: '2024-02-02T00:00:00Z', price: 3 },
]
const result = await applyPrices(data, prices)
expect(result).toStrictEqual([
{
year: 2024,
month: 1,
day: 31,
hour: 0,
minute: 0,
load: 10,
price: 10,
},
{ year: 2024, month: 2, day: 1, hour: 0, minute: 0, load: 10, price: 20 },
{ year: 2024, month: 2, day: 2, hour: 0, minute: 0, load: 10, price: 30 },
])
})
it('should not apply prices if data is before prices', async () => {
const data = [
{ year: 2020, month: 1, day: 14, hour: 0, minute: 0, load: 10 },
]
const prices = [{ startDate: '2024-01-14T00:00:00Z', price: 1 }]
const result = await applyPrices(data, prices)
expect(result).toStrictEqual([
{ year: 2020, month: 1, day: 14, hour: 0, minute: 0, load: 10 },
])
})
it('should not apply prices if prices are empty', async () => {
const data = [
{ year: 2024, month: 1, day: 14, hour: 0, minute: 0, load: 10 },
]
const prices = []
const result = await applyPrices(data, prices)
expect(result).toStrictEqual([
{ year: 2024, month: 1, day: 14, hour: 0, minute: 0, load: 10 },
])
})
})
...@@ -13,12 +13,13 @@ function aggregateMonthlyLoad(data) { ...@@ -13,12 +13,13 @@ function aggregateMonthlyLoad(data) {
const monthlyLoad = {} const monthlyLoad = {}
for (const entry of data) { for (const entry of data) {
const { year, month, load } = entry const { year, month, load, price } = entry
const monthKey = `${year}-${month}` const monthKey = `${year}-${month}`
if (!monthlyLoad[monthKey]) { if (!monthlyLoad[monthKey]) {
monthlyLoad[monthKey] = { monthlyLoad[monthKey] = {
load: load, load: load,
price: price,
year: year, year: year,
month: month, month: month,
day: 0, day: 0,
...@@ -28,6 +29,7 @@ function aggregateMonthlyLoad(data) { ...@@ -28,6 +29,7 @@ function aggregateMonthlyLoad(data) {
} }
} else { } else {
monthlyLoad[monthKey].load += load monthlyLoad[monthKey].load += load
monthlyLoad[monthKey].price += price
} }
} }
...@@ -45,11 +47,12 @@ function aggregateYearlyLoad(data) { ...@@ -45,11 +47,12 @@ function aggregateYearlyLoad(data) {
const yearlyLoad = {} const yearlyLoad = {}
for (const entry of data) { for (const entry of data) {
const { year, load } = entry const { year, load, price } = entry
if (!yearlyLoad[year]) { if (!yearlyLoad[year]) {
yearlyLoad[year] = { yearlyLoad[year] = {
load: load, load: load,
price: price,
year: year, year: year,
month: 0, month: 0,
day: 0, day: 0,
...@@ -59,6 +62,7 @@ function aggregateYearlyLoad(data) { ...@@ -59,6 +62,7 @@ function aggregateYearlyLoad(data) {
} }
} else { } else {
yearlyLoad[year].load += load yearlyLoad[year].load += load
yearlyLoad[year].price += price
} }
} }
......
require('./instrument.js') // Sentry initialization
const { const {
BaseKonnector, BaseKonnector,
log, log,
...@@ -10,9 +11,6 @@ const axios = require('axios').default ...@@ -10,9 +11,6 @@ const axios = require('axios').default
const { DateTime } = require('luxon') const { DateTime } = require('luxon')
const Sentry = require('@sentry/node') const Sentry = require('@sentry/node')
const Tracing = require('@sentry/tracing') // Needed for tracking performance in Sentry
const { version } = require('../package.json')
const { isDev } = require('./helpers/env')
const { rangeDate } = require('./types/constants') const { rangeDate } = require('./types/constants')
const { const {
aggregateMonthlyLoad, aggregateMonthlyLoad,
...@@ -21,6 +19,7 @@ const { ...@@ -21,6 +19,7 @@ const {
filterFirstYearlyLoad, filterFirstYearlyLoad,
} = require('./helpers/utils') } = require('./helpers/utils')
const { format } = require('./helpers/format') const { format } = require('./helpers/format')
const { getPrices, applyPrices } = require('./helpers/prices.js')
require('./types/types') require('./types/types')
const manualExecution = process.env.COZY_JOB_MANUAL_EXECUTION === 'true' const manualExecution = process.env.COZY_JOB_MANUAL_EXECUTION === 'true'
...@@ -39,26 +38,6 @@ const endDateString = DateTime.now() ...@@ -39,26 +38,6 @@ const endDateString = DateTime.now()
module.exports = new BaseKonnector(start) module.exports = new BaseKonnector(start)
/**
* Sentry configuration
*/
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 receives all the account information (fields). * The start function is run by the BaseKonnector instance only when it receives all the account information (fields).
* When you run this connector in 'standalone' or 'dev' mode, the account information comes from the ./konnector-dev-config.json file. * When you run this connector in 'standalone' or 'dev' mode, the account information comes from the ./konnector-dev-config.json file.
...@@ -67,65 +46,71 @@ Sentry.init({ ...@@ -67,65 +46,71 @@ Sentry.init({
* @param {object} cozyParameters - Cozy platform parameters. * @param {object} cozyParameters - Cozy platform parameters.
*/ */
async function start(fields, cozyParameters) { async function start(fields, cozyParameters) {
const transaction = Sentry.startTransaction({
op: 'konnector',
name: 'EGL Konnector',
})
transaction.startChild({ op: 'Konnector starting' })
try { try {
// Local debug data const eglBaseUrl = cozyParameters.secret.eglBaseURL
// const baseUrl = fields.eglBaseURL const boBaseUrl = cozyParameters.secret.boBaseUrl
// const apiAuthKey = fields.eglAPIAuthKey
const baseUrl = cozyParameters.secret.eglBaseURL
const apiAuthKey = cozyParameters.secret.eglAPIAuthKey const apiAuthKey = cozyParameters.secret.eglAPIAuthKey
log('info', 'Authenticating ...') log('info', 'Authenticating ...')
const response = await authenticate( const response = await authenticate(
fields.login, fields.login,
fields.password, fields.password,
baseUrl, eglBaseUrl,
apiAuthKey apiAuthKey
) )
log('info', 'Successfully logged in') log('info', 'Successfully logged in')
const eglData = await getData(response, baseUrl, apiAuthKey) let eglData = await getData(response, eglBaseUrl, apiAuthKey)
if (eglData.length === 0) {
if (!eglData) {
log('debug', 'No data found') log('debug', 'No data found')
transaction.setStatus(Tracing.SpanStatus.Ok)
transaction.finish()
return return
} }
log('info', 'Fetching BO prices')
const prices = await getPrices(boBaseUrl)
if (prices && prices.length > 0) {
log('info', 'Found BO prices, applying them to EGL data')
eglData = await applyPrices(eglData, prices)
}
log('debug', 'Process EGL daily data') log('debug', 'Process EGL daily data')
const filteredDays = await hydrateAndFilter( const filterDayKeys = [...rangeDate.day.keys, 'load']
if (prices) filterDayKeys.push('price')
const daysToUpdate = await hydrateAndFilter(
eglData, eglData,
rangeDate.day.doctype, rangeDate.day.doctype,
{ { keys: filterDayKeys }
keys: ['year', 'month', 'day', 'load'],
}
) )
log('debug', 'Store EGL daily load data') log('debug', 'Store EGL daily load data')
await updateOrCreate( await updateOrCreate(
filteredDays, daysToUpdate,
rangeDate.day.doctype, rangeDate.day.doctype,
rangeDate.day.keys rangeDate.day.keys
) )
const { year: firstYear, month: firstMonth } = eglData[0] const { year: firstYear, month: firstMonth } = eglData[0]
log('debug', 'Aggregate EGL yearly load data') log('debug', 'Aggregate EGL monthly load data')
const monthlyLoads = aggregateMonthlyLoad(eglData) const monthlyLoads = aggregateMonthlyLoad(eglData)
log('debug', 'Filter first month aggregate if already in database') log('debug', 'Filter first month aggregate if already in database')
const filteredMonthlyLoads = filterFirstMonthlyLoad( const filteredMonthlyLoads = await filterFirstMonthlyLoad(
firstMonth, firstMonth,
firstYear, firstYear,
monthlyLoads monthlyLoads
) )
const filterMonthKeys = [...rangeDate.month.keys, 'load']
if (prices) filterMonthKeys.push('price')
const monthsToUpdate = await hydrateAndFilter(
filteredMonthlyLoads,
rangeDate.month.doctype,
{ keys: filterMonthKeys }
)
log('debug', 'Store aggregated EGL monthly load data') log('debug', 'Store aggregated EGL monthly load data')
await updateOrCreate( await updateOrCreate(
filteredMonthlyLoads, monthsToUpdate,
rangeDate.month.doctype, rangeDate.month.doctype,
rangeDate.month.keys rangeDate.month.keys
) )
...@@ -134,17 +119,25 @@ async function start(fields, cozyParameters) { ...@@ -134,17 +119,25 @@ async function start(fields, cozyParameters) {
const yearlyLoads = aggregateYearlyLoad(monthlyLoads) const yearlyLoads = aggregateYearlyLoad(monthlyLoads)
log('debug', 'Filter first year aggregate if already in database') log('debug', 'Filter first year aggregate if already in database')
const filteredYearlyLoads = filterFirstYearlyLoad(firstYear, yearlyLoads) const filteredYearlyLoads = await filterFirstYearlyLoad(
firstYear,
yearlyLoads
)
const filterYearKeys = [...rangeDate.year.keys, 'load']
if (prices) filterYearKeys.push('price')
const yearsToUpdate = await hydrateAndFilter(
filteredYearlyLoads,
rangeDate.year.doctype,
{ keys: filterYearKeys }
)
log('debug', 'Store aggregated EGL yearly load data') log('debug', 'Store aggregated EGL yearly load data')
await updateOrCreate( await updateOrCreate(
filteredYearlyLoads, yearsToUpdate,
rangeDate.year.doctype, rangeDate.year.doctype,
rangeDate.year.keys rangeDate.year.keys
) )
transaction.setStatus(Tracing.SpanStatus.Ok)
transaction.finish()
} catch (error) { } catch (error) {
const errorMessage = `EGL konnector encountered an error. Response data: ${JSON.stringify( const errorMessage = `EGL konnector encountered an error. Response data: ${JSON.stringify(
error.message error.message
...@@ -152,10 +145,9 @@ async function start(fields, cozyParameters) { ...@@ -152,10 +145,9 @@ async function start(fields, cozyParameters) {
Sentry.captureMessage(errorMessage, { Sentry.captureMessage(errorMessage, {
tags: { tags: {
section: 'start', section: 'start',
login: fields.login,
}, },
}) })
transaction.setStatus(Tracing.SpanStatus.Aborted)
transaction.finish()
await Sentry.flush() await Sentry.flush()
throw error throw error
} }
...@@ -173,45 +165,55 @@ async function start(fields, cozyParameters) { ...@@ -173,45 +165,55 @@ async function start(fields, cozyParameters) {
* @returns {Promise<AuthResponse>} - The authentication response containing a token. * @returns {Promise<AuthResponse>} - The authentication response containing a token.
*/ */
async function authenticate(login, password, baseUrl, apiAuthKey) { async function authenticate(login, password, baseUrl, apiAuthKey) {
log('info', 'Authenticating ...') return Sentry.startSpan(
const authRequest = { {
method: 'post', name: 'authenticate',
url: baseUrl + '/connect.aspx',
headers: {
AuthKey: apiAuthKey,
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
login: login,
pass: password,
}, },
} async span => {
log('info', 'Authenticating ...')
const authRequest = {
method: 'post',
url: baseUrl + '/connect.aspx',
headers: {
AuthKey: apiAuthKey,
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
login: login,
pass: password,
},
}
try { try {
/** @type {AuthResponse} */ /** @type {AuthResponse} */
const respData = (await axios(authRequest)).data const respData = (await axios(authRequest)).data
if (respData.codeRetour === 100) { if (respData.codeRetour === 100) {
return respData return respData
} }
const errorMessage = `Authentication failed. Response data: ${respData?.libelleRetour}` const errorMessage = `Authentication failed. Response data: ${respData?.libelleRetour}`
log('error', errorMessage) log('error', errorMessage)
throw new Error(errors.VENDOR_DOWN) log('error', `Code retour: ${respData?.codeRetour}`)
} catch (error) { throw new Error(errors.VENDOR_DOWN)
log('error', error.response?.data) } catch (error) {
Sentry.captureException(error, { log('error', error.response?.data)
tags: { Sentry.captureException(error, {
section: 'authenticate', tags: {
}, section: 'authenticate',
extra: { },
compte: login, extra: {
}, compte: login,
}) },
if (error.response?.data.codeRetour === -4) { })
throw new Error(errors.LOGIN_FAILED) if (error.response?.data.codeRetour === -4) {
span.setStatus({ code: 2, message: 'unauthenticated' })
throw new Error(errors.LOGIN_FAILED)
}
span.setStatus({ code: 2, message: 'internal_error' })
throw new Error(errors.VENDOR_DOWN)
}
} }
throw new Error(errors.VENDOR_DOWN) )
}
} }
/** /**
...@@ -225,66 +227,74 @@ async function authenticate(login, password, baseUrl, apiAuthKey) { ...@@ -225,66 +227,74 @@ async function authenticate(login, password, baseUrl, apiAuthKey) {
* @returns {Promise<FormattedData[]>} - A promise that resolves to the retrieved and formatted data. * @returns {Promise<FormattedData[]>} - A promise that resolves to the retrieved and formatted data.
*/ */
async function getData(response, baseUrl, apiAuthKey) { async function getData(response, baseUrl, apiAuthKey) {
const dataRequest = { return Sentry.startSpan(
method: 'post', {
url: baseUrl + '/getAllAgregatsByAbonnement.aspx', name: 'getData',
headers: {
AuthKey: apiAuthKey,
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
token: response.resultatRetour.token,
num_abt: response.resultatRetour.num_abt,
date_debut: startDateString,
date_fin: endDateString,
}, },
} async span => {
const dataRequest = {
method: 'post',
url: baseUrl + '/getAllAgregatsByAbonnement.aspx',
headers: {
AuthKey: apiAuthKey,
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
token: response.resultatRetour.token,
num_abt: response.resultatRetour.num_abt,
date_debut: startDateString,
date_fin: endDateString,
},
}
try { try {
/** @type {GetDataResponse} */ /** @type {GetDataResponse} */
const respData = (await axios(dataRequest)).data const respData = (await axios(dataRequest)).data
switch (respData.codeRetour) { switch (respData.codeRetour) {
case 100: case 100:
// Sort data by date // Sort data by date
respData.resultatRetour.sort(function(a, b) { respData.resultatRetour.sort(
return new Date(a.DateReleve) - new Date(b.DateReleve) (a, b) => new Date(a.DateReleve) - new Date(b.DateReleve)
)
return format(respData.resultatRetour)
case -2:
log(
'error',
`Get data failed. codeRetour -2. ${respData.libelleRetour}`
)
throw errors.LOGIN_FAILED
case -1:
log(
'error',
`Get data failed. codeRetour -1. ${respData.libelleRetour}`
)
throw errors.VENDOR_DOWN
default:
log(
'error',
`Get data failed. ${respData.codeRetour}. ${respData.libelleRetour}`
)
log('error', respData)
throw errors.UNKNOWN_ERROR
}
} catch (error) {
log('debug', error.message)
Sentry.captureException(error, {
tags: {
section: 'getData',
},
extra: {
start: startDateString,
end: endDateString,
},
}) })
return format(respData.resultatRetour) span.setStatus({ code: 2, message: 'internal_error' })
case -2: if (axios.isAxiosError(error)) {
log( throw new Error(errors.VENDOR_DOWN)
'error', }
`Get data failed. codeRetour -2. ${respData.libelleRetour}` throw error
) }
throw errors.LOGIN_FAILED
case -1:
log(
'error',
`Get data failed. codeRetour -1. ${respData.libelleRetour}`
)
throw errors.VENDOR_DOWN
default:
log(
'error',
`Get data failed. ${respData.codeRetour}. ${respData.libelleRetour}`
)
throw errors.UNKNOWN_ERROR
}
} catch (error) {
log('debug', error.message)
Sentry.captureException(error, {
tags: {
section: 'getData',
},
extra: {
start: startDateString,
end: endDateString,
},
})
if (axios.isAxiosError(error)) {
throw new Error(errors.VENDOR_DOWN)
} }
throw error )
}
} }
const Sentry = require('@sentry/node')
const { version } = require('../package.json')
const { isDev } = require('./helpers/env')
Sentry.init({
dsn: 'https://3f97baf46c2b44c2bd9e0c371abe3e05@grandlyon.errors.cozycloud.cc/2',
tracesSampleRate: 1.0,
release: version,
environment: isDev() ? 'development' : 'production',
debug: isDev(),
autoSessionTracking: true,
})
Sentry.setTag('instance', process.env.COZY_URL)
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
* @property {number} day - The day of the data point. * @property {number} day - The day of the data point.
* @property {number} hour - The hour of the data point (in this case, always 0). * @property {number} hour - The hour of the data point (in this case, always 0).
* @property {number} minute - The minute of the data point (in this case, always 0). * @property {number} minute - The minute of the data point (in this case, always 0).
* @property {number} price - The price of the data point.
* @property {string} type - The type of the data point. * @property {string} type - The type of the data point.
*/ */
...@@ -37,3 +38,11 @@ ...@@ -37,3 +38,11 @@
* @property {number} num_abt * @property {number} num_abt
* @property {string} token * @property {string} token
*/ */
/**
* @typedef {Object} Price
* @property {number} fluidtype
* @property {number} price
* @property {string} startDate
* @property {string} endDate
*/
var path = require('path') module.exports = require('cozy-konnector-build/webpack.config')
const CopyPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
const fs = require('fs')
const SvgoInstance = require('svgo')
const entry = require('./package.json').main
const readManifest = () =>
JSON.parse(fs.readFileSync(path.join(__dirname, './manifest.konnector')))
const svgo = new SvgoInstance({
plugins: [
{
inlineStyles: { onlyMatchedOnce: false },
},
],
})
let iconName
try {
iconName = JSON.parse(fs.readFileSync('manifest.konnector', 'utf8')).icon
// we run optimize only on SVG
if (!iconName.match(/\.svg$/)) iconName = null
} catch (e) {
// console.error(`Unable to read the icon path from manifest: ${e}`)
}
const appIconRX = iconName && new RegExp(`[^/]*/${iconName}`)
module.exports = {
entry,
target: 'node',
mode: 'none',
output: {
path: path.join(__dirname, 'build'),
filename: 'index.js',
},
plugins: [
new CopyPlugin({
patterns: [
{ from: 'manifest.konnector' },
{ from: 'package.json' },
{ from: 'README.md' },
{ from: 'assets', transform: optimizeSVGIcon },
{ from: '.travis.yml' },
{ from: 'LICENSE' },
],
}),
new webpack.DefinePlugin({
__WEBPACK_PROVIDED_MANIFEST__: JSON.stringify(readManifest()),
}),
],
module: {
// to ignore the warnings like :
// WARNING in ../libs/node_modules/bindings/bindings.js 76:22-40
// Critical dependency: the request of a dependency is an expression
// Since we cannot change this dependency. I think it won't hide more important messages
exprContextCritical: false,
},
}
function optimizeSVGIcon(buffer, path) {
if (appIconRX && path.match(appIconRX)) {
return svgo.optimize(buffer).then(resp => resp.data)
} else {
return buffer
}
}
This diff is collapsed.