Дипломная работа: Потоковая визуализация данных в системе автоматизации производств SEDMAX

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Ниже приведен пример тестового сценария для сквозного тестирования «Редактора наборов» с тестами для создания набора, удаления одного и удаления всех наборов:

describe('Редактор наборов', () => {

beforeEach(() => {

return cy.login();

});

it('Создание набора modbus', function() {

cy.visit('/sedmax/web/ui/sets_data_server/editor/index')

.wait(1000)

.getByText(/Создать набор/i)

.click({ force: true })

.wait(500)

.get('.ant-modal-content .ant-btn-primary')

.click();

});

it('Удаляем набор modbus', function() {

cy.visit('/sedmax/web/ui/sets_data_server/editor/index')

.wait(1000)

.get('.ant-table-row.ant-table-row-level-0 td')

.first()

.click()

.getByText(/Удалить выбранное/i)

.click({ force: true })

.get('.ant-modal-confirm-btns button.ant-btn.ant-btn-primary')

.click()

.wait(1000);

});

it('Удалить все наборы modbus', function() {

cy.request('POST', '/sedmax/configuration/data_server_set/create', {

protocolID: 2

});

cy.request('POST', '/sedmax/configuration/data_server_set/create', {

protocolID: 2

});

cy.visit('/sedmax/web/ui/sets_data_server/editor/index')

.wait(1000)

.get('thead.ant-table-thead input[type=checkbox]')

.click()

.getByText(/Удалить выбранное/i)

.click({ force: true })

.get('.ant-modal-confirm-btns button.ant-btn.ant-btn-primary')

.click()

.wait(1000);

});

});

Так же были написаны сквозные тесты для изменения настроек в наборах Modbus и МЭК-104.

Для Unit-тестирования использован фреймворк Jest - лидер тестовых библиотек, так как:

- умеет распараллеливать тестирование для ускорения процесса

- позволяет оценить покрытие кода

- имеет возможность имитировать любые объекты за пределами области действия теста (в виде тестовых данных)

- умеет анализировать типы появляющихся ошибок.

Пример модульного теста для проверки создания компонента модального окна «Создание набора» и его функционала приведен ниже:

import React from 'react';

import ModalCreateSetConnect, { ModalCreateSet } from '../ModalCreateSet';

import {

renderWithIntlAndRedux,

cleanup,

render,

intl

} from '../../../../tests/setupTests';

import { mockProtocols } from '../../../../tests/__mock__/classifiers.mock';

describe('ModalCreateSet', () => {

afterEach(cleanup);

it('should render a ModalCreateSet, visibility', () => {

// Arrange

const createSet = jest.fn();

const changeStateModalCreateSet = jest.fn();

const { container, getByText } = render(

<ModalCreateSet

set={{ id: 1, created: undefined }}

intl={intl}

isFetching={false}

modalState

protocols={[mockProtocols]}

createSet={createSet}

changeStateModalCreateSet={changeStateModalCreateSet}

/>

);

// Act

const submitButton = getByText('OK');

const cancelButton = getByText('Cancel');

submitButton.click();

cancelButton.click();

// Assert

expect(createSet).toHaveBeenCalledTimes(1);

expect(changeStateModalCreateSet).toHaveBeenCalledTimes(1);

expect(container).toMatchSnapshot();

});

it('should render a ModalCreateSet', () => {

const { container } = renderWithIntlAndRedux(<ModalCreateSetConnect />);

expect(container).toMatchSnapshot();

});

});

Где, mockProtocols это тестовые данные:

export const mockProtocols = {

id: 2,

code: '',

name: 'Modbus TCP',

state: 0

};

На рисунке 3.2 представлен процесс подготовки внесенных изменений к внедрению на тестовый сервер, но что-то пошло не так и была обнаружена ошибка на этапе Unit-тестирования.

Рисунок 3.2 - обнаружена ошибка при автоматическом тестировании.

3.2 Внедрение

Вся система обслуживается специальным сервисом GitLab. Это сайт и система управления проектами с возможностью ведения документации и системой отслеживания ошибок.

Разработчики используют распределенную систему контроля версий Git и с помощью нее они изменяют модули системы и заливают на сервер GitLab.

При этом когда изменения заливаются на сервер, происходит отработка тестов, сборка бандлов (организация и компилирование множества ресурсов и библиотек, необходимых для современного веб-проекта в минимизированный файл), и экспортирование на сервер и в зависимые репозитории.

На рисунке 3.3 представлено успешное выполнение всех этапов обновления модуля «Редактор наборов». В результате все изменения были перенесены на тестовый сервер.

Рисунок 3.3 - тесты, сборка бандла и обновление сервера успешно выполнены

После этапа тестирования на серверах SEDMAX, новые модули отправляются на тестирование к заказчику.

4. РАЗРАБОТКА МЕТОДИЧЕСКИХ УКАЗАНИЙ ПО РАБОТЕ С СИСТЕМОЙ

