From 03df71cc5f7f2ab6ed640be27e8281b08e9660c1 Mon Sep 17 00:00:00 2001 From: Radek Jajko Date: Wed, 27 Jan 2021 13:26:53 +0100 Subject: [PATCH 1/5] Fix Bug related to PLC Notification page. The save method has been redefined. It was designed from strach to be more flexible and better respond for user changes. #1517 --- doc/EventsAPI.yaml | 23 + scadalts-ui/src/store/alarms/notifications.js | 144 ++---- .../src/views/AlarmNotifications/README.md | 7 +- .../src/views/AlarmNotifications/index.vue | 447 +++++++++--------- .../AlarmNotifications.spec.js | 2 - src/org/scada_lts/dao/event/EventDAO.java | 19 +- .../scada_lts/mango/service/EventService.java | 4 + .../web/mvc/api/EventHandlerAPI.java | 16 + 8 files changed, 323 insertions(+), 339 deletions(-) diff --git a/doc/EventsAPI.yaml b/doc/EventsAPI.yaml index 18304dc9eb..0950e82134 100644 --- a/doc/EventsAPI.yaml +++ b/doc/EventsAPI.yaml @@ -90,6 +90,29 @@ paths: schema: $ref: '#/components/schemas/EventHandlerVO' headers: {} + /eventHandler/get/plc/datapoint/{id}: + get: + tags: + - EventHandler API + summary: 'Get Event Handler by DataPoint ID' + description: 'Get Event Handler by Data point ID' + operationId: 'eventHandlerGetByDataPointId' + parameters: + - name: 'id' + in: 'path' + required: true + schema: + type: 'number' + responses: + '200': + description: "Get successful" + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EventHandlerPlcDTO' + headers: {} /eventHandler/set/{typeId}/{typeRef1}/{typeRef2}/1: post: tags: diff --git a/scadalts-ui/src/store/alarms/notifications.js b/scadalts-ui/src/store/alarms/notifications.js index e0b740494f..54d01834f3 100644 --- a/scadalts-ui/src/store/alarms/notifications.js +++ b/scadalts-ui/src/store/alarms/notifications.js @@ -38,18 +38,28 @@ const storeAlarmsNotifications = { }, //Event Handlers - getPlcEventHandlers({ dispatch }) { - return dispatch('requestGet', '/eventHandler/getAllPlc'); + _getPlcEventHandlerById({ dispatch }, eventHandlerId) { + return dispatch('requestGet', `/eventHandler/get/id/${eventHandlerId}`); }, - getPlcEventHandlerById({ dispatch }, eventHandlerId) { - return dispatch('requestGet', `/eventHandler/get/id/${eventHandlerId}`); + getPlcDataPointConfiguration({ dispatch }, datapointId) { + return dispatch('requestGet', `/eventHandler/get/plc/datapoint/${datapointId}`); }, deleteEventHandler({ dispatch }, eventHandlerId) { return dispatch('requestDelete', `/eventHandler/delete/id/${eventHandlerId}`); }, + async updateEventHandlerV2({ dispatch }, payload) { + let eventHandler = await dispatch('_getPlcEventHandlerById', payload.id); + eventHandler.activeRecipients = payload.recipients; + + return dispatch('requestPut', { + url: `/eventHandler/update/1/${payload.eventTypeRef1}/${payload.eventTypeRef2}`, + data: eventHandler, + }); + }, + //Mailing Lists getAllMailingLists({ dispatch }) { return dispatch('requestGet', '/mailingList/getAll'); @@ -70,73 +80,15 @@ const storeAlarmsNotifications = { deleteEventDetector({ dispatch }, payload) { return dispatch( 'requestDelete', - `/eventDetector/delete/${payload.dpId}/${payload.edId}`, + `/eventDetector/delete/${payload.datapointId}/${payload.pointEventDetectorId}`, ); }, - deleteMailingListFromEventHandler(context, payload) { - payload.eventHandler.activeRecipients = payload.eventHandler.activeRecipients.filter( - (e) => { - return e.referenceId !== payload.activeMailingList; - }, - ); - return payload.eventHandler; - }, - - addMailingListToEventHandler(context, payload) { - if (!payload.eventHandler.activeRecipients) { - payload.eventHandler.activeRecipients = []; - } - let mailingList = { - recipientType: 1, - referenceId: payload.activeMailingList, - referenceAddress: null, - }; - payload.eventHandler.activeRecipients.push(mailingList); - console.log(payload.eventHandler); - return payload.eventHandler; - }, - - async updateEventHandler({ dispatch }, payload) { - let eventHandler = await dispatch('getPlcEventHandlerById', payload.ehId); - - if(eventHandler.handlerType !== payload.handlerType) { - let createData = { - datapointId: payload.typeRef1, - mailingListId: payload.activeMailingList, - handlerType: payload.handlerType, - }; - return await dispatch('createEventHandler', createData); - } - - if (payload.method === 'add') { - eventHandler = await dispatch('addMailingListToEventHandler', { - eventHandler: eventHandler, - activeMailingList: payload.activeMailingList, - }); - } else if (payload.method === 'delete') { - eventHandler = await dispatch('deleteMailingListFromEventHandler', { - eventHandler: eventHandler, - activeMailingList: payload.activeMailingList, - }); - } - - if (eventHandler.activeRecipients.length === 0) { - let ehStatus = await dispatch('deleteEventHandler', eventHandler.id); - return ehStatus; - } else { - return dispatch('requestPut', { - url: `/eventHandler/update/1/${payload.typeRef1}/${payload.typeRef2}`, - data: eventHandler, - }); - } - }, - async createEventHandler({ state, dispatch }, payload) { let pedId = await dispatch('createPointEventDetector', payload.datapointId); let edId = pedId.id; let dpId = payload.datapointId; - let mlId = payload.mailingListId; + let mlId = payload.mailingListId[0]; let requestData = JSON.parse(JSON.stringify(state.ehTemplate)); let type = payload.handlerType === 2 ? 'mail' : 'sms'; @@ -151,6 +103,16 @@ const storeAlarmsNotifications = { referenceAddress: null, }, ]; + if (payload.dual) { + console.log('CREATE-DUAL'); + recipientList.push({ + recipientType: 1, + referenceId: payload.mailingListId[1], + referenceAddress: null, + }); + } else { + console.log('CREATE-SINGLE'); + } requestData.activeRecipients = recipientList; let eventHandler; @@ -163,50 +125,18 @@ const storeAlarmsNotifications = { } catch (error) { throw 'POST request failed!'; } - return { edId, ehId: eventHandler.id }; + let response = { + eventTypeId: 1, + eventTypeRef1: dpId, + eventTypeRef2: edId, + id: eventHandler.id, + xid: eventHandler.xid, + alias: eventHandler.alias, + handlerType: eventHandler.handlerType, + recipients: eventHandler.activeRecipients, + }; + return response; }, - - async createDualEventHandler({state, dispatch}, payload) { - let pedId = await dispatch('createPointEventDetector', payload.datapointId); - let edId = pedId.id; - let dpId = payload.datapointId; - let mlId = payload.mailingListId; - - let requestDataMail = JSON.parse(JSON.stringify(state.ehTemplate)); - let requestDataSms = JSON.parse(JSON.stringify(state.ehTemplate)); - - requestDataMail.xid = 'EH_' + requestDataMail.xid + `_mail_${dpId}_${edId}_${mlId}`; - requestDataMail.alias = requestDataMail.alias + `_mail_${dpId}_${edId}_${mlId}`; - - requestDataSms.xid = 'EH_' + requestDataSms.xid + `_sms_${dpId}_${edId}_${mlId}`; - requestDataSms.alias = requestDataSms.alias + `_sms_${dpId}_${edId}_${mlId}`; - - let recipientList = [ - { - recipientType: 1, - referenceId: mlId, - referenceAddress: null, - }, - ]; - - requestDataMail.activeRecipients = recipientList; - requestDataSms.activeRecipients = recipientList; - let eventHandler; - - try { - eventHandler = await dispatch('requestPost', { - url: `/eventHandler/set/1/${dpId}/${edId}/2`, - data: requestDataMail, - }); - eventHandler = await dispatch('requestPost', { - url: `/eventHandler/set/1/${dpId}/${edId}/5`, - data: requestDataSms, - }); - } catch (error) { - throw 'POST request failed!'; - } - return { edId, ehId: eventHandler.id }; - } }, getters: {}, diff --git a/scadalts-ui/src/views/AlarmNotifications/README.md b/scadalts-ui/src/views/AlarmNotifications/README.md index 4e87fc28f1..8f5ac64c16 100644 --- a/scadalts-ui/src/views/AlarmNotifications/README.md +++ b/scadalts-ui/src/views/AlarmNotifications/README.md @@ -8,7 +8,6 @@ New component for preparing a Notification Configuration that allow Scada-LTS us - Create a mailing list and assign users to it. _(if you want to receive notification about Events that was taking place druring inactive period select **Collect inactive msg** and set up **cron** pattern for example like this one "0 */1 * \* \* ?" that will try to send past emails every one minute)_ - - Create **alarm** ( AL )_ or **warning** ( ST )_ **BINARY** datapoints that will handle information about specific event. _\*(it could be any name of data point but must contain this uppercase expresion AL|ST with spaces around them. For example "Test AL Datapoint" or "Point-0 ST ". Underscores are not allowed - here are examples that are not valid: "Test_AL\_", "DP ST" or "Point-0_AL")_ @@ -28,13 +27,13 @@ New component for preparing a Notification Configuration that allow Scada-LTS us - In EventHandlers page check that valid Event Handlers has been created. ### Important notes + - It works only for BINARY data points. (Other types are not suppeorted) - When event detector exisit the new one Event Handler tries to attach to it. - Event detector templates works only when event detector for data ponit do not exists. - Scada use Mail2SMS function. It does not send pure SMS but Mail formatted as SMS. -It requires addtional SMS Gateway Server that will handle and proceed text message. -Server addess can be changed using System Settings page. - + It requires addtional SMS Gateway Server that will handle and proceed text message. + Server addess can be changed using System Settings page. ## Known bugs diff --git a/scadalts-ui/src/views/AlarmNotifications/index.vue b/scadalts-ui/src/views/AlarmNotifications/index.vue index 006fb61776..f6525e9a30 100644 --- a/scadalts-ui/src/views/AlarmNotifications/index.vue +++ b/scadalts-ui/src/views/AlarmNotifications/index.vue @@ -102,6 +102,14 @@ From 95a849361d570286f2255b5a7cc667de35d0799b Mon Sep 17 00:00:00 2001 From: Radek Jajko Date: Thu, 28 Jan 2021 16:03:27 +0100 Subject: [PATCH 4/5] Add some UnitTests to AlarmNotifications #1517 --- .../AlarmNotifications.spec.js | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/scadalts-ui/tests/unit/AlarmNotifications/AlarmNotifications.spec.js b/scadalts-ui/tests/unit/AlarmNotifications/AlarmNotifications.spec.js index c1393b4923..51a719c9f9 100644 --- a/scadalts-ui/tests/unit/AlarmNotifications/AlarmNotifications.spec.js +++ b/scadalts-ui/tests/unit/AlarmNotifications/AlarmNotifications.spec.js @@ -4,7 +4,7 @@ import Vuex from 'vuex'; import Vuetify from '@/plugins/vuetify'; import { expect } from 'chai'; -import { createLocalVue, mount } from '@vue/test-utils'; +import { config, createLocalVue, mount } from '@vue/test-utils'; import AlarmNotifications from '@/views/AlarmNotifications'; import i18n from '@/i18n'; @@ -91,4 +91,90 @@ describe('PLC Alarms Notification Tests', () => { expect(wrapper.vm.items.length).to.equal(1); }); }); + + it('Test getEventHandler', () => { + let configuration = [ + { + id: 1, + xid: "EH_MAIL_TEST", + alias: "MAIL_TEST_HANDLER", + handlerType: 2, + eventTypeId: 1, + eventTypeRef1: 1, + eventTypeRef2: 1, + recipients: [ + { + recipientType: 1, + referenceId: 1, + referenceAddress: null + } + ] + }, + { + id: 2, + xid: "EH_SMS_TEST", + alias: "MAIL_SMS_HANDLER", + handlerType: 5, + eventTypeId: 1, + eventTypeRef1: 1, + eventTypeRef2: 1, + recipients: [ + { + recipientType: 1, + referenceId: 1, + referenceAddress: null + } + ] + } + ] + let x = wrapper.vm.getEventHandler(configuration, 2); + expect(x).to.equal(configuration[0]) + x = wrapper.vm.getEventHandler(configuration, 5); + expect(x).to.equal(configuration[1]) + configuration = configuration.filter(e => { return e.id !== 2}) + x = wrapper.vm.getEventHandler(configuration, 5); + expect(x).to.equal(null); + + }) + + it('Test saveDatapoint', () => { + wrapper.vm.items = [ + {id: 1, name: 'DS', children: [ + {id:1, name: 'DP', configuration: [ + ], mail: [ + {active: false, config: true, handler: 1, mlId: 1}, + {active: true, config: true, handler: 1, mlId: 2}, + ], sms:[ + {active: false, config: false, handler: 2, mlId: 1}, + {active: true, config: true, handler: 2, mlId: 2}, + ]} + ]} + ] + + const config = [ + { + id: 1, + xid: "EH_MAIL_TEST", + alias: "MAIL_TEST_HANDLER", + handlerType: 2, + eventTypeId: 1, + eventTypeRef1: 1, + eventTypeRef2: 1, + recipients: [ + { + recipientType: 1, + referenceId: 1, + referenceAddress: null + } + ] + }] + + wrapper.vm.saveDatapoint(1, config) + + expect(wrapper.vm.items[0].children[0].mail[0].config).to.equal(false); + expect(wrapper.vm.items[0].children[0].configuration).to.equal(config); + + }) + + }); From 6e1bee56742474f763de52d4190a1e01005e5437 Mon Sep 17 00:00:00 2001 From: Radek Jajko Date: Fri, 29 Jan 2021 16:01:36 +0100 Subject: [PATCH 5/5] Change from Synchronized to AtomicInteger #1517 --- .../scada_lts/web/mvc/api/EventDetectorAPI.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/org/scada_lts/web/mvc/api/EventDetectorAPI.java b/src/org/scada_lts/web/mvc/api/EventDetectorAPI.java index 1904eab4e9..3871fcd569 100644 --- a/src/org/scada_lts/web/mvc/api/EventDetectorAPI.java +++ b/src/org/scada_lts/web/mvc/api/EventDetectorAPI.java @@ -21,6 +21,8 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; /** * Controller for EventDetector @@ -32,7 +34,7 @@ public class EventDetectorAPI { private static final Log LOG = LogFactory.getLog(EventDetectorAPI.class); - private List eventDetectorsList = Collections.synchronizedList(new ArrayList<>()); + private static AtomicInteger atomicInteger = new AtomicInteger(0); @Resource private DataPointService dataPointService; @@ -127,7 +129,6 @@ private ResponseEntity createEventDetectorType(int datap DataPointVO dataPointVO = dataPointService.getDataPoint(datapointId); PointEventDetectorVO pointEventDetectorVO = body.createPointEventDetectorVO(dataPointVO); JsonPointEventDetector jsonPointEventDetector = createEventDetector(dataPointVO, pointEventDetectorVO); - eventDetectorsList.remove(body.getXid()); return new ResponseEntity<>(jsonPointEventDetector, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); @@ -150,12 +151,15 @@ private JsonPointEventDetector createEventDetector(DataPointVO dataPointVO, Poin } dataPointVO.getEventDetectors().add(pointEventDetectorVO); dataPointService.saveEventDetectors(dataPointVO); - synchronized (eventDetectorsList) { - if(!eventDetectorsList.contains(pointEventDetectorVO.getXid())) { + if(atomicInteger.getAndDecrement() == 0) { + try { Common.ctx.getRuntimeManager().saveDataPoint(dataPointVO); - eventDetectorsList.add(pointEventDetectorVO.getXid()); + } finally { + atomicInteger.set(0); } } + + int pedID = dataPointService.getDetectorId(pointEventDetectorVO.getXid(), dataPointVO.getId()); return new JsonPointEventDetector(pedID, pointEventDetectorVO.getXid(), pointEventDetectorVO.getAlias()); }