Skip to content

Commit

Permalink
Merge pull request #12 from GridProtectionAlliance/DataSourceImprovem…
Browse files Browse the repository at this point in the history
…ents

Data source improvements
  • Loading branch information
clackner-gpa authored Jun 28, 2024
2 parents be6cf5e + df13232 commit daece77
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 224 deletions.
4 changes: 2 additions & 2 deletions TrenDAP.sql
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ CREATE TABLE DataSource
RegistrationKey VARCHAR(50) NOT NULL UNIQUE,
APIToken VARCHAR(50) NOT NULL,
Expires DATETIME NULL,
Settings VARCHAR(MAX) NOT NULL DEFAULT '{}'
SettingsString VARCHAR(MAX) NOT NULL DEFAULT '{}'
)
GO

Expand Down Expand Up @@ -104,7 +104,7 @@ CREATE TABLE DataSourceDataSet
ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
DataSourceID INT NOT NULL REFERENCES DataSource(ID),
DataSetID INT NOT NULL REFERENCES DataSet(ID),
Settings VARCHAR(MAX) NOT NULL DEFAULT '{}'
SettingsString VARCHAR(MAX) NOT NULL DEFAULT '{}'
)
GO

Expand Down
12 changes: 3 additions & 9 deletions TrenDAP/Model/DataSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,7 @@ public class DataSet

