Skip to content

Commit

Permalink
feat: #quickwin (#162)
Browse files Browse the repository at this point in the history
Co-authored-by: Michi Hoffmann <[email protected]>
  • Loading branch information
HazAT and cleptric authored Mar 14, 2024
1 parent 7b1d96b commit 96e796f
Show file tree
Hide file tree
Showing 21 changed files with 564 additions and 6 deletions.
81 changes: 81 additions & 0 deletions config/Migrations/20240302162526_AddQuickWins.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);

use Migrations\AbstractMigration;

class AddQuickWins 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('quick_wins', ['id' => false, 'primary_key' => ['id']])
->addColumn('id', 'uuid', [
'default' => null,
'limit' => null,
'null' => false,
])
->addColumn('sender_user_id', 'uuid', [
'default' => null,
'limit' => null,
'null' => false,
])
->addColumn('message', 'string', [
'default' => null,
'limit' => 4096,
'null' => false,
])
->addColumn('permalink', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
])
->addColumn('created', 'datetime', [
'default' => null,
'limit' => null,
'null' => true,
])
->addIndex(
[
'sender_user_id',
]
)
->create();

$this->table('quick_wins')
->addForeignKey(
'sender_user_id',
'users',
'id',
[
'update' => 'NO_ACTION',
'delete' => 'NO_ACTION',
]
)
->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('quick_wins')
->dropForeignKey(
'sender_user_id'
)->save();

$this->table('quick_wins')->drop()->save();
}
}
2 changes: 0 additions & 2 deletions config/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@
if (Configure::read('debug')) {
Configure::write('Cache._cake_model_.duration', '+2 minutes');
Configure::write('Cache._cake_translations_.duration', '+2 minutes');
// disable router cache during development
Configure::write('Cache._cake_routes_.duration', '+2 seconds');
}

