Skip to content

Commit

Permalink
feat(frontend|backend): To good to go (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
cleptric authored Jul 17, 2024
1 parent 2cea19c commit fc1f2b6
Show file tree
Hide file tree
Showing 16 changed files with 452 additions and 18 deletions.
42 changes: 42 additions & 0 deletions config/Migrations/20240706000315_AddUserSlackTimeZone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);

use Migrations\AbstractMigration;

class AddUserSlackTimeZone extends AbstractMigration
{
/**
* Up Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-up-method
*
* @return void
*/
public function up(): void
{
$this->table('users')
->addColumn('slack_time_zone', 'string', [
'after' => 'progression_id',
'default' => null,
'length' => 255,
'null' => true,
])
->update();
}

/**
* Down Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-down-method
*
* @return void
*/
public function down(): void
{
$this->table('users')
->removeColumn('slack_time_zone')
->update();
}
}
11 changes: 11 additions & 0 deletions frontend/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ const store = createStore({
console.log(error)
}
},
async toggleTooGoodToGoNotifications({ commit, getters }) {
commit('TOGGLE_TOO_GOOD_TO_GO_NOTIFICATIONS')
try {
const response = await api.patch('user', getters.user)
} catch (error) {
console.log(error)
}
},
setRangeFilter({ commit }, range) {
commit('SET_RANGE_FILTER', range)
},
Expand Down Expand Up @@ -129,6 +137,9 @@ const store = createStore({
TOGGLE_RECEIVED_NOTIFICATIONS(state) {
state.user.notifications.received = !state.user.notifications.received
},
TOGGLE_TOO_GOOD_TO_GO_NOTIFICATIONS(state) {
state.user.notifications.too_good_to_go = !state.user.notifications.too_good_to_go
},
SET_RANGE_FILTER(state, range) {
state.filter.range = range
localStorage.setItem('filter.range', state.filter.range)
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/views/Profile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@
</div>

<div class="mt-2 mb-32">
<div class="py-4">
<h2 class="text-lg font-medium leading-6">Your Account Info</h2>
<p class="mt-1 text-sm text-zinc-500">
Time zone is set to <strong>{{ user.slack_time_zone }}</strong>.
</p>
</div>
<div class="py-4">
<h2 class="text-lg font-medium leading-6">Your Potato Stats</h2>
<p class="mt-1 text-sm text-zinc-500">
You have <strong>{{ user.potato_left_today }}</strong> 🥔 left to gib today.
Your potato do reset in <strong>{{ user.potato_reset_in_hours }}</strong> hours and <strong>{{ user.potato_reset_in_minutes }}</strong> minutes.
</p>
<p class="mt-1 text-sm text-zinc-500">
You did gib <strong>{{ user.sent_count ?? 0 }}</strong> 🥔 and did receive <strong>{{ user.received_count ?? 0 }}</strong> 🥔 since you started potatoing
<strong>{{ new Date(user.created).toLocaleDateString('en-us', { year:"numeric", month:"short", day:"numeric"}) }}.</strong>
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/views/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,27 @@
</span>
</button>
</li>
<li class="flex items-center justify-between py-4">
<div class="flex flex-col">
<p class="text-sm font-medium" id="privacy-option-2-label">
Too good to go 🌱
</p>
<p class="text-sm text-zinc-500" id="privacy-option-2-description">
Receive a notification at the end of your work day if you have any leftover potato.
</p>
</div>
<button type="button"
class="bg-zinc-200 dark:bg-zinc-600 relative ml-4 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
:class="{ '!bg-green-500': user.notifications.too_good_to_go === true }"
@click="toggleTooGoodToGoNotifications"
>
<span
class="translate-x-0 inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="{ '!translate-x-5': user.notifications.too_good_to_go === true }"
>
</span>
</button>
</li>
</ul>
</div>
</div>
Expand All @@ -72,6 +93,9 @@ export default {
toggleReceivedNotifications() {
this.$store.dispatch('toggleReceivedNotifications')
},
toggleTooGoodToGoNotifications() {
this.$store.dispatch('toggleTooGoodToGoNotifications')
},
},
}
</script>
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

161 changes: 161 additions & 0 deletions src/Command/TooGoodToGoCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);

namespace App\Command;

use App\Database\Log\SentryQueryLogger;
use App\Http\SlackClient;
use Cake\Chronos\Chronos;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Datasource\ConnectionManager;
use DateTimeZone;
use Sentry\MonitorConfig;
use Sentry\MonitorSchedule;
use Sentry\SentrySdk;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\SpanStatus;
use Sentry\Tracing\TransactionContext;
use Sentry\Tracing\TransactionSource;
use Throwable;
use function Sentry\captureException;
use function Sentry\startTransaction;
use function Sentry\withMonitor;

/**
* TooGoodToGo command.
*/
class TooGoodToGoCommand extends Command
{
private const int TARGET_HOUR = 16;

/**
* Hook method for defining this command's option parser.
*
* @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options
* @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
* @return \Cake\Console\ConsoleOptionParser The built parser.
*/
public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser = parent::buildOptionParser($parser);

return $parser;
}

/**
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null|void The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
withMonitor(
slug: 'too-good-to-go',
callback: fn () => $this->_execute($args, $io),
monitorConfig: new MonitorConfig(
schedule: new MonitorSchedule(
type: MonitorSchedule::TYPE_CRONTAB,
value: '*/30 * * * 1-5',
),
checkinMargin: 5,
maxRuntime: 10,
timezone: 'UTC',
),
);
}