public class DataSetController : ModelController<DataSet>
{
#region [ Properties ]
private IConfiguration Configuration { get; }
#endregion

public DataSetController(IConfiguration configuration) : base(configuration)
{
Configuration = configuration;
}
public DataSetController(IConfiguration configuration) : base(configuration) {}

public override ActionResult Post([FromBody] JObject record)
{
Expand All @@ -104,8 +97,9 @@ public ActionResult PostConnections([FromBody] JObject record)
JArray connections = (JArray)record.GetValue("Connections");
foreach (JObject conn in connections)
{
conn["SettingsString"] = record["Settings"].ToString();
conn["DataSetID"] = dataSetId;
DataSourceDataSet connRecord = conn.ToObject<DataSourceDataSet>();
connRecord.DataSetID = dataSetId;
result += new TableOperations<DataSourceDataSet>(connection).AddNewRecord(connRecord);
}
return Ok(result);
Expand Down
29 changes: 24 additions & 5 deletions TrenDAP/Model/DataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;
using openXDA.APIAuthentication;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -55,22 +56,40 @@ public class DataSource
public bool Public { get; set; }
[UseEscapedName]
public string User { get; set; }
public string Settings { get; set; }

public string SettingsString { get; set; }
[NonRecordField]
public JObject Settings
{
get
{
try { return JObject.Parse(SettingsString); }
catch { return new JObject(); }
}
}
public static DataSource GetDataSource(IConfiguration configuration, int id)
{
using (AdoDataConnection connection = new AdoDataConnection(configuration["SystemSettings:ConnectionString"], configuration["SystemSettings:DataProviderString"]))
{
return new Gemstone.Data.Model.TableOperations<DataSource>(connection).QueryRecordWhere("ID = {0}", id);
}
}

}

public class DataSourceController: ModelController<DataSource>
{
public DataSourceController(IConfiguration configuration) : base(configuration){}

public DataSourceController(IConfiguration configuration) : base(configuration){ }

public override ActionResult Post([FromBody] JObject record)
{
record["SettingsString"] = record["Settings"].ToString();
return base.Post(record);
}
public override ActionResult Patch([FromBody] JObject record)
{
record["SettingsString"] = record["Settings"].ToString();
return base.Patch(record);
}

[HttpGet, Route("TestAuth/{dataSourceID:int}")]
public ActionResult TestAuth(int dataSourceID)
{
Expand Down
25 changes: 22 additions & 3 deletions TrenDAP/Model/DataSourceDataSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,33 @@ public class DataSourceDataSet
public int ID { get; set; }
public int DataSourceID { get; set; }
public int DataSetID { get; set; }
public string Settings { get; set; }
public string SettingsString { get; set; }
[NonRecordField]
public JObject Settings
{
get
{
try { return JObject.Parse(SettingsString); }
catch { return new JObject(); }
}
}
}

public class DataSourceDataSetController : ModelController<DataSourceDataSet>
{
public DataSourceDataSetController(IConfiguration configuration) : base(configuration) { }

public override ActionResult Post([FromBody] JObject record)
{
record["SettingsString"] = record["Settings"].ToString();
return base.Post(record);
}
public override ActionResult Patch([FromBody] JObject record)
{
record["SettingsString"] = record["Settings"].ToString();
return base.Patch(record);
}

[HttpGet, Route("Query/{dataSourceDataSetID:int}")]
public IActionResult GetData(int dataSourceDataSetID, CancellationToken cancellationToken)
{
Expand All @@ -61,8 +81,7 @@ public IActionResult GetData(int dataSourceDataSetID, CancellationToken cancella
DataSet dataSet = new TableOperations<DataSet>(connection).QueryRecordWhere("ID = {0}", sourceSet.DataSetID);
DataSource dataSource = new TableOperations<DataSource>(connection).QueryRecordWhere("ID = {0}", sourceSet.DataSourceID);
if (dataSet is null || dataSource is null) return BadRequest("Failure loading data source or data set.");
JObject data = JObject.Parse(sourceSet.Settings);
return Query(dataSet, dataSource, data, cancellationToken);
return Query(dataSet, dataSource, sourceSet.Settings, cancellationToken);
}
}

Expand Down
139 changes: 68 additions & 71 deletions TrenDAP/wwwroot/TypeScript/Features/DataSets/AddNewDataSet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const AddNewDataSet: React.FunctionComponent<{}> = (props) => {

const [warnings, setWarning] = React.useState<string[]>([]);
const [errors, setErrors] = React.useState<string[]>([]);
const [sourceErrors, setSourceErrors] = React.useState<string[]>([]);
const [hover, setHover] = React.useState<boolean>(false);
const [dataSet, setDataSet] = React.useState<TrenDAP.iDataSet>(SelectNewDataSet());
const [connections, setConnections] = React.useState<DataSourceTypes.IDataSourceDataSet[]>([]);
Expand All @@ -64,11 +65,10 @@ const AddNewDataSet: React.FunctionComponent<{}> = (props) => {
if (dataSet.Context == 'Relative' && dataSet.RelativeWindow == 'Day' && dataSet.RelativeValue < 366)
w.push("With the current Time Context and Week of Year Filter it is possible for the dataset to be empty at times.")
setWarning(w);
}, [dataSet])
}, [dataSet]);

React.useEffect(() => {
const e = [];

if (dataSet.Name == null || dataSet.Name.trim().length == 0)
e.push("A Name has to be entered.")
if (dataSet.Name != null && dataSet.Name.length > 200)
Expand All @@ -87,81 +87,78 @@ const AddNewDataSet: React.FunctionComponent<{}> = (props) => {
e.push("At least 1 Week has to be selected.")
if (connections.length == 0)
e.push("At least 1 DataSource needs to be added.");
setErrors(e);
}, [dataSet, connections]);
setErrors(e.concat(sourceErrors));
}, [dataSet, connections, sourceErrors]);