/*
Expand Down
2 changes: 2 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
$builder->connect('/', ['controller' => 'Home', 'action' => 'index']);
$builder->connect('/shop', ['controller' => 'Home', 'action' => 'index']);
$builder->connect('/collection', ['controller' => 'Home', 'action' => 'index']);
$builder->connect('/quick-wins', ['controller' => 'Home', 'action' => 'index']);
$builder->connect('/profile', ['controller' => 'Home', 'action' => 'index']);
$builder->connect('/settings', ['controller' => 'Home', 'action' => 'index']);

Expand All @@ -77,6 +78,7 @@
$builder->post('/shop/purchase', ['prefix' => 'Api', 'controller' => 'Shop', 'action' => 'purchase']);

$builder->get('/collection', ['prefix' => 'Api', 'controller' => 'Collection', 'action' => 'get']);
$builder->get('/quick-wins', ['prefix' => 'Api', 'controller' => 'QuickWins', 'action' => 'get']);
});
});

Expand Down
18 changes: 18 additions & 0 deletions frontend/src/components/FormattedMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<p class="text-sm pt-2.5" v-html="formattedMessage"></p>
</template>

<script>
export default {
name: 'FormattedMessage',
props: ['message'],
computed: {
formattedMessage() {
return this.message
.replace(/<(@[^>]+)>/g, (_match, p1) => `<span class="bg-blue-500 text-blue-500 bg-opacity-30 p-0.5 px-1 rounded-sm">${p1}</span>`)
.replace(/ (\w+) <(http[^>]+)>/g, (_match, p1, p2) => ` <a class="underline" href="${p2}">${p1}</a> `)
.replace(/:potato:/g, '🥔');
},
},
}
</script>
16 changes: 15 additions & 1 deletion frontend/src/components/MainMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
>
Collection
</RouterLink>
<RouterLink
to="/quick-wins"
class="rounded-md px-3 py-2 text-sm font-medium text-zinc-900"
:class="{ '!bg-zinc-900 !text-zinc-50': $route.path === '/quick-wins' }"
>
Quick Wins
</RouterLink>
</div>
</div>
</div>
Expand Down Expand Up @@ -100,10 +107,17 @@
<RouterLink
to="/collection"
class="block rounded-md px-3 py-2 text-base font-medium text-zinc-900"
:class="{ '!bg-zinc-900 text-zinc-50': $route.path === '/collection' }"
:class="{ '!bg-zinc-900 !text-zinc-50': $route.path === '/collection' }"
>
Collection
</RouterLink>
<RouterLink
to="/quick-wins"
class="block rounded-md px-3 py-2 text-base font-medium text-zinc-900"
:class="{ '!bg-zinc-900 !text-zinc-50': $route.path === '/quick-wins' }"
>
Quick Wins
</RouterLink>
</div>
<div class="border-t border-zinc-900 pt-4 pb-3">
<div class="flex items-center px-5">
Expand Down
1 change: 1 addition & 0 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import './assets/main.css'
store.dispatch('getUsers'),
store.dispatch('getProducts'),
store.dispatch('getCollection'),
store.dispatch('getQuickWins'),
])

app
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const router = createRouter({
name: 'collection',
component: () => import('@/views/Collection.vue')
},
{
path: '/quick-wins',
name: 'quick-wins',
component: () => import('@/views/QuickWins.vue')
},
{
path: '/profile',
name: 'profile',
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const store = createStore({
users: [],
products: [],
collection: [],
quickWins: [],
filter: {
range: helper.getRangeFilter(),
order: helper.getOrderFilter(),
Expand All @@ -22,6 +23,7 @@ const store = createStore({
users: state => state.users,
products: state => state.products,
collection: state => state.collection,
quickWins: state => state.quickWins,
filter: state => state.filter,
range: state => state.filter.range,
order: state => state.filter.order,
Expand Down Expand Up @@ -71,6 +73,14 @@ const store = createStore({
console.log(error)
}
},
async getQuickWins({ commit }) {
try {
const response = await api.get('quick-wins')
commit('SET_QUICK_WINS', response.data)
} catch (error) {
console.log(error)
}
},
async toggleSentNotifications({ commit, getters }) {
commit('TOGGLE_SENT_NOTIFICATIONS')
try {
Expand Down Expand Up @@ -110,6 +120,9 @@ const store = createStore({
SET_COLLECTION(state, collection) {
state.collection = collection
},
SET_QUICK_WINS(state, quickWins) {
state.quickWins = quickWins
},
TOGGLE_SENT_NOTIFICATIONS(state) {
state.user.notifications.sent = !state.user.notifications.sent
},
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/views/QuickWins.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<div v-if="quickWins.length">

<div>
<h2 class="text-lg font-medium leading-6">
Messages containing #quickwin and 🥔 will be immortalized here
</h2>
</div>

<div class="mt-8 grid grid-cols-1 gap-y-12 sm:grid-cols-2 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8 mb-32">

<div v-for="(item, index) in quickWins">
<div
:index="index"
class="flex items-start gap-2.5"
>
<img
class="w-8 h-8 rounded-full"
:src="item.user.slack_picture"
>
<div class="p-4 border border-zinc-300 rounded-e-xl rounded-es-xl">
<div class="">
<div class="text-sm font-semibold">
{{item.user.slack_name}}
</div>
<div class="text-sm font-normal text-zinc-500">
{{ new Date(item.created).toLocaleDateString('en-us', { year:"numeric", month:"short", day:"numeric"}) }}
</div>
</div>
<FormattedMessage :message="item.message" />
</div>
</div>
</div>
</div>

</div>

<div
v-else
class="absolute inset-0 flex items-center justify-center"
>
<h1 class="text-2xl font-extrabold">
No #quickwins happened so far...
</h1>
</div>

</template>

<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
import FormattedMessage from '../components/FormattedMessage.vue'
export default {
name: 'QuickWins',
components: {
FormattedMessage,
},
setup() {
const store = useStore()
return {
quickWins: computed(() => store.getters.quickWins),
}
},
}
</script>
4 changes: 2 additions & 2 deletions package-lock.json

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

1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<env name="APP_NAME" value="gib_potato"/>
<env name="POTAL_TOKEN" value="something-secret"/>
<env name="SECURITY_SALT" value="a-random-value-that-you-cannot-guess"/>
<env name="POTATO_CHANNEL" value="some-channel"/>

<env name="CACHE_DEFAULT_URL" value="array://"/>
<env name="CACHE_CAKECORE_URL" value="array://"/>
Expand Down
60 changes: 60 additions & 0 deletions src/Controller/Api/QuickWinsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);

namespace App\Controller\Api;

use Cake\Http\Response;

/**
* @property \Authentication\Controller\Component\AuthenticationComponent $Authentication
*/
class QuickWinsController extends ApiController
{
/**
* @return \Cake\Http\Response
*/
public function get(): Response
{
$quickWinsTable = $this->fetchTable('QuickWins');

$quickWins = $quickWinsTable->find()
->contain('Users')
->orderBy(['QuickWins.created' => 'DESC'])
->all();

$collectedUsersInMessages = [];
foreach ($quickWins as $quickWin) {
// extract all user ids from message: "<@U042CECCR7A> has tagged you in a message"
preg_match_all('/<@([A-Z0-9]+)>/', $quickWin->message, $matches);
foreach ($matches[1] as $match) {
$collectedUsersInMessages[] = $match;
}
}

$collectedUsersInMessages = array_unique($collectedUsersInMessages);

$usersTable = $this->fetchTable('Users');
$users = $usersTable->find()
->where([
'slack_user_id IN' => !empty($collectedUsersInMessages) ?
$collectedUsersInMessages : [null],
])
->all();

foreach ($quickWins as $quickWin) {
foreach ($users as $user) {
// replace all user ids with user names
$quickWin->message = str_replace(
"<@{$user->slack_user_id}>",
"<@{$user->slack_name}>",
$quickWin->message
);
}
}

return $this->response
->withStatus(200)
->withType('json')
->withStringBody(json_encode($quickWins));
}
}
Loading

0 comments on commit 96e796f

Please sign in to comment.