forked from CITKParus/P8-Panels
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a20de79a40 | ||
|
fa00940776 | ||
|
4115961742 | ||
|
f6b29d5339 | ||
|
b3e7d7bf7b | ||
|
f9b2f1ae34 | ||
|
836a3db5e2 |
@ -105,11 +105,14 @@ const getDisplaySize = () => {
|
|||||||
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
|
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
|
||||||
|
|
||||||
//Конвертация объекта в Base64 XML
|
//Конвертация объекта в Base64 XML
|
||||||
const object2Base64XML = (obj, builderOptions) => {
|
const object2XML = (obj, builderOptions) => {
|
||||||
const builder = new XMLBuilder(builderOptions);
|
const builder = new XMLBuilder(builderOptions);
|
||||||
return btoa(unescape(encodeURIComponent(builder.build(obj))));
|
return builder.build(obj);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Конвертация объекта в Base64 XML
|
||||||
|
const object2Base64XML = (obj, builderOptions) => btoa(unescape(encodeURIComponent(object2XML(obj, builderOptions))));
|
||||||
|
|
||||||
//Конвертация XML в JSON
|
//Конвертация XML в JSON
|
||||||
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
|
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -200,6 +203,7 @@ export {
|
|||||||
hasValue,
|
hasValue,
|
||||||
getDisplaySize,
|
getDisplaySize,
|
||||||
deepCopyObject,
|
deepCopyObject,
|
||||||
|
object2XML,
|
||||||
object2Base64XML,
|
object2Base64XML,
|
||||||
xml2JSON,
|
xml2JSON,
|
||||||
formatDateRF,
|
formatDateRF,
|
||||||
|
@ -19,11 +19,12 @@ import { DATA_TYPE } from "../../common"; //Общие ресурсы и кон
|
|||||||
|
|
||||||
//Типовые цвета точек привязки
|
//Типовые цвета точек привязки
|
||||||
const HANDLE_BORDER_COLOR = "#69db7c";
|
const HANDLE_BORDER_COLOR = "#69db7c";
|
||||||
|
const HANDLE_BORDER_COLOR_INVALID = "#ff0000";
|
||||||
const HANDLE_BORDER_COLOR_DISABLED = "#adb5bd";
|
const HANDLE_BORDER_COLOR_DISABLED = "#adb5bd";
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CONTAINER: { display: "flex", width: "100%", height: "100%" },
|
CONTAINER: { display: "flex", width: "100%", height: "100%", cursor: "default" },
|
||||||
HANDLE_SOURCE: isConnecting => ({
|
HANDLE_SOURCE: isConnecting => ({
|
||||||
width: 14,
|
width: 14,
|
||||||
height: 14,
|
height: 14,
|
||||||
@ -32,17 +33,18 @@ const STYLES = {
|
|||||||
borderRadius: 7,
|
borderRadius: 7,
|
||||||
background: "white"
|
background: "white"
|
||||||
}),
|
}),
|
||||||
HANDLE_TARGET: isConnecting => ({
|
HANDLE_TARGET: (isConnecting, isValidConnection) => ({
|
||||||
width: isConnecting ? 14 : 0,
|
width: isConnecting ? 14 : 0,
|
||||||
height: 14,
|
height: 14,
|
||||||
left: isConnecting ? -7 : 0,
|
left: isConnecting ? -7 : 0,
|
||||||
border: `2px solid ${HANDLE_BORDER_COLOR}`,
|
border: `2px solid ${isValidConnection ? HANDLE_BORDER_COLOR : HANDLE_BORDER_COLOR_INVALID}`,
|
||||||
borderRadius: 7,
|
borderRadius: 7,
|
||||||
background: "white",
|
background: "white",
|
||||||
visibility: isConnecting ? "visible" : "hidden"
|
visibility: isConnecting ? "visible" : "hidden"
|
||||||
}),
|
}),
|
||||||
CONTENT_STACK: { width: "100%" },
|
CONTENT_STACK: { width: "100%" },
|
||||||
TITLE_NAME_STACK: { width: "100%", containerType: "inline-size" }
|
TITLE_NAME_STACK: { width: "100%", containerType: "inline-size" },
|
||||||
|
ATTR_PROP_ICON: { fontSize: "0.9rem" }
|
||||||
};
|
};
|
||||||
|
|
||||||
//Иконки
|
//Иконки
|
||||||
@ -55,11 +57,36 @@ const ICONS = {
|
|||||||
|
|
||||||
//Структура данных об атрибуте сущности
|
//Структура данных об атрибуте сущности
|
||||||
const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({
|
const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
parentEntity: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
dataType: PropTypes.number.isRequired
|
dataType: PropTypes.oneOf(Object.values(DATA_TYPE)),
|
||||||
|
agg: PropTypes.string,
|
||||||
|
alias: PropTypes.string,
|
||||||
|
use: PropTypes.oneOf([0, 1]).isRequired,
|
||||||
|
show: PropTypes.oneOf([0, 1]).isRequired
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//-----------------------
|
||||||
|
//Вспомогательные функции
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
//Получение атрибутики состояния включения атрибута в запрос
|
||||||
|
const attrGetUse = (attr, callToAction = false) => {
|
||||||
|
return [attr.use === 1, `${attr.use === 1 ? "Включен в запрос" : "Не включен в запрос"}${callToAction ? "- нажмите, чтобы изменить" : ""}`];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Получение атрибутики состояния отображения атрибута в результатах запроса
|
||||||
|
const attrGetShow = (attr, callToAction = false) => {
|
||||||
|
return [
|
||||||
|
`${attr.show == 1 ? "Отображается в результатах запроса" : "Не отображается в результатах запроса"}${
|
||||||
|
callToAction ? "- нажмите, чтобы изменить" : ""
|
||||||
|
}`,
|
||||||
|
attr.show == 1 ? "visibility" : "visibility_off"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
@ -67,25 +94,45 @@ const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({
|
|||||||
//Атрибут сущности
|
//Атрибут сущности
|
||||||
const Attribute = ({ data }) => {
|
const Attribute = ({ data }) => {
|
||||||
//Поиск идентификатора соединяемого элемента
|
//Поиск идентификатора соединяемого элемента
|
||||||
const connectionNodeId = useStore(state => state.connectionNodeId);
|
const [connectionNodeId, targetConnectionNode, connectionStatus] = useStore(state => [
|
||||||
|
state.connectionNodeId,
|
||||||
|
state?.connectionEndHandle?.nodeId,
|
||||||
|
state.connectionStatus
|
||||||
|
]);
|
||||||
|
|
||||||
//Флаг выполнения соединения сущностей
|
//Флаг выполнения соединения сущностей
|
||||||
const isConnecting = Boolean(connectionNodeId);
|
const isConnecting = Boolean(connectionNodeId);
|
||||||
|
|
||||||
|
//Флаг корректности соединения сущностей
|
||||||
|
const isValidConnection = !(data.id == targetConnectionNode && connectionStatus == "invalid");
|
||||||
|
|
||||||
|
//Получим атрибуты состояния отображения
|
||||||
|
const [showTitle, showIcon] = attrGetShow(data);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Box p={1} sx={STYLES.CONTAINER}>
|
<Box p={1} sx={STYLES.CONTAINER}>
|
||||||
<Handle type={"source"} position={Position.Right} style={STYLES.HANDLE_SOURCE(isConnecting)} />
|
<Handle type={"source"} position={Position.Right} style={STYLES.HANDLE_SOURCE(isConnecting)} />
|
||||||
<Handle type={"target"} position={Position.Left} isConnectableStart={false} style={STYLES.HANDLE_TARGET(isConnecting)} />
|
<Handle
|
||||||
|
type={"target"}
|
||||||
|
position={Position.Left}
|
||||||
|
isConnectableStart={false}
|
||||||
|
style={STYLES.HANDLE_TARGET(isConnecting, isValidConnection)}
|
||||||
|
/>
|
||||||
<Stack direction={"row"} alignItems={"center"} spacing={1} sx={STYLES.CONTENT_STACK}>
|
<Stack direction={"row"} alignItems={"center"} spacing={1} sx={STYLES.CONTENT_STACK}>
|
||||||
<Icon color={"action"}>{ICONS[data.dataType] || ICONS.DEFAULT}</Icon>
|
<Icon color={"action"}>{ICONS[data.dataType] || ICONS.DEFAULT}</Icon>
|
||||||
<Stack direction={"column"} alignItems={"left"} sx={STYLES.TITLE_NAME_STACK}>
|
<Stack direction={"column"} alignItems={"left"} sx={STYLES.TITLE_NAME_STACK}>
|
||||||
<Typography variant={"body2"} noWrap title={data.title}>
|
<Typography variant={"body2"} noWrap title={data.title}>
|
||||||
{data.title}
|
{data.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant={"caption"} color={"text.secondary"} noWrap title={data.name}>
|
<Stack direction={"row"} alignItems={"center"} spacing={0.5}>
|
||||||
{data.name}
|
<Typography component={"div"} variant={"caption"} color={"text.secondary"} noWrap title={data.name}>
|
||||||
|
{`${data.name},`}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Icon color={"action"} sx={STYLES.ATTR_PROP_ICON} title={showTitle}>
|
||||||
|
{showIcon}
|
||||||
|
</Icon>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
@ -101,4 +148,4 @@ Attribute.propTypes = {
|
|||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { Attribute };
|
export { Attribute, ATTRIBUTE_DATA_SHAPE, attrGetUse, attrGetShow };
|
||||||
|
@ -17,6 +17,7 @@ import "./entity.css"; //Стили компомнента
|
|||||||
|
|
||||||
//Структура данных о сущности запроса
|
//Структура данных о сущности запроса
|
||||||
const ENTITY_DATA_SHAPE = PropTypes.shape({
|
const ENTITY_DATA_SHAPE = PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired
|
title: PropTypes.string.isRequired
|
||||||
});
|
});
|
||||||
@ -26,7 +27,7 @@ const ENTITY_DATA_SHAPE = PropTypes.shape({
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Сущность запроса
|
//Сущность запроса
|
||||||
const Entity = ({ data, selected }) => {
|
const Entity = ({ data, selected = false }) => {
|
||||||
return (
|
return (
|
||||||
<div className="entity__wrapper" data-selected={selected}>
|
<div className="entity__wrapper" data-selected={selected}>
|
||||||
<div className="entity__title">
|
<div className="entity__title">
|
||||||
@ -47,4 +48,4 @@ Entity.propTypes = {
|
|||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { Entity };
|
export { Entity, ENTITY_DATA_SHAPE };
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор запросов
|
||||||
|
Компонент: Список атрибутов сущности
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, ListItemIcon, Checkbox, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { ATTRIBUTE_DATA_SHAPE, attrGetUse, attrGetShow } from "../attribute/attribute"; //Атрибут сущности
|
||||||
|
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
SMALL_TOOL_ICON: {
|
||||||
|
fontSize: 20
|
||||||
|
},
|
||||||
|
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Список атрибутов сущности
|
||||||
|
const AttrsList = ({ attrs = [], filter, onSelect = null, onShow = null } = {}) => {
|
||||||
|
//При выборе элемента списка
|
||||||
|
const handleSelectClick = attr => {
|
||||||
|
onSelect && onSelect(attr);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на исправлении
|
||||||
|
const handleShowClick = (e, attr) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShow && onShow(attr);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Рег. выражение для фильтра
|
||||||
|
const filterRegExp = filter ? new RegExp(filter, "i") : null;
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<List sx={STYLES.LIST}>
|
||||||
|
{attrs &&
|
||||||
|
attrs
|
||||||
|
.filter(attr => (filterRegExp ? filterRegExp.test(attr.name) || filterRegExp.test(attr.title) : true))
|
||||||
|
.map((attr, i) => {
|
||||||
|
const [selected, selectedTitle] = attrGetUse(attr, true);
|
||||||
|
const [showTitle, showIcon] = attrGetShow(attr, true);
|
||||||
|
return (
|
||||||
|
<ListItem key={i} disablePadding>
|
||||||
|
<ListItemButton onClick={() => handleSelectClick(attr)} selected={selected} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Checkbox edge="start" checked={selected} tabIndex={-1} disableRipple title={selectedTitle} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={attr.title}
|
||||||
|
secondaryTypographyProps={{ component: "div" }}
|
||||||
|
secondary={
|
||||||
|
<Stack direction={"column"}>
|
||||||
|
<Typography variant={"caption"}>{`${attr.alias || attr.name}`}</Typography>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Stack direction={"row"}>
|
||||||
|
<IconButton onClick={e => handleShowClick(e, attr)} title={showTitle}>
|
||||||
|
<Icon>{showIcon}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Список атрибутов сущности
|
||||||
|
AttrsList.propTypes = {
|
||||||
|
attrs: PropTypes.arrayOf(ATTRIBUTE_DATA_SHAPE),
|
||||||
|
filter: PropTypes.string,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
onShow: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { AttrsList };
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор запросов
|
||||||
|
Компонент: Диалог настройки атрибутов сущности
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { TextField, InputAdornment, Icon, IconButton } from "@mui/material"; //Интерфейсные элементы MUI
|
||||||
|
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||||
|
import { AttrsList } from "./attrs_list"; //Список атрибутов сущности
|
||||||
|
import { useEntityAttrs } from "./hooks"; //Хуки диалога настройки атрибутов сущности
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Диалог настройки атрибутов сущности
|
||||||
|
const EntityAttrsDialog = ({ query, id, title, onOk, onCancel }) => {
|
||||||
|
//Собственное состояние - фильтр атрибутов
|
||||||
|
const [filter, setFilter] = useState("");
|
||||||
|
|
||||||
|
//Собственное состояние - список атрибутов
|
||||||
|
const [attrs, setAttrs] = useState([]);
|
||||||
|
|
||||||
|
//Хук для взаимодействия с сервером
|
||||||
|
const [srvAttrs, saveAttrs] = useEntityAttrs(query, id);
|
||||||
|
|
||||||
|
//Нажатие на кнопку "Ok"
|
||||||
|
const handleOk = async () => {
|
||||||
|
await saveAttrs(attrs);
|
||||||
|
onOk && onOk();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Нажатие на кнопку "Отмена"
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//Выбор/исключение атрибута из запроса
|
||||||
|
const handleAttrSelect = attr =>
|
||||||
|
setAttrs(
|
||||||
|
attrs.map(a => ({
|
||||||
|
...(a.id === attr.id ? { ...a, use: a.use === 1 ? 0 : 1, show: a.use === 1 ? 0 : a.show } : a)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
//Отображение/сокрытие атрибута в запросе
|
||||||
|
const handleAttrShow = attr =>
|
||||||
|
setAttrs(
|
||||||
|
attrs.map(a => ({
|
||||||
|
...(a.id === attr.id ? { ...a, show: a.show === 1 ? 0 : 1, use: a.show === 0 ? 1 : a.use } : a)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
//При изменении значения фильтра
|
||||||
|
const handleFilterChange = e => setFilter(e.target.value);
|
||||||
|
|
||||||
|
//При очистке фильтра
|
||||||
|
const handleFilterClear = () => setFilter("");
|
||||||
|
|
||||||
|
//При загрузке данных с сервера
|
||||||
|
useEffect(() => {
|
||||||
|
if (srvAttrs) setAttrs(srvAttrs.map(srvAttr => ({ ...srvAttr })));
|
||||||
|
}, [srvAttrs]);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<P8PDialog title={`Атрибуты сущности "${title}"`} onOk={handleOk} onCancel={handleCancel}>
|
||||||
|
<TextField
|
||||||
|
margin={"normal"}
|
||||||
|
variant={"standard"}
|
||||||
|
fullWidth
|
||||||
|
placeholder={"Поиск атрибута..."}
|
||||||
|
value={filter}
|
||||||
|
onChange={handleFilterChange}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position={"start"}>
|
||||||
|
<Icon>search</Icon>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position={"end"}>
|
||||||
|
<IconButton onClick={handleFilterClear}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AttrsList attrs={attrs} filter={filter} onSelect={handleAttrSelect} onShow={handleAttrShow} />
|
||||||
|
</P8PDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Диалог настройки атрибутов сущности
|
||||||
|
EntityAttrsDialog.propTypes = {
|
||||||
|
query: PropTypes.number.isRequired,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onOk: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { EntityAttrsDialog };
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор запросов
|
||||||
|
Пользовательские хуки диалога настройки атрибутов сущности
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||||
|
import { BackEndСtx } from "../../../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
|
import { object2Base64XML } from "../../../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Работа с атрибутами сущности
|
||||||
|
const useEntityAttrs = (query, entity) => {
|
||||||
|
//Собственное состояние - флаг загрузки
|
||||||
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - флаг необходимости обновления
|
||||||
|
const [refresh, setRefresh] = useState(true);
|
||||||
|
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//Обновление данных
|
||||||
|
const doRefresh = () => setRefresh(true);
|
||||||
|
|
||||||
|
//Установка атрибутов сущности
|
||||||
|
const setAttrs = useCallback(
|
||||||
|
async attrs => {
|
||||||
|
await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_QE.QUERY_ENT_ATTRS_SET",
|
||||||
|
args: {
|
||||||
|
NRN: query,
|
||||||
|
SID: entity,
|
||||||
|
CATTRS: {
|
||||||
|
VALUE: object2Base64XML(attrs, { arrayNodeName: "XATTR", ignoreAttributes: false, attributeNamePrefix: "" }),
|
||||||
|
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loader: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query, entity, executeStored, SERV_DATA_TYPE_CLOB]
|
||||||
|
);
|
||||||
|
|
||||||
|
//При необходимости получить/обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
//Загрузка данных с сервера
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_QE.QUERY_ENT_ATTRS_GET",
|
||||||
|
args: { NRN: query, SID: entity },
|
||||||
|
respArg: "COUT",
|
||||||
|
isArray: name => ["XATTR"].includes(name),
|
||||||
|
attributeValueProcessor: (name, val) => (["name", "title", "agg"].includes(name) ? undefined : val),
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
setData(data?.XATTRS?.XATTR || []);
|
||||||
|
} finally {
|
||||||
|
setRefresh(false);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//Если надо обновить
|
||||||
|
if (refresh)
|
||||||
|
//Получим данные
|
||||||
|
loadData();
|
||||||
|
}, [refresh, query, entity, executeStored]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [data, setAttrs, doRefresh, isLoading];
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { useEntityAttrs };
|
@ -10,6 +10,7 @@
|
|||||||
import React from "react"; //Классы React
|
import React from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -20,14 +21,15 @@ import { BUTTONS } from "../../../../../app.text"; //Общие текстовы
|
|||||||
const STYLES = {
|
const STYLES = {
|
||||||
SMALL_TOOL_ICON: {
|
SMALL_TOOL_ICON: {
|
||||||
fontSize: 20
|
fontSize: 20
|
||||||
}
|
},
|
||||||
|
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||||
};
|
};
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
//Структура данных о сущности запроса
|
//Структура элемента списка запросов
|
||||||
const QUERIES_LIST_ITEM_SHAPE = PropTypes.shape({
|
const QUERIES_LIST_ITEM_SHAPE = PropTypes.shape({
|
||||||
rn: PropTypes.number.isRequired,
|
rn: PropTypes.number.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
@ -43,7 +45,7 @@ const QUERIES_LIST_ITEM_SHAPE = PropTypes.shape({
|
|||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Диалог открытия запроса
|
//Список запросов
|
||||||
const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = null, onReady = null, onEdit = null, onDelete = null } = {}) => {
|
const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = null, onReady = null, onEdit = null, onDelete = null } = {}) => {
|
||||||
//При выборе элемента списка
|
//При выборе элемента списка
|
||||||
const handleSelectClick = query => {
|
const handleSelectClick = query => {
|
||||||
@ -76,7 +78,7 @@ const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = nu
|
|||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<List sx={{ height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto" }}>
|
<List sx={STYLES.LIST}>
|
||||||
{queries.map((query, i) => {
|
{queries.map((query, i) => {
|
||||||
const selected = query.rn === current;
|
const selected = query.rn === current;
|
||||||
const disabled = !query.modify;
|
const disabled = !query.modify;
|
||||||
@ -85,8 +87,8 @@ const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = nu
|
|||||||
const readyTitle = `${query.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
const readyTitle = `${query.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||||
const readyIcon = query.ready === 1 ? "touch_app" : "do_not_touch";
|
const readyIcon = query.ready === 1 ? "touch_app" : "do_not_touch";
|
||||||
return (
|
return (
|
||||||
<ListItem key={i}>
|
<ListItem key={i} disablePadding>
|
||||||
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected}>
|
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected} dense>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={query.name}
|
primary={query.name}
|
||||||
secondaryTypographyProps={{ component: "div" }}
|
secondaryTypographyProps={{ component: "div" }}
|
||||||
|
@ -36,7 +36,7 @@ const QueriesManager = ({ current = null, onQuerySelect = null, onCancel = null
|
|||||||
const handleQueryAdd = () => setModQuery(true);
|
const handleQueryAdd = () => setModQuery(true);
|
||||||
|
|
||||||
//При выборе запроса
|
//При выборе запроса
|
||||||
const handleQuerySelect = query => onQuerySelect && onQuerySelect(query.rn);
|
const handleQuerySelect = query => onQuerySelect && onQuerySelect({ ...query });
|
||||||
|
|
||||||
//При установке признака публичности
|
//При установке признака публичности
|
||||||
const handleQueryPblSet = query => setQueryPbl(query.rn, query.pbl === 1 ? 0 : 1);
|
const handleQueryPblSet = query => setQueryPbl(query.rn, query.pbl === 1 ? 0 : 1);
|
||||||
|
@ -8,10 +8,11 @@
|
|||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useState, useCallback, useEffect } from "react"; //Классы React
|
import React, { useState, useCallback, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import ReactFlow, { addEdge, Controls, getOutgoers, applyNodeChanges, applyEdgeChanges } from "reactflow"; //Библиотека редактора диаграмм
|
import ReactFlow, { addEdge, Controls, getOutgoers, applyNodeChanges, applyEdgeChanges } from "reactflow"; //Библиотека редактора диаграмм
|
||||||
import { NODE_TYPE } from "../../common"; //Общие ресурсы и константы редактора
|
import { NODE_TYPE } from "../../common"; //Общие ресурсы и константы редактора
|
||||||
import { Entity } from "../entity/entity"; //Сущность запроса
|
import { Entity, ENTITY_DATA_SHAPE } from "../entity/entity"; //Сущность запроса
|
||||||
import { Attribute } from "../attribute/attribute"; //Атрибут сущности
|
import { Attribute, ATTRIBUTE_DATA_SHAPE } from "../attribute/attribute"; //Атрибут сущности
|
||||||
import "reactflow/dist/style.css"; //Типовые стили библиотеки редактора диаграмм
|
import "reactflow/dist/style.css"; //Типовые стили библиотеки редактора диаграмм
|
||||||
import "./query_diagram.css"; //Стили компонента
|
import "./query_diagram.css"; //Стили компонента
|
||||||
|
|
||||||
@ -36,42 +37,48 @@ const NODE_TYPES_COMPONENTS = {
|
|||||||
[NODE_TYPE.ATTRIBUTE]: Attribute
|
[NODE_TYPE.ATTRIBUTE]: Attribute
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Структура сущности запроса
|
||||||
|
const ENTITY_SHAPE = PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.oneOf([NODE_TYPE.ENTITY, NODE_TYPE.ATTRIBUTE]).isRequired,
|
||||||
|
style: PropTypes.object,
|
||||||
|
position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }),
|
||||||
|
draggable: PropTypes.bool.isRequired,
|
||||||
|
data: PropTypes.oneOfType([ENTITY_DATA_SHAPE, ATTRIBUTE_DATA_SHAPE])
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура связи запроса
|
||||||
|
const RELATION_SHAPE = PropTypes.shape({ id: PropTypes.string.isRequired, source: PropTypes.string.isRequired, target: PropTypes.string.isRequired });
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
//Вспомогательные функции и компоненты
|
//Вспомогательные функции и компоненты
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
|
//Проверка зацикленности связи
|
||||||
const hasCycle = (connection, target, nodes, edges, visited = new Set()) => {
|
const hasCycle = (connection, target, nodes, edges, visited = new Set()) => {
|
||||||
if (visited.has(target.id)) {
|
if (visited.has(target.id)) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.add(target.id);
|
visited.add(target.id);
|
||||||
|
for (const outgoer of getOutgoers(target, nodes, edges))
|
||||||
for (const outgoer of getOutgoers(target, nodes, edges)) {
|
if (outgoer.id === connection.source || hasCycle(connection, outgoer, nodes, edges, visited)) return true;
|
||||||
if (outgoer.id === connection.source || hasCycle(connection, outgoer, nodes, edges, visited)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Проверка корректности связи
|
||||||
const isValidConnection = (connection, nodes, edges) => {
|
const isValidConnection = (connection, nodes, edges) => {
|
||||||
if (!connection.source || !connection.target) {
|
//Должны быть заданы источник и приёмник
|
||||||
return false;
|
if (!connection.source || !connection.target) return false;
|
||||||
}
|
//Найдем источник и приёмник
|
||||||
|
const source = nodes.find(node => node.id === connection.source);
|
||||||
const tableId = connection.source.split("-")[0];
|
|
||||||
const isSameTable = connection.target.startsWith(tableId);
|
|
||||||
if (isSameTable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = nodes.find(node => node.id === connection.target);
|
const target = nodes.find(node => node.id === connection.target);
|
||||||
if (!target || target.id === connection.source) {
|
//Приёмник и источник должны существовать
|
||||||
return false;
|
if (!target || !source) return false;
|
||||||
}
|
//Нельзя ссылаться на самого себя
|
||||||
|
if (source?.data?.parentEntity == target?.data?.parentEntity) return false;
|
||||||
|
//Типы данны источника и приёмника должны совпадать
|
||||||
|
if (source?.data?.dataType != target?.data?.dataType) return false;
|
||||||
|
//Приёмник должен не должен быть источником
|
||||||
|
if (target.id === connection.source) return false;
|
||||||
|
//Нельзя зацикливаться
|
||||||
return !hasCycle(connection, target, nodes, edges);
|
return !hasCycle(connection, target, nodes, edges);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,7 +87,18 @@ const isValidConnection = (connection, nodes, edges) => {
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Диаграмма запроса
|
//Диаграмма запроса
|
||||||
const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRemove, onRelationAdd, onRelationRemove }) => {
|
const QueryDiagram = ({
|
||||||
|
entities = [],
|
||||||
|
relations = [],
|
||||||
|
onEntityClick,
|
||||||
|
onEntityAttrClick,
|
||||||
|
onEntityPositionChange,
|
||||||
|
onEntityRemove,
|
||||||
|
onRelactionClick,
|
||||||
|
onRelationAdd,
|
||||||
|
onRelationRemove,
|
||||||
|
onPaneClick
|
||||||
|
}) => {
|
||||||
//Собственное состояние - элементы
|
//Собственное состояние - элементы
|
||||||
const [nodes, setNodes] = useState(entities);
|
const [nodes, setNodes] = useState(entities);
|
||||||
|
|
||||||
@ -93,17 +111,44 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
|||||||
//При изменении элементов на диаграмме
|
//При изменении элементов на диаграмме
|
||||||
const handleNodesChange = useCallback(
|
const handleNodesChange = useCallback(
|
||||||
changes => {
|
changes => {
|
||||||
setNodes(nodesSnapshot => applyNodeChanges(changes, nodesSnapshot));
|
//При выборе атрибута подсветим всю сущность
|
||||||
|
const tmpChanges = changes.reduce((prevChanges, curChanges) => {
|
||||||
|
const tmp = { ...curChanges };
|
||||||
|
if (tmp.type == "select") {
|
||||||
|
const chEnt = entities.find(e => e.id === tmp.id);
|
||||||
|
if (chEnt && chEnt?.data?.parentEntity) {
|
||||||
|
prevChanges.push({ ...curChanges, id: chEnt.data.parentEntity });
|
||||||
|
tmp.selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevChanges.push(tmp);
|
||||||
|
return prevChanges;
|
||||||
|
}, []);
|
||||||
|
//Применим изменения в диаграмме
|
||||||
|
setNodes(nodesSnapshot => applyNodeChanges(tmpChanges, nodesSnapshot));
|
||||||
|
//Если двигали сущность - запомним начало движения
|
||||||
if (changes.length == 1 && changes[0].type == "position" && changes[0].dragging)
|
if (changes.length == 1 && changes[0].type == "position" && changes[0].dragging)
|
||||||
setMovedNode({ id: changes[0].id, position: { ...changes[0].position } });
|
setMovedNode({ id: changes[0].id, position: { ...changes[0].position } });
|
||||||
|
//Если закончили двигать сущность и ранее запомнили начало движения - вызываем колбэки для нажатия и двиения сущности
|
||||||
if (changes.length == 1 && changes[0].type == "position" && !changes[0].dragging && movedNode) {
|
if (changes.length == 1 && changes[0].type == "position" && !changes[0].dragging && movedNode) {
|
||||||
if (onEntityPositionChange) onEntityPositionChange(movedNode.id, movedNode.position);
|
if (onEntityPositionChange) onEntityPositionChange(movedNode.id, movedNode.position);
|
||||||
|
if (onEntityClick) onEntityClick(movedNode.id);
|
||||||
setMovedNode(null);
|
setMovedNode(null);
|
||||||
}
|
}
|
||||||
|
//Если удалили сущность - вызываем колбэк для удаления
|
||||||
if (changes[0].type == "remove" && entities.find(e => e.id == changes[0].id && e.type == NODE_TYPE.ENTITY) && onEntityRemove)
|
if (changes[0].type == "remove" && entities.find(e => e.id == changes[0].id && e.type == NODE_TYPE.ENTITY) && onEntityRemove)
|
||||||
onEntityRemove(changes[0].id);
|
onEntityRemove(changes[0].id);
|
||||||
},
|
},
|
||||||
[movedNode, entities, onEntityPositionChange, onEntityRemove]
|
[movedNode, entities, onEntityClick, onEntityPositionChange, onEntityRemove]
|
||||||
|
);
|
||||||
|
|
||||||
|
//При выборе элемента диаграммы
|
||||||
|
const handleNodeClick = useCallback(
|
||||||
|
(e, node) =>
|
||||||
|
node?.type == NODE_TYPE.ENTITY
|
||||||
|
? onEntityClick && onEntityClick(node?.id)
|
||||||
|
: onEntityAttrClick && onEntityAttrClick(node?.parentId, node?.id),
|
||||||
|
[onEntityClick, onEntityAttrClick]
|
||||||
);
|
);
|
||||||
|
|
||||||
//При связывании элементов на диаграмме
|
//При связывании элементов на диаграмме
|
||||||
@ -112,6 +157,9 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
|||||||
onRelationAdd && onRelationAdd(connection.source, connection.target);
|
onRelationAdd && onRelationAdd(connection.source, connection.target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//При выборе связи диаграммы
|
||||||
|
const handleEdgeClick = useCallback((e, edge) => onRelactionClick && onRelactionClick(edge.id), [onRelactionClick]);
|
||||||
|
|
||||||
//При изменении связей на диаграмме
|
//При изменении связей на диаграмме
|
||||||
const handleEdgesChange = useCallback(
|
const handleEdgesChange = useCallback(
|
||||||
changes => {
|
changes => {
|
||||||
@ -121,9 +169,13 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
|||||||
[onRelationRemove]
|
[onRelationRemove]
|
||||||
);
|
);
|
||||||
|
|
||||||
const validateConnection = connection => {
|
//При нажатии на холст диаграммы
|
||||||
return isValidConnection(connection, nodes, edges);
|
const handlePaneClick = () => onPaneClick && onPaneClick();
|
||||||
};
|
|
||||||
|
//Валидация связи
|
||||||
|
const validateConnection = connection => isValidConnection(connection, nodes, edges);
|
||||||
|
|
||||||
|
//Подсветка выбранной сущности
|
||||||
|
|
||||||
//При изменении состава сущностей
|
//При изменении состава сущностей
|
||||||
useEffect(() => setNodes(entities), [entities]);
|
useEffect(() => setNodes(entities), [entities]);
|
||||||
@ -137,8 +189,11 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
|||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
nodeTypes={NODE_TYPES_COMPONENTS}
|
nodeTypes={NODE_TYPES_COMPONENTS}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
|
onNodeClick={handleNodeClick}
|
||||||
onNodesChange={handleNodesChange}
|
onNodesChange={handleNodesChange}
|
||||||
|
onEdgeClick={handleEdgeClick}
|
||||||
onEdgesChange={handleEdgesChange}
|
onEdgesChange={handleEdgesChange}
|
||||||
|
onPaneClick={handlePaneClick}
|
||||||
defaultEdgeOptions={{
|
defaultEdgeOptions={{
|
||||||
animated: true,
|
animated: true,
|
||||||
style: STYLES.EDGE
|
style: STYLES.EDGE
|
||||||
@ -153,6 +208,20 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Диаграмма запроса
|
||||||
|
QueryDiagram.propTypes = {
|
||||||
|
entities: PropTypes.arrayOf(ENTITY_SHAPE),
|
||||||
|
relations: PropTypes.arrayOf(RELATION_SHAPE),
|
||||||
|
onEntityClick: PropTypes.func,
|
||||||
|
onEntityAttrClick: PropTypes.func,
|
||||||
|
onEntityPositionChange: PropTypes.func,
|
||||||
|
onEntityRemove: PropTypes.func,
|
||||||
|
onRelactionClick: PropTypes.func,
|
||||||
|
onRelationAdd: PropTypes.func,
|
||||||
|
onRelationRemove: PropTypes.func,
|
||||||
|
onPaneClick: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор запросов
|
||||||
|
Свойства запроса
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения
|
||||||
|
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||||
|
import { EntityAddDialog } from "../entity_add_dialog/entity_add_dialog"; //Диалог добавления сущности
|
||||||
|
import { EntityAttrsDialog } from "../entity_attrs_dialog/entity_attrs_dialog"; //Диалог настройки атрибутов сущности
|
||||||
|
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер параметров редактора
|
||||||
|
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
|
||||||
|
import { ENTITY_DATA_SHAPE } from "../entity/entity"; //Описание сущности
|
||||||
|
import { RELATION_DATA_SHAPE } from "../relation/relation"; //Описание связи
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Свойства запроса
|
||||||
|
const QueryOptions = ({ query, entity, relation, onEntityAdd, onEntityRemove, onRelationRemove, onQueryOptionsChanged }) => {
|
||||||
|
//Отображение диалога добавления сущности
|
||||||
|
const [openEntityAddDialog, setOpenEntityAddDialog] = useState(false);
|
||||||
|
|
||||||
|
//Отображение диалога настройки атрибутов сущности
|
||||||
|
const [openEntityAttrsDialog, setOpenEntityAttrsDialog] = useState(false);
|
||||||
|
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgWarn } = useContext(MessagingСtx);
|
||||||
|
|
||||||
|
//При нажатии на кнопку добавлении сущности в запрос
|
||||||
|
const handleEntityAddClick = () => setOpenEntityAddDialog(true);
|
||||||
|
|
||||||
|
//При нажатии на кнопку настройки атрибутов сущности
|
||||||
|
const handleEntityAttrsClick = () => setOpenEntityAttrsDialog(true);
|
||||||
|
|
||||||
|
//При нажатии на кнопку даления сущности из запроса
|
||||||
|
const handleEntityRemoveClick = () =>
|
||||||
|
showMsgWarn(`Удалить сущность "${entity.title}"?`, () => entity?.id && onEntityRemove && onEntityRemove(entity.id));
|
||||||
|
|
||||||
|
//При нажатии на кнопку даления связи из запроса
|
||||||
|
const handleRelationRemoveClick = () =>
|
||||||
|
showMsgWarn(
|
||||||
|
`Удалить связь "${relation.source}" - "${relation.target}"?`,
|
||||||
|
() => relation?.id && onRelationRemove && onRelationRemove(relation.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
//Закрытие диалога добавления сущности по "Отмена"
|
||||||
|
const handleEntityAddDialogCancel = () => setOpenEntityAddDialog(false);
|
||||||
|
|
||||||
|
//Закрытие диалога добавления сущности по "ОК"
|
||||||
|
const handleEntityAddDialogOk = values => onEntityAdd && onEntityAdd(values.name, res => res && setOpenEntityAddDialog(false));
|
||||||
|
|
||||||
|
//Закрытие диалога настройки атрибутов сущности по "Отмена"
|
||||||
|
const handleEntityAttrsDialogCancel = () => setOpenEntityAttrsDialog(false);
|
||||||
|
|
||||||
|
//Закрытие диалога настройки атрибутов сущности по "ОК"
|
||||||
|
const handleEntityAttrsDialogOk = () => {
|
||||||
|
onQueryOptionsChanged && onQueryOptionsChanged();
|
||||||
|
setOpenEntityAttrsDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{openEntityAddDialog && <EntityAddDialog onOk={handleEntityAddDialogOk} onCancel={handleEntityAddDialogCancel} />}
|
||||||
|
{openEntityAttrsDialog && (
|
||||||
|
<EntityAttrsDialog query={query} {...entity} onOk={handleEntityAttrsDialogOk} onCancel={handleEntityAttrsDialogCancel} />
|
||||||
|
)}
|
||||||
|
<P8PEditorBox title={"Настройки запроса"}>
|
||||||
|
<P8PEditorSubHeader title={"Параметры"} />
|
||||||
|
<P8PEditorSubHeader title={"Условия отбора"} />
|
||||||
|
<P8PEditorSubHeader title={"Сущности"} />
|
||||||
|
<Button startIcon={<Icon>add</Icon>} onClick={handleEntityAddClick}>
|
||||||
|
{BUTTONS.INSERT}
|
||||||
|
</Button>
|
||||||
|
{entity && (
|
||||||
|
<>
|
||||||
|
<P8PEditorSubHeader title={entity.title} />
|
||||||
|
<Button startIcon={<Icon>edit_attributes</Icon>} onClick={handleEntityAttrsClick}>
|
||||||
|
Атрибуты
|
||||||
|
</Button>
|
||||||
|
<Button startIcon={<Icon>delete</Icon>} onClick={handleEntityRemoveClick}>
|
||||||
|
{BUTTONS.DELETE}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{relation && (
|
||||||
|
<>
|
||||||
|
<P8PEditorSubHeader title={"Связь"} />
|
||||||
|
<Button startIcon={<Icon>delete</Icon>} onClick={handleRelationRemoveClick}>
|
||||||
|
{BUTTONS.DELETE}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</P8PEditorBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Свойства запроса
|
||||||
|
QueryOptions.propTypes = {
|
||||||
|
query: PropTypes.number.isRequired,
|
||||||
|
entity: ENTITY_DATA_SHAPE,
|
||||||
|
relation: RELATION_DATA_SHAPE,
|
||||||
|
onEntityAdd: PropTypes.func,
|
||||||
|
onEntityRemove: PropTypes.func,
|
||||||
|
onRelationRemove: PropTypes.func,
|
||||||
|
onQueryOptionsChanged: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { QueryOptions };
|
27
app/panels/query_editor/components/relation/relation.js
Normal file
27
app/panels/query_editor/components/relation/relation.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор запросов
|
||||||
|
Компоненты: Связь сущностей запроса
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Структура данных о связи сущностей запроса
|
||||||
|
const RELATION_DATA_SHAPE = PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
source: PropTypes.string.isRequired,
|
||||||
|
target: PropTypes.string.isRequired
|
||||||
|
});
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { RELATION_DATA_SHAPE };
|
@ -7,22 +7,23 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
import React, { useState, useContext } from "react"; //Классы React
|
||||||
import { Box, Grid, Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
import { Box, Grid } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { ApplicationСtx } from "../../context/application"; //Контекст взаимодействия с приложением
|
||||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Компоненты рабочего стола
|
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Компоненты рабочего стола
|
||||||
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
||||||
import { BUTTONS } from "../../../app.text"; //Общие текстовые ресурсы приложения
|
|
||||||
import { QueryDiagram } from "./components/query_diagram/query_diagram"; //Диаграмма запроса
|
import { QueryDiagram } from "./components/query_diagram/query_diagram"; //Диаграмма запроса
|
||||||
|
import { QueryOptions } from "./components/query_options/query_options"; //Свойства запроса
|
||||||
import { QueriesManager } from "./components/queries_manager/queries_manager"; //Менеджер запросов
|
import { QueriesManager } from "./components/queries_manager/queries_manager"; //Менеджер запросов
|
||||||
import { EntityAddDialog } from "./components/entity_add_dialog/entity_add_dialog"; //Диалог добавления сущности
|
|
||||||
import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер параметров редактора
|
|
||||||
import { P8PEditorSubHeader } from "../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
|
|
||||||
import { useQueryDesc } from "./hooks"; //Пользовательские хуки
|
import { useQueryDesc } from "./hooks"; //Пользовательские хуки
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
|
//Заголовок панели по умолчанию
|
||||||
|
const APP_BAR_TITLE_DEFAULT = "Редактор запросов";
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CONTAINER: { display: "flex" },
|
CONTAINER: { display: "flex" },
|
||||||
@ -39,26 +40,79 @@ const QueryEditor = () => {
|
|||||||
//Текущий запрос
|
//Текущий запрос
|
||||||
const [query, setQuery] = useState(null);
|
const [query, setQuery] = useState(null);
|
||||||
|
|
||||||
|
//Текущая сущность
|
||||||
|
const [entity, setEntity] = useState(null);
|
||||||
|
|
||||||
|
//Текущая связь
|
||||||
|
const [relation, setRelation] = useState(null);
|
||||||
|
|
||||||
//Отображения менеджера запросов
|
//Отображения менеджера запросов
|
||||||
const [openQueriesManager, setOpenQueriesManager] = useState(true);
|
const [openQueriesManager, setOpenQueriesManager] = useState(true);
|
||||||
|
|
||||||
//Отображение диалога добавления сущности
|
|
||||||
const [openEntityAddDialog, setOpenEntityAddDialog] = useState(false);
|
|
||||||
|
|
||||||
//Получение данных запроса
|
//Получение данных запроса
|
||||||
const [queryDiagram, addEnt, removeEnt, setEntPosition, addRl, removeRl] = useQueryDesc(query);
|
const [queryDiagram, addEnt, removeEnt, setEntPosition, addRl, removeRl, doRefresh] = useQueryDesc(query);
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { setAppBarTitle } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//Выбор сущности
|
||||||
|
const selectEntity = ent => {
|
||||||
|
setRelation(null);
|
||||||
|
const queryEnt = queryDiagram.entities.find(e => e.id === ent);
|
||||||
|
if (queryEnt) setEntity({ ...queryEnt.data });
|
||||||
|
};
|
||||||
|
|
||||||
|
//Выбор связи
|
||||||
|
const selectRelation = rl => {
|
||||||
|
setEntity(null);
|
||||||
|
const queryRl = queryDiagram.relations.find(r => r.id === rl);
|
||||||
|
if (queryRl) setRelation({ ...queryRl });
|
||||||
|
};
|
||||||
|
|
||||||
|
//Сброс выбора связи и сущности
|
||||||
|
const cleanupEnRlSelection = () => {
|
||||||
|
setRelation(null);
|
||||||
|
setEntity(null);
|
||||||
|
};
|
||||||
|
|
||||||
//Обработка изменения положения сущности на диаграмме
|
//Обработка изменения положения сущности на диаграмме
|
||||||
const handleEntityPositionChange = (ent, position) => setEntPosition(ent, position.x, position.y);
|
const handleEntityPositionChange = (ent, position) => setEntPosition(ent, position.x, position.y);
|
||||||
|
|
||||||
|
//Обработка добавления сущности в запрос
|
||||||
|
const handleEntityAdd = async (entName, cb) => {
|
||||||
|
await addEnt(entName, "VIEW");
|
||||||
|
cb(true);
|
||||||
|
};
|
||||||
|
|
||||||
//Обработка удаления сущности из запроса
|
//Обработка удаления сущности из запроса
|
||||||
const handleEntityRemove = ent => removeEnt(ent);
|
const handleEntityRemove = async ent => {
|
||||||
|
await removeEnt(ent);
|
||||||
|
if (entity && entity?.id === ent) cleanupEnRlSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Обработка выделения сущности
|
||||||
|
const handleEntityClick = ent => selectEntity(ent);
|
||||||
|
|
||||||
|
//Обработка выделения тарибута сущности
|
||||||
|
const handleEntityAttrClick = ent => selectEntity(ent);
|
||||||
|
|
||||||
|
//Обработка выделения связи
|
||||||
|
const handleRelationClick = rl => selectRelation(rl);
|
||||||
|
|
||||||
//Обработка добавления отношения cущностей
|
//Обработка добавления отношения cущностей
|
||||||
const handleRelationAdd = (source, target) => addRl(source, target);
|
const handleRelationAdd = (source, target) => {
|
||||||
|
cleanupEnRlSelection();
|
||||||
|
addRl(source, target);
|
||||||
|
};
|
||||||
|
|
||||||
//Обработка удаления отношения cущностей
|
//Обработка удаления отношения cущностей
|
||||||
const handleRelationRemove = rl => removeRl(rl);
|
const handleRelationRemove = async rl => {
|
||||||
|
await removeRl(rl);
|
||||||
|
if (relation && relation?.id === rl) cleanupEnRlSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на панели (пустом месте) диаграммы запроса
|
||||||
|
const handlePaneClick = () => cleanupEnRlSelection();
|
||||||
|
|
||||||
//Открытие менеджера запросов
|
//Открытие менеджера запросов
|
||||||
const handleOpenQueriesManager = () => setOpenQueriesManager(true);
|
const handleOpenQueriesManager = () => setOpenQueriesManager(true);
|
||||||
@ -67,26 +121,25 @@ const QueryEditor = () => {
|
|||||||
const handleCancelQueriesManager = () => setOpenQueriesManager(false);
|
const handleCancelQueriesManager = () => setOpenQueriesManager(false);
|
||||||
|
|
||||||
//Закрытие запроса
|
//Закрытие запроса
|
||||||
const handleQueryClose = () => setQuery(null);
|
const handleQueryClose = () => {
|
||||||
|
setAppBarTitle(APP_BAR_TITLE_DEFAULT);
|
||||||
|
cleanupEnRlSelection();
|
||||||
|
setQuery(null);
|
||||||
|
};
|
||||||
|
|
||||||
//При выборе запроса
|
//При выборе запроса
|
||||||
const handleQuerySelect = query => {
|
const handleQuerySelect = ({ rn, name }) => {
|
||||||
setQuery(query);
|
setAppBarTitle(`Запрос [${name}]`);
|
||||||
|
setQuery(rn);
|
||||||
setOpenQueriesManager(false);
|
setOpenQueriesManager(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
//При добавлении сущности в запрос
|
//При изменении свойств запроса
|
||||||
const handleEntityAdd = () => setOpenEntityAddDialog(true);
|
const handleQueryOptionsChanged = () => {
|
||||||
|
cleanupEnRlSelection();
|
||||||
//Закрытие диалога добавления сущности по "ОК"
|
doRefresh();
|
||||||
const handleEntityAddDialogOk = async values => {
|
|
||||||
await addEnt(values.name, "VIEW");
|
|
||||||
setOpenEntityAddDialog(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Закрытие диалога добавления сущности по "ОК"
|
|
||||||
const handleEntityAddDialogCancel = () => setOpenEntityAddDialog(false);
|
|
||||||
|
|
||||||
//Панель инструмментов
|
//Панель инструмментов
|
||||||
const toolBar = (
|
const toolBar = (
|
||||||
<P8PEditorToolBar
|
<P8PEditorToolBar
|
||||||
@ -101,28 +154,34 @@ const QueryEditor = () => {
|
|||||||
return (
|
return (
|
||||||
<Box sx={STYLES.CONTAINER}>
|
<Box sx={STYLES.CONTAINER}>
|
||||||
{openQueriesManager && <QueriesManager current={query} onQuerySelect={handleQuerySelect} onCancel={handleCancelQueriesManager} />}
|
{openQueriesManager && <QueriesManager current={query} onQuerySelect={handleQuerySelect} onCancel={handleCancelQueriesManager} />}
|
||||||
{openEntityAddDialog && <EntityAddDialog onOk={handleEntityAddDialogOk} onCancel={handleEntityAddDialogCancel} />}
|
|
||||||
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
||||||
<Grid item xs={20}>
|
<Grid item xs={20}>
|
||||||
{queryDiagram && (
|
{queryDiagram && (
|
||||||
<QueryDiagram
|
<QueryDiagram
|
||||||
{...queryDiagram}
|
{...queryDiagram}
|
||||||
|
onEntityClick={handleEntityClick}
|
||||||
|
onEntityAttrClick={handleEntityAttrClick}
|
||||||
onEntityPositionChange={handleEntityPositionChange}
|
onEntityPositionChange={handleEntityPositionChange}
|
||||||
onEntityRemove={handleEntityRemove}
|
onEntityRemove={handleEntityRemove}
|
||||||
|
onRelactionClick={handleRelationClick}
|
||||||
onRelationAdd={handleRelationAdd}
|
onRelationAdd={handleRelationAdd}
|
||||||
onRelationRemove={handleRelationRemove}
|
onRelationRemove={handleRelationRemove}
|
||||||
|
onPaneClick={handlePaneClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
||||||
{toolBar}
|
{toolBar}
|
||||||
{query && (
|
{query && (
|
||||||
<P8PEditorBox title={"Параметры запроса"}>
|
<QueryOptions
|
||||||
<P8PEditorSubHeader title={"Сущности"} />
|
query={query}
|
||||||
<Button startIcon={<Icon>add</Icon>} onClick={handleEntityAdd}>
|
entity={entity}
|
||||||
{BUTTONS.INSERT}
|
relation={relation}
|
||||||
</Button>
|
onEntityAdd={handleEntityAdd}
|
||||||
</P8PEditorBox>
|
onEntityRemove={handleEntityRemove}
|
||||||
|
onRelationRemove={handleRelationRemove}
|
||||||
|
onQueryOptionsChanged={handleQueryOptionsChanged}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -59,6 +59,22 @@ create or replace package PKG_P8PANELS_QE as
|
|||||||
NY in number -- Координата по оси ординат
|
NY in number -- Координата по оси ординат
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Получение состава атрибутов сущности */
|
||||||
|
procedure QUERY_ENT_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер запроса
|
||||||
|
SID in varchar2, -- Идентификатор сущности
|
||||||
|
COUT out clob -- Сериализованное описание атрибутов сущности
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Установка состава атрибутов сущности */
|
||||||
|
procedure QUERY_ENT_ATTRS_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер запроса
|
||||||
|
SID in varchar2, -- Идентификатор сущности
|
||||||
|
CATTRS in clob -- Сериализованное описание атрибутов сущности (BASE64 XML)
|
||||||
|
);
|
||||||
|
|
||||||
/* Добавление связи в запрос */
|
/* Добавление связи в запрос */
|
||||||
procedure QUERY_RL_ADD
|
procedure QUERY_RL_ADD
|
||||||
(
|
(
|
||||||
@ -192,7 +208,6 @@ create or replace package body PKG_P8PANELS_QE as
|
|||||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||||
RENT PKG_P8PANELS_QE_BASE.TENT; -- Удаляемая сущность
|
RENT PKG_P8PANELS_QE_BASE.TENT; -- Удаляемая сущность
|
||||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||||
RRLS_TMP PKG_P8PANELS_QE_BASE.TRLS; -- Буфер для коллекции удаляемых связей
|
|
||||||
begin
|
begin
|
||||||
/* Провим права доступа */
|
/* Провим права доступа */
|
||||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
@ -218,20 +233,8 @@ create or replace package body PKG_P8PANELS_QE as
|
|||||||
if ((RENT.RATTRS is not null) and (RENT.RATTRS.COUNT > 0)) then
|
if ((RENT.RATTRS is not null) and (RENT.RATTRS.COUNT > 0)) then
|
||||||
for I in RENT.RATTRS.FIRST .. RENT.RATTRS.LAST
|
for I in RENT.RATTRS.FIRST .. RENT.RATTRS.LAST
|
||||||
loop
|
loop
|
||||||
/* Если атрибут есть в связях (как источник или как приёмник) */
|
/* Удаляем связи в которых он задействован */
|
||||||
for J in 0 .. 1
|
PKG_P8PANELS_QE_BASE.TRLS_CLEANUP_BY_ATTR(RRLS => RRLS, SATTR_ID => RENT.RATTRS(I).SID);
|
||||||
loop
|
|
||||||
RRLS_TMP := PKG_P8PANELS_QE_BASE.TRLS_LIST_BY_ST(RRLS => RRLS,
|
|
||||||
SSOURCE_TARGET => RENT.RATTRS(I).SID,
|
|
||||||
NLIST_TYPE => J);
|
|
||||||
/* То связь должна быть удалена */
|
|
||||||
if ((RRLS_TMP is not null) and (RRLS_TMP.COUNT > 0)) then
|
|
||||||
for K in RRLS_TMP.FIRST .. RRLS_TMP.LAST
|
|
||||||
loop
|
|
||||||
PKG_P8PANELS_QE_BASE.TRLS_REMOVE(RRLS => RRLS, SID => RRLS_TMP(K).SID);
|
|
||||||
end loop;
|
|
||||||
end if;
|
|
||||||
end loop;
|
|
||||||
end loop;
|
end loop;
|
||||||
end if;
|
end if;
|
||||||
/* Сохраняем обновленный набор сущностей */
|
/* Сохраняем обновленный набор сущностей */
|
||||||
@ -265,6 +268,77 @@ create or replace package body PKG_P8PANELS_QE as
|
|||||||
end if;
|
end if;
|
||||||
end QUERY_ENT_POSITION_SET;
|
end QUERY_ENT_POSITION_SET;
|
||||||
|
|
||||||
|
/* Получение состава атрибутов сущности */
|
||||||
|
procedure QUERY_ENT_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер запроса
|
||||||
|
SID in varchar2, -- Идентификатор сущности
|
||||||
|
COUT out clob -- Сериализованное описание атрибутов сущности
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Провим права доступа */
|
||||||
|
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Сформируем описание атрибутов */
|
||||||
|
PKG_P8PANELS_QE_BASE.QUERY_ENT_ATTRS_GET(NRN => NRN, SID => SID, COUT => COUT);
|
||||||
|
end QUERY_ENT_ATTRS_GET;
|
||||||
|
|
||||||
|
/* Установка состава атрибутов сущности */
|
||||||
|
procedure QUERY_ENT_ATTRS_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер запроса
|
||||||
|
SID in varchar2, -- Идентификатор сущности
|
||||||
|
CATTRS in clob -- Сериализованное описание атрибутов сущности (BASE64 XML)
|
||||||
|
)
|
||||||
|
is
|
||||||
|
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||||
|
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||||
|
NENT_INDEX PKG_STD.TNUMBER; -- Индекс изменяемой сущности
|
||||||
|
RATTRS PKG_P8PANELS_QE_BASE.TATTRS; -- Коллекция полученных атриубтов
|
||||||
|
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||||
|
begin
|
||||||
|
/* Провим права доступа */
|
||||||
|
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Читаем запись запроса */
|
||||||
|
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||||
|
/* Читаем существующие сущности */
|
||||||
|
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||||
|
/* Читаем свзяи */
|
||||||
|
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||||
|
/* Находим изменяемую сущность */
|
||||||
|
NENT_INDEX := PKG_P8PANELS_QE_BASE.TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID);
|
||||||
|
if (NENT_INDEX is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Сущность с идентификатором "%s" в запросе "%s" не определена.',
|
||||||
|
COALESCE(SID, '<НЕ УКАЗАН>'),
|
||||||
|
TO_CHAR(NRN));
|
||||||
|
end if;
|
||||||
|
/* Десериализуем новый набор атрибутов */
|
||||||
|
RATTRS := PKG_P8PANELS_QE_BASE.TATTRS_FROM_XML_BASE64(CXML => CATTRS,
|
||||||
|
SCHARSET => PKG_CHARSET.CHARSET_UTF_(),
|
||||||
|
BADD_ROOT => true);
|
||||||
|
/* Сбросим текущие атрибуты сущности */
|
||||||
|
RENTS(NENT_INDEX).RATTRS := PKG_P8PANELS_QE_BASE.TATTRS();
|
||||||
|
/* Наполним полученными и зачистим связи */
|
||||||
|
if (RATTRS is not null) and (RATTRS.COUNT > 0) then
|
||||||
|
for I in RATTRS.FIRST .. RATTRS.LAST
|
||||||
|
loop
|
||||||
|
/* Если атрибут используется - кладём в обновленную коллекцию атрибутов сущности */
|
||||||
|
if (RATTRS(I).NUSE = 1) then
|
||||||
|
RENTS(NENT_INDEX).RATTRS.EXTEND();
|
||||||
|
RENTS(NENT_INDEX).RATTRS(RENTS(NENT_INDEX).RATTRS.LAST) := RATTRS(I);
|
||||||
|
else
|
||||||
|
/* Атрибут не используется - необходимо очистись связи в которых он задействован */
|
||||||
|
PKG_P8PANELS_QE_BASE.TRLS_CLEANUP_BY_ATTR(RRLS => RRLS, SATTR_ID => RATTRS(I).SID);
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end if;
|
||||||
|
/* Сохраняем обновленный набор сущностей */
|
||||||
|
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||||
|
/* Сохраняем обновленный набор связей */
|
||||||
|
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||||
|
end QUERY_ENT_ATTRS_SET;
|
||||||
|
|
||||||
/* Добавление связи в запрос */
|
/* Добавление связи в запрос */
|
||||||
procedure QUERY_RL_ADD
|
procedure QUERY_RL_ADD
|
||||||
(
|
(
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
create or replace package PKG_P8PANELS_QE_BASE as
|
create or replace package PKG_P8PANELS_QE_BASE as
|
||||||
|
|
||||||
/* Константы - Типы сущностей */
|
|
||||||
SENT_TYPE_TABLE constant PKG_STD.TSTRING := 'TABLE'; -- Таблица
|
|
||||||
SENT_TYPE_VIEW constant PKG_STD.TSTRING := 'VIEW'; -- Представление
|
|
||||||
|
|
||||||
/* Типы данных - Атрибут сущности */
|
/* Типы данных - Атрибут сущности */
|
||||||
type TATTR is record
|
type TATTR is record
|
||||||
(
|
(
|
||||||
SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе
|
SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе
|
||||||
SNAME PKG_STD.TSTRING, -- Имя
|
SNAME PKG_STD.TSTRING, -- Имя
|
||||||
STITLE PKG_STD.TSTRING, -- Заголовок
|
STITLE PKG_STD.TSTRING, -- Заголовок
|
||||||
NDATA_TYPE PKG_STD.TNUMBER -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
|
NDATA_TYPE PKG_STD.TNUMBER, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
|
||||||
|
SAGG PKG_STD.TSTRING, -- Агрегатная функция
|
||||||
|
SALIAS PKG_STD.TSTRING, -- Псевдоним в выборке
|
||||||
|
NUSE PKG_STD.TNUMBER, -- Флаг применения в запросе (1 - да, 0 - нет)
|
||||||
|
NSHOW PKG_STD.TNUMBER -- Флаг отображения в выборке (1 - да, 0 - нет)
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Типы данных - Коллекция атрибутов сущности */
|
/* Типы данных - Коллекция атрибутов сущности */
|
||||||
@ -42,6 +42,14 @@ create or replace package PKG_P8PANELS_QE_BASE as
|
|||||||
/* Типы данных - Коллекция отношений */
|
/* Типы данных - Коллекция отношений */
|
||||||
type TRLS is table of TRL;
|
type TRLS is table of TRL;
|
||||||
|
|
||||||
|
/* Десериализация коллекции атрибутов сущности (из BASE64) */
|
||||||
|
function TATTRS_FROM_XML_BASE64
|
||||||
|
(
|
||||||
|
CXML in clob, -- XML-описание коллекции атрибутов сущности (BASE64)
|
||||||
|
SCHARSET in varchar2, -- Кодировка, в которой XML-данные были упакованы в BASE64
|
||||||
|
BADD_ROOT in boolean := false -- Флаг необходимости добавления корневого тэга (false - нет, true - да)
|
||||||
|
) return TATTRS; -- Коллекция атрибутов сущности
|
||||||
|
|
||||||
/* Поиск индекса сущности по идентификатору */
|
/* Поиск индекса сущности по идентификатору */
|
||||||
function TENTS_INDEX_BY_ID
|
function TENTS_INDEX_BY_ID
|
||||||
(
|
(
|
||||||
@ -73,14 +81,6 @@ create or replace package PKG_P8PANELS_QE_BASE as
|
|||||||
NY in number -- Координата по оси ординат
|
NY in number -- Координата по оси ординат
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Формирование коллекции связей по источнику/приёмнику */
|
|
||||||
function TRLS_LIST_BY_ST
|
|
||||||
(
|
|
||||||
RRLS in TRLS, -- Коллекция связей
|
|
||||||
SSOURCE_TARGET in varchar2, -- Идентификатор источника/приёмкника
|
|
||||||
NLIST_TYPE in number -- Тип формирования коллекции (0 - по источнику, 1 - по приёмнику
|
|
||||||
) return TRLS; -- Сформированная коллекция
|
|
||||||
|
|
||||||
/* Добавление связи в коллекцию */
|
/* Добавление связи в коллекцию */
|
||||||
procedure TRLS_APPEND
|
procedure TRLS_APPEND
|
||||||
(
|
(
|
||||||
@ -96,6 +96,13 @@ create or replace package PKG_P8PANELS_QE_BASE as
|
|||||||
SID in varchar2 -- Идентификатор удялемой связи
|
SID in varchar2 -- Идентификатор удялемой связи
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Подчистка коллекции связей по атрибуту */
|
||||||
|
procedure TRLS_CLEANUP_BY_ATTR
|
||||||
|
(
|
||||||
|
RRLS in out nocopy TRLS, -- Изменяемая коллекция
|
||||||
|
SATTR_ID in varchar2 -- Идентификатор атрибута
|
||||||
|
);
|
||||||
|
|
||||||
/* Считывание записи запроса */
|
/* Считывание записи запроса */
|
||||||
function QUERY_GET
|
function QUERY_GET
|
||||||
(
|
(
|
||||||
@ -177,6 +184,14 @@ create or replace package PKG_P8PANELS_QE_BASE as
|
|||||||
RENTS in TENTS -- Коллекция сущностей
|
RENTS in TENTS -- Коллекция сущностей
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Получение состава атрибутов сущности */
|
||||||
|
procedure QUERY_ENT_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер запроса
|
||||||
|
SID in varchar2, -- Идентификатор сущности
|
||||||
|
COUT out clob -- Сериализованное описание атрибутов сущности
|
||||||
|
);
|
||||||
|
|
||||||
/* Чтение связей запроса */
|
/* Чтение связей запроса */
|
||||||
function QUERY_RLS_GET
|
function QUERY_RLS_GET
|
||||||
(
|
(
|
||||||
@ -208,6 +223,10 @@ end PKG_P8PANELS_QE_BASE;
|
|||||||
/
|
/
|
||||||
create or replace package body PKG_P8PANELS_QE_BASE as
|
create or replace package body PKG_P8PANELS_QE_BASE as
|
||||||
|
|
||||||
|
/* Константы - Типы сущностей */
|
||||||
|
SENT_TYPE_TABLE constant PKG_STD.TSTRING := 'TABLE'; -- Таблица
|
||||||
|
SENT_TYPE_VIEW constant PKG_STD.TSTRING := 'VIEW'; -- Представление
|
||||||
|
|
||||||
/* Константы - Теги для сериализации */
|
/* Константы - Теги для сериализации */
|
||||||
STAG_DATA constant PKG_STD.TSTRING := 'XDATA'; -- Данные
|
STAG_DATA constant PKG_STD.TSTRING := 'XDATA'; -- Данные
|
||||||
STAG_QUERIES constant PKG_STD.TSTRING := 'XQUERIES'; -- Запросы
|
STAG_QUERIES constant PKG_STD.TSTRING := 'XQUERIES'; -- Запросы
|
||||||
@ -238,6 +257,10 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
SATTR_Y constant PKG_STD.TSTRING := 'y'; -- Координата по Y
|
SATTR_Y constant PKG_STD.TSTRING := 'y'; -- Координата по Y
|
||||||
SATTR_SOURCE constant PKG_STD.TSTRING := 'source'; -- Источник
|
SATTR_SOURCE constant PKG_STD.TSTRING := 'source'; -- Источник
|
||||||
SATTR_TARGET constant PKG_STD.TSTRING := 'target'; -- Приёмник
|
SATTR_TARGET constant PKG_STD.TSTRING := 'target'; -- Приёмник
|
||||||
|
SATTR_AGG constant PKG_STD.TSTRING := 'agg'; -- Агрегатная функция
|
||||||
|
SATTR_ALIAS constant PKG_STD.TSTRING := 'alias'; -- Псевдоним
|
||||||
|
SATTR_USE constant PKG_STD.TSTRING := 'use'; -- Применение в запросе
|
||||||
|
SATTR_SHOW constant PKG_STD.TSTRING := 'show'; -- Отображение в запросе
|
||||||
|
|
||||||
/* Получение заголовка представления из метаданных */
|
/* Получение заголовка представления из метаданных */
|
||||||
function DMSCLVIEWS_TITLE_GET
|
function DMSCLVIEWS_TITLE_GET
|
||||||
@ -328,6 +351,10 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RATTR.SNAME);
|
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RATTR.SNAME);
|
||||||
PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RATTR.STITLE);
|
PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RATTR.STITLE);
|
||||||
PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RATTR.NDATA_TYPE);
|
PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RATTR.NDATA_TYPE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_AGG, SVALUE => RATTR.SAGG);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_ALIAS, SVALUE => RATTR.SALIAS);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_USE, NVALUE => RATTR.NUSE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_SHOW, NVALUE => RATTR.NSHOW);
|
||||||
/* Закрываем описание атрибута сущности */
|
/* Закрываем описание атрибута сущности */
|
||||||
PKG_XFAST.UP();
|
PKG_XFAST.UP();
|
||||||
end TATTR_TO_XML;
|
end TATTR_TO_XML;
|
||||||
@ -356,6 +383,10 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
RRES.SNAME := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME);
|
RRES.SNAME := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME);
|
||||||
RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE);
|
RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE);
|
||||||
RRES.NDATA_TYPE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE);
|
RRES.NDATA_TYPE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE);
|
||||||
|
RRES.SAGG := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_AGG);
|
||||||
|
RRES.SALIAS := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ALIAS);
|
||||||
|
RRES.NUSE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_USE);
|
||||||
|
RRES.NSHOW := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_SHOW);
|
||||||
/* Освободим документ */
|
/* Освободим документ */
|
||||||
PKG_XPATH.FREE(RDOCUMENT => XDOC);
|
PKG_XPATH.FREE(RDOCUMENT => XDOC);
|
||||||
exception
|
exception
|
||||||
@ -371,6 +402,119 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
return RRES;
|
return RRES;
|
||||||
end TATTR_FROM_XML;
|
end TATTR_FROM_XML;
|
||||||
|
|
||||||
|
/* Поиск индекса атрибута по имени */
|
||||||
|
function TATTRS_INDEX_BY_NAME
|
||||||
|
(
|
||||||
|
RATTRS in TATTRS, -- Коллекция атрибутов
|
||||||
|
SNAME in varchar2 -- Искомое имя
|
||||||
|
) return number -- Индекс найденного атрибута (null - если не найдено)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Обходим коллекцию */
|
||||||
|
if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then
|
||||||
|
for I in RATTRS.FIRST .. RATTRS.LAST
|
||||||
|
loop
|
||||||
|
begin
|
||||||
|
/* Возвращаем найденный индекс */
|
||||||
|
if (RATTRS(I).SNAME = SNAME) then
|
||||||
|
return I;
|
||||||
|
end if;
|
||||||
|
exception
|
||||||
|
when NO_DATA_FOUND then
|
||||||
|
null;
|
||||||
|
end;
|
||||||
|
end loop;
|
||||||
|
end if;
|
||||||
|
/* Ничего не нашли */
|
||||||
|
return null;
|
||||||
|
end TATTRS_INDEX_BY_NAME;
|
||||||
|
|
||||||
|
/* Формирование списка атрибутов сущности по типу и наименованию */
|
||||||
|
function TATTRS_MAKE
|
||||||
|
(
|
||||||
|
RENT in TENT, -- Родительская сущность
|
||||||
|
NCOUNT in number := null -- Количество атрибутов (null - все)
|
||||||
|
) return TATTRS -- Коллекция атрибутов сущности
|
||||||
|
is
|
||||||
|
RRES TATTRS; -- Буфер для результата
|
||||||
|
RVIEW_FIELDS PKG_OBJECT_DESC.TCOLUMNS; -- Коллекция описаний полей представления
|
||||||
|
RVIEW_FIELD PKG_OBJECT_DESC.TCOLUMN; -- Описание поля представления
|
||||||
|
NATTR_INDEX PKG_STD.TNUMBER; -- Индекс поля в коллекции атрибутов сущности
|
||||||
|
BINIT boolean := false; -- Признак инициализации атрибутов сущности
|
||||||
|
begin
|
||||||
|
/* Проверим корректность типа сущности */
|
||||||
|
if (RENT.STYPE not in (SENT_TYPE_TABLE, SENT_TYPE_VIEW)) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Сущности типа "%s" не поддерживаются.',
|
||||||
|
COALESCE(RENT.STYPE, '<НЕ УКАЗАН>'));
|
||||||
|
end if;
|
||||||
|
/* Проверим, что у сущности задан идентификатор */
|
||||||
|
if (RENT.SID is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Ошибка формирования атрибутов - не задан идентификатор сущности.');
|
||||||
|
end if;
|
||||||
|
/* Проверим, что у сущности задано имя */
|
||||||
|
if (RENT.SNAME is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Ошибка формирования атрибутов - не задано имя сущности.');
|
||||||
|
end if;
|
||||||
|
/* Инициализируем результат */
|
||||||
|
RRES := TATTRS();
|
||||||
|
/* Установим флаг инициализации - в сущности нет атрибутов и нас просили ограничить количество */
|
||||||
|
if (((RENT.RATTRS is null) or (RENT.RATTRS.COUNT = 0)) and (NCOUNT is not null)) then
|
||||||
|
BINIT := true;
|
||||||
|
end if;
|
||||||
|
/* Если сущность это представление */
|
||||||
|
if (RENT.STYPE = SENT_TYPE_VIEW) then
|
||||||
|
/* Получим список полей представления */
|
||||||
|
RVIEW_FIELDS := PKG_OBJECT_DESC.DESC_SEL_COLUMNS(SSELECT_NAME => RENT.SNAME, BRAISE_ERROR => true);
|
||||||
|
/* Собираем атрибуты в ответ */
|
||||||
|
for I in 1 .. PKG_OBJECT_DESC.COUNT_COLUMNS(RCOLUMNS => RVIEW_FIELDS)
|
||||||
|
loop
|
||||||
|
/* Считываем очередное поле из коллекции описания полей представления */
|
||||||
|
RVIEW_FIELD := PKG_OBJECT_DESC.FETCH_COLUMN(RCOLUMNS => RVIEW_FIELDS, IINDEX => I);
|
||||||
|
/* Если поле поддерживаемого типа */
|
||||||
|
if (RVIEW_FIELD.DATA_TYPE in (PKG_STD.DATA_TYPE_STR(), PKG_STD.DATA_TYPE_NUM(), PKG_STD.DATA_TYPE_DATE())) then
|
||||||
|
/* Добавляем элемент в результирующую коллекцию */
|
||||||
|
RRES.EXTEND();
|
||||||
|
/* Ищем такой атрибут в родительской сущности */
|
||||||
|
NATTR_INDEX := TATTRS_INDEX_BY_NAME(RATTRS => RENT.RATTRS, SNAME => RVIEW_FIELD.COLUMN_NAME);
|
||||||
|
/* Если это поле уже есть в коллекции атрибутов родительской сущности */
|
||||||
|
if (NATTR_INDEX is not null) then
|
||||||
|
/* Возьмём элемент оттуда */
|
||||||
|
RRES(RRES.LAST) := RENT.RATTRS(NATTR_INDEX);
|
||||||
|
else
|
||||||
|
/* Такого элемента нет - берем из представления */
|
||||||
|
RRES(RRES.LAST).SID := TATTR_ID_MAKE(SENT_ID => RENT.SID, SNAME => RVIEW_FIELD.COLUMN_NAME);
|
||||||
|
RRES(RRES.LAST).SNAME := RVIEW_FIELD.COLUMN_NAME;
|
||||||
|
RRES(RRES.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME,
|
||||||
|
SATTR_NAME => RRES(RRES.LAST).SNAME);
|
||||||
|
RRES(RRES.LAST).NDATA_TYPE := RVIEW_FIELD.DATA_TYPE;
|
||||||
|
/* Если это инициализиция сущности - установим флаг применения в запросе (тогда атрибут будет отображен в диаграмме) */
|
||||||
|
if (BINIT) then
|
||||||
|
RRES(RRES.LAST).NUSE := 1;
|
||||||
|
RRES(RRES.LAST).NSHOW := 1;
|
||||||
|
else
|
||||||
|
RRES(RRES.LAST).NUSE := 0;
|
||||||
|
RRES(RRES.LAST).NSHOW := 0;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
/* Ограничим объем коллекции если необходимо */
|
||||||
|
if (NCOUNT is not null) then
|
||||||
|
exit when RRES.LAST = NCOUNT;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end if;
|
||||||
|
/* Если сущность это таблица */
|
||||||
|
if (RENT.STYPE = SENT_TYPE_TABLE) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Поддержка формирования атрибутов для сущностей типа "Таблица" ещё не реализована.');
|
||||||
|
end if;
|
||||||
|
/* Возвращаем результат */
|
||||||
|
return RRES;
|
||||||
|
end TATTRS_MAKE;
|
||||||
|
|
||||||
/* Сериализация коллекции атрибутов сущности */
|
/* Сериализация коллекции атрибутов сущности */
|
||||||
procedure TATTRS_TO_XML
|
procedure TATTRS_TO_XML
|
||||||
(
|
(
|
||||||
@ -439,6 +583,26 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
return RRES;
|
return RRES;
|
||||||
end TATTRS_FROM_XML;
|
end TATTRS_FROM_XML;
|
||||||
|
|
||||||
|
/* Десериализация коллекции атрибутов сущности (из BASE64) */
|
||||||
|
function TATTRS_FROM_XML_BASE64
|
||||||
|
(
|
||||||
|
CXML in clob, -- XML-описание коллекции атрибутов сущности (BASE64)
|
||||||
|
SCHARSET in varchar2, -- Кодировка, в которой XML-данные были упакованы в BASE64
|
||||||
|
BADD_ROOT in boolean := false -- Флаг необходимости добавления корневого тэга (false - нет, true - да)
|
||||||
|
) return TATTRS -- Коллекция атрибутов сущности
|
||||||
|
is
|
||||||
|
CTMP clob; -- Буфер для преобразований
|
||||||
|
begin
|
||||||
|
/* Избавимся от BASE64 */
|
||||||
|
CTMP := BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CXML), SCHARSET => SCHARSET);
|
||||||
|
/* Если надо - добавим корень */
|
||||||
|
if (BADD_ROOT) then
|
||||||
|
CTMP := '<' || STAG_ATTRS || '>' || CTMP || '</' || STAG_ATTRS || '>';
|
||||||
|
end if;
|
||||||
|
/* Десериализуем */
|
||||||
|
return TATTRS_FROM_XML(CXML => CTMP);
|
||||||
|
end TATTRS_FROM_XML_BASE64;
|
||||||
|
|
||||||
/* Формирование идентификатора сущности */
|
/* Формирование идентификатора сущности */
|
||||||
function TENT_ID_MAKE
|
function TENT_ID_MAKE
|
||||||
(
|
(
|
||||||
@ -469,8 +633,6 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
is
|
is
|
||||||
RENT TENT; -- Буфер для результата
|
RENT TENT; -- Буфер для результата
|
||||||
RVIEW PKG_OBJECT_DESC.TVIEW; -- Описание представления
|
RVIEW PKG_OBJECT_DESC.TVIEW; -- Описание представления
|
||||||
RVIEW_FIELDS PKG_OBJECT_DESC.TCOLUMNS; -- Коллекция описаний полей представления
|
|
||||||
RVIEW_FIELD PKG_OBJECT_DESC.TCOLUMN; -- Описание поля представления
|
|
||||||
begin
|
begin
|
||||||
/* Проверим корректность типа сущности */
|
/* Проверим корректность типа сущности */
|
||||||
if (STYPE not in (SENT_TYPE_TABLE, SENT_TYPE_VIEW)) then
|
if (STYPE not in (SENT_TYPE_TABLE, SENT_TYPE_VIEW)) then
|
||||||
@ -482,30 +644,13 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
if (STYPE = SENT_TYPE_VIEW) then
|
if (STYPE = SENT_TYPE_VIEW) then
|
||||||
/* Получим описание представления */
|
/* Получим описание представления */
|
||||||
RVIEW := PKG_OBJECT_DESC.DESC_VIEW(SVIEW_NAME => SNAME, BRAISE_ERROR => true);
|
RVIEW := PKG_OBJECT_DESC.DESC_VIEW(SVIEW_NAME => SNAME, BRAISE_ERROR => true);
|
||||||
/* Получим список полей представления */
|
|
||||||
RVIEW_FIELDS := PKG_OBJECT_DESC.DESC_SEL_COLUMNS(SSELECT_NAME => SNAME, BRAISE_ERROR => true);
|
|
||||||
/* Собираем заголовок сущности */
|
/* Собираем заголовок сущности */
|
||||||
RENT.SID := TENT_ID_MAKE(SNAME => RVIEW.VIEW_NAME, NNUMB => NNUMB);
|
RENT.SID := TENT_ID_MAKE(SNAME => RVIEW.VIEW_NAME, NNUMB => NNUMB);
|
||||||
RENT.SNAME := RVIEW.VIEW_NAME;
|
RENT.SNAME := RVIEW.VIEW_NAME;
|
||||||
RENT.STITLE := DMSCLVIEWS_TITLE_GET(SVIEW_NAME => RENT.SNAME);
|
RENT.STITLE := DMSCLVIEWS_TITLE_GET(SVIEW_NAME => RENT.SNAME);
|
||||||
RENT.STYPE := SENT_TYPE_VIEW;
|
RENT.STYPE := SENT_TYPE_VIEW;
|
||||||
RENT.RATTRS := TATTRS();
|
/* Формируем набор атрибутов */
|
||||||
/* Собираем атрибуты в ответ */
|
RENT.RATTRS := TATTRS_MAKE(RENT => RENT, NCOUNT => 10);
|
||||||
for I in 1 .. PKG_OBJECT_DESC.COUNT_COLUMNS(RCOLUMNS => RVIEW_FIELDS)
|
|
||||||
loop
|
|
||||||
/* По умолчанию - первые 10 */
|
|
||||||
exit when I > 10;
|
|
||||||
/* Считываем очередное поле из коллекции описания полей представления */
|
|
||||||
RVIEW_FIELD := PKG_OBJECT_DESC.FETCH_COLUMN(RCOLUMNS => RVIEW_FIELDS, IINDEX => I);
|
|
||||||
/* Формируем описание поля и добавляем в коллекцию атрибутов сущности */
|
|
||||||
RENT.RATTRS.EXTEND();
|
|
||||||
RENT.RATTRS(RENT.RATTRS.LAST).SID := TATTR_ID_MAKE(SENT_ID => RENT.SID, SNAME => RVIEW_FIELD.COLUMN_NAME);
|
|
||||||
RENT.RATTRS(RENT.RATTRS.LAST).SNAME := RVIEW_FIELD.COLUMN_NAME;
|
|
||||||
RENT.RATTRS(RENT.RATTRS.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME,
|
|
||||||
SATTR_NAME => RENT.RATTRS(RENT.RATTRS.LAST)
|
|
||||||
.SNAME);
|
|
||||||
RENT.RATTRS(RENT.RATTRS.LAST).NDATA_TYPE := RVIEW_FIELD.DATA_TYPE;
|
|
||||||
end loop;
|
|
||||||
end if;
|
end if;
|
||||||
/* Если сущность это таблица */
|
/* Если сущность это таблица */
|
||||||
if (STYPE = SENT_TYPE_TABLE) then
|
if (STYPE = SENT_TYPE_TABLE) then
|
||||||
@ -945,6 +1090,29 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
end if;
|
end if;
|
||||||
end TRLS_REMOVE;
|
end TRLS_REMOVE;
|
||||||
|
|
||||||
|
/* Подчистка коллекции связей по атрибуту */
|
||||||
|
procedure TRLS_CLEANUP_BY_ATTR
|
||||||
|
(
|
||||||
|
RRLS in out nocopy TRLS, -- Изменяемая коллекция
|
||||||
|
SATTR_ID in varchar2 -- Идентификатор атрибута
|
||||||
|
)
|
||||||
|
is
|
||||||
|
RRLS_TMP PKG_P8PANELS_QE_BASE.TRLS; -- Буфер для коллекции удаляемых связей
|
||||||
|
begin
|
||||||
|
/* Если атрибут есть в связях (как источник или как приёмник) */
|
||||||
|
for J in 0 .. 1
|
||||||
|
loop
|
||||||
|
RRLS_TMP := TRLS_LIST_BY_ST(RRLS => RRLS, SSOURCE_TARGET => SATTR_ID, NLIST_TYPE => J);
|
||||||
|
/* То связь должна быть удалена */
|
||||||
|
if ((RRLS_TMP is not null) and (RRLS_TMP.COUNT > 0)) then
|
||||||
|
for K in RRLS_TMP.FIRST .. RRLS_TMP.LAST
|
||||||
|
loop
|
||||||
|
TRLS_REMOVE(RRLS => RRLS, SID => RRLS_TMP(K).SID);
|
||||||
|
end loop;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end TRLS_CLEANUP_BY_ATTR;
|
||||||
|
|
||||||
/* Сериализация коллекции связей */
|
/* Сериализация коллекции связей */
|
||||||
procedure TRLS_TO_XML
|
procedure TRLS_TO_XML
|
||||||
(
|
(
|
||||||
@ -1484,6 +1652,56 @@ create or replace package body PKG_P8PANELS_QE_BASE as
|
|||||||
QUERY_CH_DATE_SYNC(NRN => NRN);
|
QUERY_CH_DATE_SYNC(NRN => NRN);
|
||||||
end QUERY_ENTS_SET;
|
end QUERY_ENTS_SET;
|
||||||
|
|
||||||
|
/* Получение состава атрибутов сущности */
|
||||||
|
procedure QUERY_ENT_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер запроса
|
||||||
|
SID in varchar2, -- Идентификатор сущности
|
||||||
|
COUT out clob -- Сериализованное описание атрибутов сущности
|
||||||
|
)
|
||||||
|
is
|
||||||
|
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||||
|
NENT_INDEX PKG_STD.TNUMBER; -- Индекс изменяемой сущности в коллекции
|
||||||
|
RENTS TENTS; -- Коллекция существующих сущностей
|
||||||
|
RENT TENT; -- Изменяемая сущность
|
||||||
|
RATTRS TATTRS; -- Коллекция атрибутов изменяемой сущности
|
||||||
|
begin
|
||||||
|
/* Читаем запись запроса */
|
||||||
|
RQ := QUERY_GET(NRN => NRN);
|
||||||
|
/* Читаем существующие сущности */
|
||||||
|
RENTS := QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||||
|
/* Читаем изменяемую сущность */
|
||||||
|
NENT_INDEX := TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID);
|
||||||
|
if (NENT_INDEX is not null) then
|
||||||
|
RENT := RENTS(NENT_INDEX);
|
||||||
|
else
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Сущность с идентификатором "%s" не определена.',
|
||||||
|
COALESCE(SID, '<НЕ УКАЗАН>'));
|
||||||
|
end if;
|
||||||
|
/* Получим полный набор атрибутов сущности */
|
||||||
|
RATTRS := TATTRS_MAKE(RENT => RENT);
|
||||||
|
/* Начинаем формирование XML */
|
||||||
|
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
|
||||||
|
/* Открываем корень */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
|
||||||
|
/* Формируем описание тарибутов */
|
||||||
|
TATTRS_TO_XML(RATTRS => RATTRS);
|
||||||
|
/* Закрываем корень */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Сериализуем */
|
||||||
|
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
exception
|
||||||
|
when others then
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
/* Вернем ошибку */
|
||||||
|
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||||
|
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||||
|
end QUERY_ENT_ATTRS_GET;
|
||||||
|
|
||||||
/* Чтение связей запроса */
|
/* Чтение связей запроса */
|
||||||
function QUERY_RLS_GET
|
function QUERY_RLS_GET
|
||||||
(
|
(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user