return (
<>
<div className="row" style={{ margin: 10 }}>
<div className="card" style={{ width: '100%', height: window.innerHeight - 60 }}>
<div className="card-header">
New Data Set {dataSet.Name !== null && dataSet.Name.trim().length > 0 ? ('(' + dataSet.Name + ')') : ''}
</div>
<div className="card-body" style={{ overflowY: 'auto' }}>
<TabSelector Tabs={[
{ Label: 'Settings', Id: 'settings' },
...connections.map((item, index) => ({
Label: dataSources.find(ds => ds.ID === item.DataSourceID)?.Name,
Id: dataSources.find(ds => ds.ID === item.DataSourceID)?.Name + index.toString(),
})),
]}
SetTab={(item) => setTab(item)} CurrentTab={tab} />
<DataSet DataSet={dataSet} SetDataSet={setDataSet} Connections={connections} SetConnections={setConnections} Tab={tab} />
</div>
<div className="card-footer">
<div className="row">
<div className="d-flex col-6 justify-content-start">
<button type="button" data-tooltip="newBtn"
className={"btn btn-success" + (errors.length > 0 ? ' disabled' : '')}
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
onClick={() => {
if (errors.length > 0)
return;
const handle = $.ajax({
type: "POST",
url: `${homePath}api/DataSet/NewWithConnections`,
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({
DataSet: {
...dataSet, UpdatedOn: moment.utc().format('MM/DD/YYYY HH:mm:ss')
},
Connections: connections
}),
cache: false,
async: true
}).done(() => {
dispatch(FetchDataSourceDataSets());
dispatch(FetchDataSets());
}).done(() => {
navigate(`${homePath}DataSets`);
});
return () => { if (handle != null && handle.abort != null) handle.abort(); }
}}
> Save</button>
</div>
<ToolTip Target="newBtn" Show={hover && (warnings.length > 0 || errors.length > 0)} Position={'top'}>
{warnings.map((w, i) => <p key={2 * i}>{Warning} {w} </p>)}
{errors.map((e, i) => <p key={2 * i + 1}>{CrossMark} {e} </p>)}
</ToolTip>
{tab !== 'settings' ?
<div className="d-flex col-6 justify-content-end">
<button className='btn btn-danger' onClick={() => {
let deletedConnectionIdx = connections.findIndex((con, index) => dataSources.find(ds => ds.ID === con.DataSourceID)?.Name + index.toString() === tab)
let newConnections = _.cloneDeep(connections);
newConnections.splice(deletedConnectionIdx, 1);
setConnections(newConnections);
setTab('settings');
}}
>Remove DataSource</button>
</div> : null
}
<div className="row" style={{ margin: 10 }}>
<div className="card" style={{ width: '100%', height: window.innerHeight - 60 }}>
<div className="card-header">
New Data Set {dataSet.Name !== null && dataSet.Name.trim().length > 0 ? ('(' + dataSet.Name + ')') : ''}
</div>
<div className="card-body" style={{ overflowY: 'auto' }}>
<TabSelector Tabs={[
{ Label: 'Settings', Id: 'settings' },
...connections.map((item, index) => ({
Label: dataSources.find(ds => ds.ID === item.DataSourceID)?.Name,
Id: dataSources.find(ds => ds.ID === item.DataSourceID)?.Name + index.toString(),
})),
]}
SetTab={(item) => setTab(item)} CurrentTab={tab} />
<DataSet DataSet={dataSet} SetDataSet={setDataSet} Connections={connections} SetConnections={setConnections} Tab={tab} SetErrors={setSourceErrors} />
</div>
<div className="card-footer">
<div className="row">
<div className="d-flex col-6 justify-content-start">
<button type="button" data-tooltip="newBtn"
className={"btn btn-success" + (errors.length > 0 ? ' disabled' : '')}
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
onClick={() => {
if (errors.length > 0)
return;
const handle = $.ajax({
type: "POST",
url: `${homePath}api/DataSet/NewWithConnections`,
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({
DataSet: {
...dataSet, UpdatedOn: moment.utc().format('MM/DD/YYYY HH:mm:ss')
},
Connections: connections
}),
cache: false,
async: true
}).done(() => {
dispatch(FetchDataSourceDataSets());
dispatch(FetchDataSets());
}).done(() => {
navigate(`${homePath}DataSets`);
});
return () => { if (handle != null && handle.abort != null) handle.abort(); }
}}
> Save</button>
</div>
<ToolTip Target="newBtn" Show={hover && (warnings.length > 0 || errors.length > 0)} Position={'top'}>
{warnings.map((w, i) => <p key={2 * i}>{Warning} {w} </p>)}
{errors.map((e, i) => <p key={2 * i + 1}>{CrossMark} {e} </p>)}
</ToolTip>
{tab !== 'settings' ?
<div className="d-flex col-6 justify-content-end">
<button className='btn btn-danger' onClick={() => {
let deletedConnectionIdx = connections.findIndex((con, index) => dataSources.find(ds => ds.ID === con.DataSourceID)?.Name + index.toString() === tab)
let newConnections = _.cloneDeep(connections);
newConnections.splice(deletedConnectionIdx, 1);
setConnections(newConnections);
setTab('settings');
}}
>Remove DataSource</button>
</div> : null
}
</div>
</div>
</div>

</>
</div>
);
}

