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

[#172621882] Get all data for a specific user #42

Merged
merged 29 commits into from
May 13, 2020

Conversation

balanza
Copy link
Contributor

@balanza balanza commented May 11, 2020

Implementation of part of the Activit handles needed to complete the task.

Things to note:

  • some feature may fit better into io-functions-commons, like mocks. We might move them there after this activity
  • NotificationModel has been extended to support a specific use case. This as well might be moved into commons.

@balanza balanza requested review from gunzip and AleDore May 11, 2020 10:10
@balanza balanza self-assigned this May 11, 2020
@balanza balanza changed the title [#172621882] accesso ai dati [#172621882] access to data for citizens May 11, 2020
@pagopa-github-bot
Copy link
Contributor

pagopa-github-bot commented May 11, 2020

Affected stories

  • 🌟 #172621882: Come CIT, quando esprimo la volontà di scaricare i miei dati, voglio ricevere un link per il download dei dati entro 7gg dalla richiesta

Generated by 🚫 dangerJS

Comment on lines 125 to 137
tryCatch(
() =>
userDataProcessingModel.createOrUpdateByNewOne({
...currentRecord,
status: nextStatus
}),
(err: Error) => {
return ActivityResultQueryFailure.encode({
kind: "QUERY_FAILURE",
query: "userDataProcessingModel.createOrUpdateByNewOne",
reason: err.message
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use fromQueryEither in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, but still fromQueryEither isn't as generic as to be placed in a dedicated module. I'll just copy it over though, because I think it's more eloquent

@codecov-io
Copy link

codecov-io commented May 11, 2020

Codecov Report

Merging #42 into master will decrease coverage by 1.80%.
The diff coverage is 77.98%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #42      +/-   ##
==========================================
- Coverage   88.81%   87.00%   -1.81%     
==========================================
  Files          21       24       +3     
  Lines         608      762     +154     
  Branches       37       49      +12     
==========================================
+ Hits          540      663     +123     
- Misses         67       98      +31     
  Partials        1        1              
Impacted Files Coverage Δ
CreateService/handler.ts 85.18% <0.00%> (ø)
UpdateService/handler.ts 90.47% <0.00%> (ø)
GetService/handler.ts 86.95% <50.00%> (+3.62%) ⬆️
ExtractUserDataActivity/handler.ts 73.00% <73.00%> (ø)
SetUserDataProcessingStatusActivity/handler.ts 88.09% <88.09%> (ø)
utils/userData.ts 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a06e8f6...919c475. Read the comment docs.

value: fiscalCode
}
],
query: `SELECT * FROM m WHERE m.fiscal_code = @fiscalCode`
Copy link
Contributor

@gunzip gunzip May 11, 2020

Choose a reason for hiding this comment

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

since the partitionKey of the notifications collection is messageId this results in a cross partition query which should be avoided (see https://docs.microsoft.com/it-it/azure/cosmos-db/how-to-query-container#cross-partition-query).

you should include a condition on messageId so: get all users messages then, for each retrieved message, loop to get the message-status and the relative nofitications; then, for each retrieved notification, loop to get then the notification-status.

Comment on lines 4 to 12
* The validation process use a `id and validator` strategy.
*
* The `id` is generated using an ulid generator and is used when searching
* a specific ValidationToken entity in the table storage.
*
* For the `validator` we use a random-bytes generator. This `validator` value is
* hashed using the `sha256` strategy and then stored in the entity as `validatorHash`
*
* Each token has also a `InvalidAfter` field to set the token lifetime.
Copy link
Contributor

Choose a reason for hiding this comment

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

probably we should remove this comments? :)

@balanza balanza marked this pull request as ready for review May 12, 2020 13:18
@balanza balanza requested a review from gunzip May 12, 2020 13:19
*
* @returns the same document without db metadata
*/
const fromRetrievedDbDocument = <T>(doc: T & RetrievedDocumentT): T => {
Copy link
Contributor

Choose a reason for hiding this comment

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

every model has a class method toBaseType method which does exactly the same and does not depend on cosmosdb internal, consider using that one and remove this one
ie. https://github.com/pagopa/io-functions-commons/blob/6b60b5cc5fb840ff397f2ebc3261a3c7408b2168/src/models/profile.ts#L129

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it seems to to be used by anyone. It's passed to the base class which exposes it as protected, then never actually used.
https://github.com/pagopa/io-functions-commons/blob/master/src/utils/documentdb_model.ts#L31

Copy link
Contributor

Choose a reason for hiding this comment

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

If we cannot use that, I suggest to remove this method anyway and just use
t.exact(Message).encode(o); inline when needed

const appendContentToMessage = (
messageFromDb: RetrievedMessageWithoutContent,
content: MessageContent
): MessageWithContent => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to merge the message with its content ?
it's better to just save them in a separate blob

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good, even easier

Copy link
Contributor

Choose a reason for hiding this comment

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

the structure of the zip should mimic the internal representation (that won't happen, but suppose you have to restore all data fom the content of the zip :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This includes also document-related data? I mean: should we export also RetrievedDocument-related fields?

Copy link
Contributor

Choose a reason for hiding this comment

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

no since they're not needed to rebuild the whole thing

.map(([notificationId, channel]) =>
fromQueryEither(
() =>
notificationStatusModel.findOneNotificationStatusByNotificationChannel(
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to differentiate by channel?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, but I wanted to use the actual method which queries by statusId, and calculates the statusId by its channel

ReadonlyArray<RetrievedNotification>
> => {
if (messages.length) {
// this spread is needed as typescript wouldn't recognize messages[0] to be defined otherwise
Copy link
Contributor

@gunzip gunzip May 12, 2020

Choose a reason for hiding this comment

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

it's not clear, why do we need this pattern here ? consider using a NonEmptyArray instead and avoid to call the same code twice

NonEmptyArray.decode(messages)... or if (NonEmptyArray.is(message)) ...

Copy link
Contributor

Choose a reason for hiding this comment

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

"fp-ts/lib/NonEmptyArray"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

didn't know that, thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

are you sure it works like that? it doesn't seem to be a decoder though.

Anyway, I think it doesn't solved the issue: to pass an arbitrary list of taskEither to a sequenceT to resolve them in parallel: https://github.com/pagopa/io-functions-admin/pull/42/files/98dd1f4ddb37bed424eb078bd0549cd0a9c064b9#diff-6c3441a7b004efdd3512746300508009R339

Copy link
Contributor

@gunzip gunzip May 12, 2020

Choose a reason for hiding this comment

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

I mean: you can just use NonEmptyArray.fromArray(messages).map(...) instead of check messages length and separate head and tail (it's not clear why do you want to do that). you can use sequence (instead of sequenceT) to process them in parallel

https://grossbart.github.io/fp-ts-recipes/#/async?a=work-with-a-list-of-tasks-in-parallel

arrayOfArray =>
fromEither(
right(
arrayOfArray.reduce(
Copy link
Contributor

Choose a reason for hiding this comment

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

you can use array.flatten from fp-ts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it doesn't support readonly arrays (introduced in v2.5)

ReadonlyArray<RetrievedNotification>
> => {
if (messages.length) {
// this spread is needed as typescript wouldn't recognize messages[0] to be defined otherwise
Copy link
Contributor

@gunzip gunzip May 12, 2020

Choose a reason for hiding this comment

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

I mean: you can just use NonEmptyArray.fromArray(messages).map(...) instead of check messages length and separate head and tail (it's not clear why do you want to do that). you can use sequence (instead of sequenceT) to process them in parallel

https://grossbart.github.io/fp-ts-recipes/#/async?a=work-with-a-list-of-tasks-in-parallel

@balanza balanza closed this May 13, 2020
@balanza balanza reopened this May 13, 2020
ExtractUserDataActivity/handler.ts Outdated Show resolved Hide resolved
ExtractUserDataActivity/handler.ts Outdated Show resolved Hide resolved
ExtractUserDataActivity/handler.ts Outdated Show resolved Hide resolved
Comment on lines 300 to 305
.foldTaskEither(
e => fromEither(left(e)),
arrayOfArray =>
// tslint:disable-next-line: readonly-array
fromEither(right(flatten(arrayOfArray as RetrievedNotification[][])))
);
Copy link
Contributor

Choose a reason for hiding this comment

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

since you are converting back to taskEither, maybe I'm wrong but it looks like you can just use map(flatten) here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you're right, foldTaskEither come from a version of the code which has been refactored now. No more reason for that, thanks

() => messageModel.getContentFromBlob(blobService, messageId),
"messageModel.getContentFromBlob"
).foldTaskEither<ActivityResultQueryFailure, MessageContentWithId>(
failure => fromEither(left(failure)),
Copy link
Contributor

Choose a reason for hiding this comment

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

when the left value is unchanged, probably you can chain or map instead of folding

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case it's not trivial, because the right path may become left if Option is none.

Comment on lines +321 to +326
switch (channel) {
case NotificationChannelEnum.EMAIL:
case NotificationChannelEnum.WEBHOOK:
return [notificationId, channel];
default:
assertNever(channel);
Copy link
Contributor

Choose a reason for hiding this comment

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

cannot ...Object.values(NotificationChannelEnum).map(channel => [notificationId, channel])
just work without the switch here?

Copy link
Contributor Author

@balanza balanza May 13, 2020

Choose a reason for hiding this comment

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

Yes, but this way I could implement an exhaustive check that will force us to update this function if in the future we add/remove options in NotificationChannelEnum

Copy link
Contributor

Choose a reason for hiding this comment

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

why do we want to be forced to update this method in case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we'll add support for another notification channel

Copy link
Contributor

Choose a reason for hiding this comment

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

and why .map(channel => [notificationId, channel]) won't suffice in that case?

ActivityResultQueryFailure,
ReadonlyArray<NotificationStatus>
>(
e => fromEither(left(e)),
Copy link
Contributor

Choose a reason for hiding this comment

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

map / chain ?

})
)
.chain(({ fiscalCode }) => queryAllUserData(fiscalCode))
.map(allUserData =>
Copy link
Contributor

Choose a reason for hiding this comment

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

when you have map + mapLeft you can use bimap to handle both

ExtractUserDataActivity/index.ts Outdated Show resolved Hide resolved
ExtractUserDataActivity/notification.ts Outdated Show resolved Hide resolved
@gunzip gunzip changed the title [#172621882] access to data for citizens [#172621882] Get all data from database and storage for a specific user May 13, 2020
@gunzip gunzip changed the title [#172621882] Get all data from database and storage for a specific user [#172621882] Get all data for a specific user May 13, 2020
balanza and others added 11 commits May 13, 2020 12:12
Co-authored-by: Danilo Spinelli <gunzip@users.noreply.github.com>
Co-authored-by: Danilo Spinelli <gunzip@users.noreply.github.com>
Co-authored-by: Danilo Spinelli <gunzip@users.noreply.github.com>
Co-authored-by: Danilo Spinelli <gunzip@users.noreply.github.com>
Co-authored-by: Danilo Spinelli <gunzip@users.noreply.github.com>
@gunzip gunzip merged commit fdfc8e9 into master May 13, 2020
@gunzip gunzip deleted the 172621882-accesso-ai-dati branch May 13, 2020 16:13
@balanza balanza restored the 172621882-accesso-ai-dati branch May 14, 2020 20:31
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.

5 participants