/**
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null|void The exit code or null for success
*/
protected function _execute(Arguments $args, ConsoleIo $io)
{
$io->out('Sending out Too Good To Go notifications');

$slackClient = new SlackClient();

$logger = new SentryQueryLogger();

$connection = ConnectionManager::get('default');
$connection->getDriver()->setLogger($logger);

$transactionContext = TransactionContext::make()
->setOp('command')
->setName('COMMAND too_good_to_go')
->setSource(TransactionSource::task());

$transaction = startTransaction($transactionContext);

SentrySdk::getCurrentHub()->setSpan($transaction);

$usersTable = $this->fetchTable('Users');
$users = $usersTable->find()
->where([
'slack_time_zone IN' => $this->_getApplicableTimeZones(),
])
->all();

foreach ($users as $user) {
if (
$user->notifications['too_good_to_go'] !== true
|| $user->potatoLeftToday() <= 0
) {
continue;
}

$spanContext = SpanContext::make()
->setOp('command')
->setDescription('Send notification');
$span = $transaction->startChild($spanContext);

SentrySdk::getCurrentHub()->setSpan($span);

try {
$message = 'Hallo, just letting you know that you have *' . $user->potatoLeftToday()
. '* 🥔 left to gib today 🌱' . PHP_EOL;
$message .= 'Would be a bummer if they go to waste 😢' . PHP_EOL;
$message .= 'If someone did something nice today, gib them 🥔😊!';

$slackClient->postMessage(
channel: $user->slack_user_id,
text: $message,
);

$span->setStatus(SpanStatus::ok());
} catch (Throwable $e) {
captureException($e);
$span->setStatus(SpanStatus::internalError());
} finally {
$span->finish();
}
}
SentrySdk::getCurrentHub()->setSpan($transaction);

$transaction->setStatus(SpanStatus::ok())
->finish();

$io->success("\n[DONE]");
}

/**
* @return array
*/
protected function _getApplicableTimeZones(): array
{
$timeZones = DateTimeZone::listIdentifiers();
$applicableTimeZones = [];

foreach ($timeZones as $timezone) {
$localNow = new Chronos(timezone: $timezone);
if ($localNow->hour === self::TARGET_HOUR) {
$applicableTimeZones[] = $timezone;
}
}

return $applicableTimeZones;
}
}
2 changes: 2 additions & 0 deletions src/Command/UpdateUsersCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,15 @@ protected function _execute(Arguments $args, ConsoleIo $io)
'slack_user_id' => $slackUser['id'],
'slack_name' => $slackUser['real_name'],
'slack_picture' => $slackUser['profile']['image_72'],
'slack_time_zone' => $slackUser['tz'],
'slack_is_bot' => $slackUser['is_bot'] ?? false,
], [
'accessibleFields' => [
'status' => true,
'slack_user_id' => true,
'slack_name' => true,
'slack_picture' => true,
'slack_time_zone' => true,
'slack_is_bot' => true,
],
]);
Expand Down
5 changes: 5 additions & 0 deletions src/Controller/Api/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public function get(): Response

/** @var \App\Model\Entity\User $user */
$user->spendable_count = $user->spendablePotato();
$user->potato_sent_today = $user->potatoSentToday();
$user->potato_left_today = $user->potatoLeftToday();
$user->potato_reset_in_hours = $user->potatoResetInHours();
$user->potato_reset_in_minutes = $user->potatoResetInMinutes();

return $this->response
->withStatus(200)
Expand All @@ -93,6 +97,7 @@ public function edit(): Response
'notifications' => [
'sent' => (bool)$this->request->getData('notifications.sent'),
'received' => (bool)$this->request->getData('notifications.received'),
'too_good_to_go' => (bool)$this->request->getData('notifications.too_good_to_go'),
],
], [
'accessibleFields' => [
Expand Down
Loading

0 comments on commit fc1f2b6

Please sign in to comment.