Expand Down
44 changes: 25 additions & 19 deletions TrenDAP/wwwroot/TypeScript/Features/DataSets/DataSet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ interface IProps {
SetDataSet: (ws: TrenDAP.iDataSet) => void,
Connections: DataSourceTypes.IDataSourceDataSet[],
SetConnections: (arg: DataSourceTypes.IDataSourceDataSet[]) => void,
Tab: string
Tab: string,
SetErrors: (e: string[]) => void
}

const DataSet: React.FunctionComponent<IProps> = (props: IProps) => {
const dispatch = useAppDispatch();
const dataSources = useAppSelector(SelectDataSources);
const dsStatus = useAppSelector(SelectDataSourcesStatus);
const wrapperErrors = React.useRef<Map<string, string[]>>(new Map<string, string[]>());

React.useEffect(() => {
if (dsStatus === 'unitiated' || dsStatus === 'changed') dispatch(FetchDataSources());
Expand All @@ -56,33 +58,37 @@ const DataSet: React.FunctionComponent<IProps> = (props: IProps) => {
props.SetConnections(newConns);
}, [props.Connections, props.SetConnections]);

const deleteConn = React.useCallback((index: number) => {
const newConns = [...props.Connections];
if (index < 0 || index >= newConns.length) {
console.error(`Could not find connection ${index} in connection array.`);
return;
}
newConns.splice(index, 1);
props.SetConnections(newConns);
}, [props.Connections, props.SetConnections]);
const setSourceErrors = React.useCallback((newErrors: string[], name: string) => {
if (newErrors.length > 0) wrapperErrors.current.set(name, newErrors);
else wrapperErrors.current.delete(name);

const allErrors: string[] = [];
[...wrapperErrors.current.keys()].forEach(key => {
allErrors.push(`The following errors exist for datasource ${key}:`);
const keyErrors = wrapperErrors.current.get(key);
keyErrors.forEach(error => allErrors.push(`\t${error}`));
});
props.SetErrors(allErrors);
}, [props.SetErrors]);

return (
<>
<div className="tab-content" style={{height: '100%', width: '100%'}}>
<div className={"tab-pane container " + (props.Tab === "settings" ? 'active' : 'fade')} style={{height: '100%', width: '100%'}}>
<DataSetGlobalSettings DataSet={props.DataSet} SetDataSet={props.SetDataSet} Connections={props.Connections} SetConnections={props.SetConnections} />
</div>
{
props.Connections.map((conn, index) => (
<div className={"tab-pane container " + (dataSources.find(ds => ds.ID === conn.DataSourceID)?.Name + index.toString() === props.Tab ? 'active' : 'fade')} id={index.toString()} key={index}>
<DataSourceWrapper DataSource={dataSources.find(ds => ds.ID === conn.DataSourceID)}
ComponentType='datasetConfig' DataSet={props.DataSet}
DataSetConn={conn} SetDataSetConn={newConn => changeConn(index, newConn)} />
</div>
))
props.Connections.map((conn, index) => {
const src = dataSources.find(ds => ds.ID === conn.DataSourceID);
return (
<div className={"tab-pane container " + (src?.Name + index.toString() === props.Tab ? 'active' : 'fade')} id={index.toString()} key={index}>
<DataSourceWrapper DataSource={src}
ComponentType='datasetConfig' DataSet={props.DataSet} SetErrors={(e) => setSourceErrors(e, src?.Name)}
DataSetConn={conn} SetDataSetConn={newConn => changeConn(index, newConn)} />
</div>
);
})
}
</div>
</>
);
}

Expand Down
Loading

0 comments on commit daece77

Please sign in to comment.