4.1 Виджеты

Для создания новых виджетов в правой части главного меню есть кнопка в виде глаза. При нажатии правой кнопкой мыши открывается контекстное меню со списком уже имеющихся виджетов, в котором нужно нажать «Добавить виджет». Меню представлено на рисунке 4.1.

Рисунок 4.1 - меню виджетов.

Откроется модальное окно создания нового виджета. Нужно ввести название нового виджета. Слева в дереве нужно выбрать устройства. Параметры выбранных устройств окажутся в таблице «Доступные параметры». В данной таблице доступен поиск по устройствам и тегам.

После выбора доступных параметров нужно нажать на кнопку «Переместить выбранное». В результате выбранные параметры окажутся в таблице «Выбранные параметры». В этой таблице можно воспользоваться поиском по всем полям таблицы и ввести псевдонимы для параметров.

После всех проделанных шагов нужно нажать на кнопку «Создать». Модальное окно представлено на рисунке 4.2.

Рисунок 4.2 - модальное окно создания нового виджета.

Теперь, вызвав контекстное меню, нажав на иконку глаза можно увидеть список всех виджетов, включить/отключить отображение конкретного виджета.

В списке виджетов можно удалить виджет или его редактировать при нажатии на соответствующие иконки.

При нажатии на иконку глаза левой кнопкой мыши можно включить и отключить глобальную видимость всех виджетов.

Созданные виджеты можно перетаскивать по всему экрану. Их параметры, позиции и видимость сохраняются для конкретного пользователя.

4.2 Паспорт и диспетчерские метки на мнемосхемах

Открытие паспорта и диспетчерских меток можно произвести по нажатию на элемент мнемосхемы правой кнопкой мыши. В результате откроется контекстное меню, в котором нужно выбрать соответствующие пункты.

В паспорте объекта есть информация о его параметрах. В таблице, в соответствующем поле можно отправить команду ручного ввода или сбросить его.

Паспорт представлен на рисунке 4.3.

Рисунок 4.3 - паспорт объекта в модуле мнемосхем

В окне диспетчерских меток можно установить или снять соответствующие метки и оставить комментарий к действию. На рисунке 4.4 представлено окно диспетчерских меток.

Рисунок 4.4 - паспорт и диспетчерские метки в мнемосхемах

4.3 Редактор наборов

Редактор наборов представляет таблица наборов, которые можно редактировать и удалять. Для создания нового набора нужно нажать на соответствующую кнопку в редакторе наборов. Появится модальное окно с выбором протокола для набора: Modbus или МЭК-104.

Модальное окно представлено на рисунке 4.5.

Рисунок 4.5 - модальное окно для выбора протокола для набора

На рисунке 4.6 представлен список наборов.

Рисунок 4.6 - список наборов

После создания набора, пользователь попадает в настройки созданного набора, где нужно выбрать устройства из дерева, выбрать параметры нужных устройств из «доступных параметров» и добавить их в «параметры набора», а также заполнить главные поля набора и дополнительные поля параметров набора. На рисунке 4.7 представлена «настройка набора».

Рисунок 4.7 - настройка набора

На рисунке 4.8 представлено модальное окно расширенных настроек для протокола МЭК-104.

Рисунок 4.8 - настройка набора

4.4 Анализ полноты данных

Для того чтобы воспользоваться нужным инструментом, нужно выбрать тип отчета: «часы в датах» или «даты в месяцах», далее устройство из дерева устройств и нужные даты в выпадающем календаре.

На рисунке 4.9 представлен календарь для выбора диапазона дат.

Рисунок 4.9 - выпадающий календарь для выбора нужных дат

При наведении курсора на конкретную ячейку диаграммы, появляется подсказка с датой и соответствующим ей значением.

Рисунок 4.10 - выпадающий календарь для выбора нужных дат

Все модули задокументированы и протестированы с помощью GitLab.

ЗАКЛЮЧЕНИЕ

Во ходе выполнения выпускной квалификационной работы был реализован функционал новых модулей и доработаны старые

Разработка на базе ReactJS, Redux, Golang, MongoDB позволила обеспечить:

- высокую надежность модулей;

- стабильность высоконагруженных модулей

- реализацию требуемого функционала;

- быструю скорость взаимодействия пользователя с приложением;

- читаемость кода.

В будущем планируется добавить поддержку новых протоколов для редактора наборов, переписать модуль «виджеты» под новые технологии и продолжить улучшать другие модули системы SEDMAX

В настоящий момент новые модули внедрены на тестовые серверы для апробации, дальнейшего развития и подготовке к отправке к заказчикам.

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

1. Бэнкс, А. И. React и Redux: функциональная веб-разработка / А. Бэнкс, Е. Порселло. - СПб.: Питер, 2019. - 336 с.

2. СТО ВоГУ-МУ.1-2019. Выпускная квалификационная работа. Требования к структуре, содержанию и оформлению: - Введ. 19.02.2019. - Вологда: Вогу, 2019. - 67 c.

