Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/time series profile dto #733

Draft
wants to merge 29 commits into
base: develop
Choose a base branch
from

Conversation

AndreasChristmann
Copy link

No description provided.

@AndreasChristmann AndreasChristmann marked this pull request as ready for review June 24, 2024 20:44
@AndreasChristmann
Copy link
Author

@rma-psmorris please review

@rma-psmorris
Copy link
Collaborator

@AndreasChristmann this should be flagged as a draft until your work in completed

@AndreasChristmann AndreasChristmann marked this pull request as draft June 25, 2024 15:51
public void validate() throws FieldException
{
if (this.parameterList == null) {
throw new FieldException("Parameter list field can't be null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, there are more specific exceptions derived from this exception that can be used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to use the Validator

@@ -0,0 +1,23 @@
{
"office-id": "SWT",
"location-id": "TIMESERIESPROFILE_LOC",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly a base location has a max length of 24 characters. (I didn't count, it just looks long.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

picked a shorter name

Copy link
Collaborator

@rma-psmorris rma-psmorris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTO Feedback

@Schema(description = "Key Parameter")
private final String keyParameter;
@Schema(description = "Reference TS")
private final String refTsId;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a time series identifier DTO class be used here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a time series identifier DTO class? Did not see one. How should it look like if we create one

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a CwmsId

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used CwmsId

@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public final class TimeSeriesProfile extends CwmsDTO
{
@Schema(description = "Location ID")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the location identifier class

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to CwmsId

}
}

@Override
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using hashcode/equals in your production code or only in unit testing? If only in unit testing, then this business should be removed from the DTO and placed in test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to test

private final String locationId;
@Schema(description = "Description")
private final String description;
@Schema(description = "Parameter List")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider re-using ParameterInfo for the key parameter and the list of parameters?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, ParameterInfo is for the parser and holds info how to parse.

@JsonDeserialize(builder = ParameterInfo.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public class ParameterInfo implements CwmsDTOBase
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - what format are you using for this source code, is it using the format that has been created for this repository?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the rep formatter now. Class can be final, can make the other Profile classes final too

{
return keyParameter;
}
public String getRecordDelimiter(){ return String.valueOf(recordDelimiter); }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why hold these as Char in the DTO when your public API has converts them to Strings?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the API to return char

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean String?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant char, it is not a String, but a single character. Also FieldDelimiter is a Character, as is can be null


public String getTimeInTwoFields()
{
return timeInTwoFields?"T":"F";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesnt follow established DTO Boolean handling present elsewhere in the CDA API

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to return boolean

}

@Override
public int hashCode() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not used in production, move to test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to test

return result;
}
@Override
public boolean equals(Object o) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not used in production, move to test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved equals and hash to test

@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public class ParameterInfo implements CwmsDTOBase
{
private final String parameter;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following columns are in the DB:
LOCATION_CODE,
KEY_PARAMETER_CODE,
PARAMETER_CODE,
PARAMETER_UNIT,
PARAMETER_FIELD,
PARAMETER_COL_START,
PARAMETER_COL_END

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on updating to the full set of DB columns?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. now that I know what col_start and col_end is used for

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added colStart and colEnd, note that those are exclusive to Field

Copy link
Contributor

@MikeNeilson MikeNeilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall structure itself looks reasonable. I have some concerns about performance and pagination however those don't necessarily need to be solved immediately.

The sample json files should be made a cohesive whole that can be used in an integration test.


public List<TimeSeriesProfile> retrieveTimeSeriesProfiles(String locationIdMask, String parameterIdMask, String officeIdMask) {
return connectionResult(dsl, conn -> {
List<TimeSeriesProfile> timeSeriesProfileList = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure this isn't going to end up large in the future?
If so we can wait on pagination but it's generally something we want to consider.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndreasChristmann From internal meeting recommend coding this against the view to allow for pagination to be added if needed later on.

{
// TODO
return connectionResult(dsl, conn -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as previous about possible need for pagination.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar as above: @AndreasChristmann - From an internal meeting recommend coding this against the view to allow for pagination to be added if needed later on.


import java.time.Instant;

@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ever formatted on it's own or always as part of some container object? If always within a container it doesn't need the FormattableWith annotation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is always part of a container

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the annotation

@Test
void testCopyTimeSeriesProfile() throws SQLException {
CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
databaseLink.connection(c -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these usages need to have CwmsDataApiSetupCallback.getWebUser() added as the second parameter so the correct user with appropriate office permissions is used during the operations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added getWebUser as the second parameter throughout

}

private static TimeSeriesProfile buildTestTimeSeriesProfile(String location, String parameter) {
String officeId = "HEC";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a real office, add it to the user.sql in the test resources.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now when using getWebUser, I have access to a real office. Switched to LRL

{
"parameter": "Temp-Water",
"parameter": "Temperature",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? The original was correct. Temperature is not a base parameter in CWMS.... or have I gone mad?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched to Temp-Water again

}
]
},
{
"parameter": "Depth",
"unit": "ft",
"time-value-pairs":
"time-zone": "PST",
"time-value-pair-list":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at the time series "container" from that end point, the naming here should match.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed time-value-pair-list to values

}
]
},
{
"parameter": "Depth",
"unit": "ft",
"time-value-pairs":
"time-zone": "PST",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: if this file is an example of input, CDA should and will only process numeric date-times as UTC.

ISO format data-times should include include their timezone in each but allowing a TZ parameter to use to then immediately convert to UTC.

CDA is pedantic about time and fosters no confusion. (Granted, that's the intent, we've had plenty of confusion.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time in the time series container is just a long, output from the instant, I followed the other timeseries container's format.

"location-id": {
"office-id": "SWT",
"name": "location"
},
"key-parameter": "Depth",
"record-delimiter": "\n",
"field-delimiter": ",",
"time-format": "MM/DD/YYYY,HH24:MI:SS",
"time-zone": "UTC",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is to process data coming from not CDA, the previous comments don't necessarily apply.

"location-id": "TIMESERIESPROFILE_LOC",
"location-id": {
"office-id": "SWT",
"name": "location"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a more realistic name.

Also since you've used SWT here, use that for the others. Since these sample json file can make up an consistent whole, they should.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used SWT and real life name

@Override
public void validate() throws FieldException {

if(index==null && !(startColumn!=null && endColumn!=null))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may need to pull down my logic design book but I'm pretty sure the second check is a perfect application for the "exclusive or"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But let someone else besides me decide if the logic makes sense. Doing too much boolean algebra homework makes you overthink these things.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my intention was to mimic in the expression that if index is null, then start and end must not be null,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, the intention is at least clear, just very... nested.
Though I just roughed out a truth table and a clearer way didn't immediately present itself. I think xor would work in aggregate but you wouldn't be able to have as clear of an error message as you currently do. So just ignore me on this one.

Copy link
Contributor

@MikeNeilson MikeNeilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's starting to really come together.

Copy link
Collaborator

@rma-psmorris rma-psmorris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Primarily DTO review

@JsonDeserialize(builder = ParameterInfo.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public final class ParameterInfo implements CwmsDTOBase {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class needs a unit test with JSON resource file content

public void validate() throws FieldException {
if(index==null && !(startColumn!=null && endColumn!=null))
{
throw new FieldException("if index is null, startColumn and endColumn must be defined!");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exceptions as a rule need to be dynamic with the content that they are reporting an error on.
"if index is null, startColumn and endColumn must be defined!"
This message does not do enough to help debug the content that caused this issue.

Also use proper sentence case for exception messages, they should be clear and formatted for the end user.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed validate, no longer needed

}

@Override
public int hashCode() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you need hashCode & equals for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed hashCode and equals

@JsonDeserialize(builder = TimeValuePair.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public final class TimeValuePair {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The database API only gives you time and value, no other tsv data such as quality, DED, version date?

Copy link
Collaborator

@rma-psmorris rma-psmorris Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed PL/SQL API, pvq_t returns
value binary_double,
quality_code integer);

We need to add quality.
DED and version date are not returned by the pl/sql package API

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added quality


public List<TimeSeriesProfile> retrieveTimeSeriesProfiles(String locationIdMask, String parameterIdMask, String officeIdMask) {
return connectionResult(dsl, conn -> {
List<TimeSeriesProfile> timeSeriesProfileList = new ArrayList<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndreasChristmann From internal meeting recommend coding this against the view to allow for pagination to be added if needed later on.

@Schema(description = "Key Parameter")
private final String keyParameter;
@Schema(description = "Reference TS")
private final String refTsId;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a CwmsId

import cwms.cda.data.dto.CwmsDTOBase;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = ParameterInfoIndexed.class),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please define the type name parts here following our easy to ready dash-delimited naming pattern, "indexed-parameter-info" (I think this is easiest to read) or "parameter-info-indexed" if you want to adhere to the classname.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example:
@JsonSubTypes({@JsonSubTypes.Type(value = TurbineSetting.class, name = "turbine-setting")})

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used name = "indexed-parameter-info"

validator.required(endColumn, "endColumn");
}

public String parameterInfoString()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not follow the bean spec for this method?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bean spec would be: getParameterInfoString()
You need to include an annotation to have Jackson ignore this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used getParameterInfoString with JsonIgnore

}

@Override
public String parameterInfoString()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as previous on bean spec

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to getParameterInfoString with JsonIgnore

}


public BigInteger getTimeField() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be Optional, could also include annotations describing that it can be null. Let's discuss during internal meeting.

Copy link
Collaborator

@rma-psmorris rma-psmorris Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example: Optional <BigInteger>
Could also include @nullable (@nullable and @null from)
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeField must not be null for this class

}
catch(cwms.cda.api.errors.NotFoundException ex)
{
// return null for not found
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zack-rma -- this should include a logger at developer levels

Copy link
Collaborator

@rma-psmorris rma-psmorris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no further requests on this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants