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

feat(files_sharing): reminder for link shares with expiration date #47412

Merged
merged 4 commits into from
Sep 3, 2024

Conversation

Luka-sama
Copy link
Contributor

Summary

This pull request implements the feature as described in the associated issue. A cron job runs hourly to check if any empty folders have less than a day remaining before their expiration date. The feature supports two types of shares: user shares and email shares.

Additionally, a migration was added to include a 'reminder sent' flag, ensuring that reminders are neither skipped nor sent multiple times when the job runs at irregular intervals, especially when using webcron or ajax instead of cron.

This solution can be easily extended to send reminders for link shares without an expiration date (e.g. after 4 days), if the maintainers choose to implement this feature.

grafik

Checklist

@Luka-sama Luka-sama force-pushed the feat/shares-reminder branch 2 times, most recently from 3deeb2c to 86be354 Compare August 22, 2024 07:48
Comment on lines 76 to 92
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'share_type')
->from('share')
->where(
$qb->expr()->andX(
$qb->expr()->orX(
$qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_USER)),
$qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_EMAIL))
),
$qb->expr()->eq('item_type', $qb->expr()->literal('folder')),
$qb->expr()->gte('expiration', $qb->createNamedParameter($minDate->format('Y-m-d H:i:s'))),
$qb->expr()->lt('expiration', $qb->createNamedParameter($maxDate->format('Y-m-d H:i:s'))),
$qb->expr()->eq('reminder_sent', $qb->createNamedParameter(
false, IQueryBuilder::PARAM_BOOL
))
)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this go into a mapper or manager of some kind? I know in other apps we usually do not hit the DB directly like that, not sure for shares.

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 could move this to the share manager, but I'm not sure if I should, since the query is very specific to this particular job 🤔

apps/files_sharing/appinfo/info.xml Show resolved Hide resolved
$qb->update('share')
->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent()))
->execute();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
->execute();
->executeStatement();

But again, I’d like to see that moved somewhere else.
The updateShare method seems a bit much for such a small flag change though, so I don’t know 🤔

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 updateShare works well here, the overhead (if any) is quite insignificant.

Comment on lines 75 to 76
/** @var bool */
private $reminderSent = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/** @var bool */
private $reminderSent = false;
private bool $reminderSent = false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I maybe update other properties to this format as well (to make it consistent)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I maybe update other properties to this format as well (to make it consistent)?

I decided against it because it breaks a lot of code (e.g. some int properties are sometimes set to null). So I did the strict typing only for my property.

lib/private/Share20/Share.php Show resolved Hide resolved
Comment on lines 58 to 66
public function run(mixed $argument): void {
$shares = $this->getShares();
[$foldersByEmail, $langByEmail] = $this->prepareReminders($shares);
$this->sendReminders($foldersByEmail, $langByEmail);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to put a limit here, and work only on a chunk of possible reminders, like 1000. Otherwise you can run into memory issues and such.
Also, you are first gathering all the info and then sending all the email, consider using Generators to send shares as you find them.
Also, you need to consider what happens if your job crashes or is stopped mid-process. In the current state it sets the flag reminder_sent on all shares first, which means it won’t get sent for other shares if it crashes on a share before. This is better than re-sending reminders, but it would still be better to flag shares one by one just before sending the email.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, thank you!

@skjnldsv
Copy link
Member

Great idea @Luka-sama !!
Take your time to address Come's comments 👍

@Luka-sama
Copy link
Contributor Author

Thank you @come-nc and @skjnldsv! The idea comes from the issue, but I found it a very interesting task to implement it here :)
That's a very nice idea with generators! I will fix/address this and other problems in the next days.

@Luka-sama
Copy link
Contributor Author

So, I've fixed the problems that @come-nc found!
To do this, I moved the condition checking (write permissions and folder emptiness) to the database query - to avoid the situation where the job checks the same 1000 shares every time.

@Luka-sama Luka-sama requested a review from come-nc August 27, 2024 07:45
Copy link
Contributor

@come-nc come-nc left a comment

Choose a reason for hiding this comment

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

reminder_sent should be a bool in DB as well, otherwise good.
Only thing a bit questionable is how would other providers add support for this, but this can be refactored in later.

apps/files_sharing/lib/SharesReminderJob.php Outdated Show resolved Hide resolved
apps/sharebymail/lib/ShareByMailProvider.php Outdated Show resolved Hide resolved
@Luka-sama
Copy link
Contributor Author

Thank you for your code review!
Yes, currently only two types of shares are supported (both in providers and in the job itself). However, support for other types can easily be added if needed.

Copy link
Contributor

@come-nc come-nc left a comment

Choose a reason for hiding this comment

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

What I meant was that this could’ve been added in a more extensible way, for instance with an interface for providers which want to implement the feature, and which adds a method to get which email address the reminder should be sent to.
Just thinking out loud, I have no idea if this would’ve been a good idea and all it implies, just a suggestion for future work to think about extensibility by applications.

Approved 👍

apps/files_sharing/lib/SharesReminderJob.php Fixed Show fixed Hide fixed
'token' => $share->getToken()
]);
}
if (empty($reminderInfo['email'])) {

Check notice

Code scanning / Psalm

RiskyTruthyFalsyComparison Note

Operand of type null|string contains type string, which can be falsy and truthy. This can cause possibly unexpected behavior. Use strict comparison instead.
@Luka-sama Luka-sama force-pushed the feat/shares-reminder branch from 23ee40d to 3e91bb7 Compare September 2, 2024 19:59
@Luka-sama
Copy link
Contributor Author

You're right, that's definitely a good idea for the future to make the feature more universal this way.

Thanks!

@skjnldsv skjnldsv added 4. to release Ready to be released and/or waiting for tests to finish and removed 2. developing Work in progress labels Sep 3, 2024
@skjnldsv skjnldsv force-pushed the feat/shares-reminder branch from 3e91bb7 to f6d05d0 Compare September 3, 2024 11:37
@skjnldsv
Copy link
Member

skjnldsv commented Sep 3, 2024

Ok, let's go! 🚀

@skjnldsv skjnldsv force-pushed the feat/shares-reminder branch from f6d05d0 to 2685501 Compare September 3, 2024 15:23
@skjnldsv skjnldsv merged commit f9fcc5b into nextcloud:master Sep 3, 2024
42 of 44 checks passed
Copy link

welcome bot commented Sep 3, 2024

Thanks for your first pull request and welcome to the community! Feel free to keep them coming! If you are looking for issues to tackle then have a look at this selection: https://github.com/nextcloud/server/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22

@Pytal
Copy link
Member

Pytal commented Sep 3, 2024

This broke CI on master cc @skjnldsv

use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version31000Date20240821142813 extends SimpleMigrationStep {
Copy link
Contributor

Choose a reason for hiding this comment

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

As this target 31 it should have a migration annotation, otherwise we have no information of the impact.

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 have added it:
d758442
However, my changes are no longer displayed here in this pull request, probably because the branch is already merged into master.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Luka-sama yes because it is already merged. But is also not about a doc-comment but about annotation for the kind of the migration (OCP\Migration\Attributes\MigrationAttribute).
Nevertheless thank you very much!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@susnux Oh, I see! I must have used the older migrations as an example, the attributes seem to be something new (from the 30th version).
Should I add them too? Except that it would probably be an overkill to make a new pull request just for that 😅 (or not?)

Copy link
Contributor

github-actions bot commented Sep 6, 2024

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: ☑️ Done
Development

Successfully merging this pull request may close these issues.

Reminder for link shares with expiration date
6 participants