3. Дж. Резиг, Б. Бибо. Секреты JavaScript ниндзя : Пер. с англ. - М. : ООО «И.Д. Вильямс», 2013. - 416с. : ил. - Парал. тит. англ.

ПРИЛОЖЕНИЕ

Запросы к Application Program Interface для модуля «Виджеты»

export function* requestCreateWidget(action) {

const { params } = action;

try {

const response = yield call(createWidget, params);

const { data } = response;

yield put(createWidgetResponse(fromJS(data)));

yield* requestWidgetsList();

} catch (error) {

const statusCode = error.response.status;

errorsHandling(statusCode, 'action');

yield put(createWidgetFailed(error));

}

}

export function* requestWidget(action) {

const { id } = action;

try {

const response = yield call(getWidget, id);

const { data } = response;

let selectedOptions;

if (data.parameters !== null) {

selectedOptions = data.parameters.map(parameter =>

`${parameter.object_id}_${parameter.device_id}_${parameter.ti_id}`

);

} else {

selectedOptions = new Set();

}

const dataModified = {

...data,

selectedOptions: new Set(selectedOptions),

selectedNodes: []

};

yield put(widgetResponse(fromJS(dataModified)));

}

catch (error) {

const statusCode = error.response.status;

errorsHandling(statusCode);

yield put(widgetFailed(error));

}

}

export function* requestUpdateWidget(action) {

const { id, params } = action;

try {

const response = yield call(updateWidget, id, params);

const { data } = response;

yield put(updateWidgetResponse(fromJS(data)));

yield* requestWidgetsList();

} catch (error) {

const statusCode = error.response.status;

errorsHandling(statusCode, 'action');

yield put(updateWidgetFailed(error));

}

}

export function* requestDeleteWidget(action) {

const { id } = action;

try {

const response = yield call(deleteWidget, id);

const { data } = response;

yield put(deleteWidgetResponse(fromJS(data)));

successfulNotification(locale, 'deleteWidget');

yield* requestWidgetsList();

} catch (error) {

const statusCode = error.response.status;

errorsHandling(statusCode, 'action');

yield put(deleteWidgetFailed(error));

}

}

Создание веб-сокета для модуля «Виджеты»

export function* watchSocket() {

const socket = yield call(createSocket);

const socketChannel = yield call(createSocketChannel, socket);

while (true) {

const payload = yield take(socketChannel);

if (payload === 'connection') {

yield* firstSending(socket);

yield fork(sendingToSocket, socket);

} else if (Array.isArray(payload)) {

const finalValueList = fromJS(yield select(getValuesList)).merge(

Map(convertData(payload))

);

yield put(changeWidgetsValuesList(finalValueList.toJS()));

}

}

}

function convertData(data) {

return data.reduce(

(result, { id, value }) => ({

...result, [id]: value

}), {}

);

}

function createSocket() {

const socket = new SockJS(getSocket);

socket.onerror = error => {

throwErrorMessage(error, 'CONNECTION ERROR');

socket.close();

};

socket.onclose = event => {

if (event.wasClean) {

throwSuccessMessage('Соединение закрыто чисто', 'Connection closed');

} else {

throwErrorMessage('Обрыв соединения', 'ERROR');

}

};

return socket;

}

function createSocketChannel(socket) {

return eventChannel(emit => {

socket.onopen = () => {

emit('connection');

};

socket.onmessage = e => {

emit(JSON.parse(e.data));

};

const unsubscribe = () => {

socket.close();

};

return unsubscribe;

});

}

function* sendingToSocket(socket) {

while (yield take(CHANGE_WIDGETS_TI_LIST)) {

const visibleTi = yield select(getVisibleTi);

if (visibleTi.length !== 0) {

socket.send(

JSON.stringify([

{

class: 'rtd',

attributes: ['value'],

filters: [

{

ids: visibleTi

}

]

}

])

);

}

}

}

Функция подбора цвета для ячейки тепловой карты

valuesToColorHSL = value => {

const { maxValueOfData } = this.props;

const { maxValue = maxValueOfData, minValue, gamma } = this.props;

const maxColor = this.getColors(gamma)[0];

const minColor = this.getColors(gamma)[1];

const offset = this.getColors(gamma)[2];

const intervalValue = maxValue - minValue;

const HSL = [];

HSL[0] = 0;

HSL[1] = 100;

HSL[2] = gamma.lightness;

const delta = 120 / intervalValue;

if (value === null) {

HSL[1] = 0;

HSL[2] = 85;

} else {

HSL[0] = Math.abs(Math.round((maxValue - value) * delta + offset));

}

if (value <= minValue) {

HSL[0] = minColor;

}

if (value > maxValue) {

HSL[0] = maxColor;

}

return `${'hsl('}${HSL[0]},${HSL[1]}${'%'},${HSL[2]}${'%'}${')'}`;

};

Источник: https://otherreferats.allbest.ru/download/1181521/