Skip to content

Commit

Permalink
Retro add user ready status (#545)
Browse files Browse the repository at this point in the history
Resolves #506
  • Loading branch information
StevenWeathers committed Mar 30, 2024
1 parent 057291b commit 2d4ee5f
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 15 deletions.
6 changes: 6 additions & 0 deletions docs/swagger/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8938,6 +8938,12 @@ const docTemplate = `{
"phase": {
"type": "string"
},
"readyUsers": {
"type": "array",
"items": {
"type": "string"
}
},
"teamName": {
"type": "string"
},
Expand Down
6 changes: 6 additions & 0 deletions docs/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -8929,6 +8929,12 @@
"phase": {
"type": "string"
},
"readyUsers": {
"type": "array",
"items": {
"type": "string"
}
},
"teamName": {
"type": "string"
},
Expand Down
4 changes: 4 additions & 0 deletions docs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,10 @@ definitions:
type: string
phase:
type: string
readyUsers:
items:
type: string
type: array
teamName:
type: string
updatedDate:
Expand Down
9 changes: 9 additions & 0 deletions internal/db/migrations/20240329084022_update_retro_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE thunderdome.retro ADD COLUMN ready_users jsonb DEFAULT '[]'::jsonb;
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
ALTER TABLE thunderdome.retro DROP COLUMN ready_users;
-- +goose StatementEnd
60 changes: 58 additions & 2 deletions internal/db/retro/retro.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,18 @@ func (d *Service) RetroGet(RetroID string, UserID string) (*thunderdome.Retro, e
ActionItems: make([]*thunderdome.RetroAction, 0),
Votes: make([]*thunderdome.RetroVote, 0),
Facilitators: make([]string, 0),
ReadyUsers: make([]string, 0),
}

// get retro
var JoinCode string
var FacilitatorCode string
var Facilitators string
var ReadyUsers string
err := d.DB.QueryRow(
`SELECT
r.id, r.name, r.owner_id, r.format, r.phase, COALESCE(r.join_code, ''), COALESCE(r.facilitator_code, ''),
r.max_votes, r.brainstorm_visibility, r.created_date, r.updated_date,
r.max_votes, r.brainstorm_visibility, r.ready_users, r.created_date, r.updated_date,
CASE WHEN COUNT(rf) = 0 THEN '[]'::json ELSE array_to_json(array_agg(rf.user_id)) END AS facilitators
FROM thunderdome.retro r
LEFT JOIN thunderdome.retro_facilitator rf ON r.id = rf.retro_id
Expand All @@ -192,6 +194,7 @@ func (d *Service) RetroGet(RetroID string, UserID string) (*thunderdome.Retro, e
&FacilitatorCode,
&b.MaxVotes,
&b.BrainstormVisibility,
&ReadyUsers,
&b.CreatedDate,
&b.UpdatedDate,
&Facilitators,
Expand Down Expand Up @@ -222,6 +225,11 @@ func (d *Service) RetroGet(RetroID string, UserID string) (*thunderdome.Retro, e
b.FacilitatorCode = DecryptedCode
}

readyUsersError := json.Unmarshal([]byte(ReadyUsers), &b.ReadyUsers)
if readyUsersError != nil {
d.Logger.Error("ready users json error", zap.Error(readyUsersError))
}

b.Items = d.GetRetroItems(RetroID)
b.Groups = d.GetRetroGroups(RetroID)
b.Users = d.RetroGetUsers(RetroID)
Expand Down Expand Up @@ -452,7 +460,7 @@ func (d *Service) RetroAbandon(RetroID string, UserID string) ([]*thunderdome.Re
func (d *Service) RetroAdvancePhase(RetroID string, Phase string) (*thunderdome.Retro, error) {
var b thunderdome.Retro
err := d.DB.QueryRow(
`UPDATE thunderdome.retro SET updated_date = NOW(), phase = $2 WHERE id = $1 RETURNING name;`,
`UPDATE thunderdome.retro SET updated_date = NOW(), phase = $2, ready_users = '[]'::jsonb WHERE id = $1 RETURNING name;`,
RetroID, Phase,
).Scan(&b.Name)
if err != nil {
Expand Down Expand Up @@ -632,3 +640,51 @@ func (d *Service) CleanRetros(ctx context.Context, DaysOld int) error {

return nil
}

// MarkUserReady marks a user as ready for next phase
func (d *Service) MarkUserReady(RetroID string, userID string) ([]string, error) {
var rawReadyUsers string
readyUsers := make([]string, 0)

err := d.DB.QueryRow(
`UPDATE thunderdome.retro
SET updated_date = NOW(), ready_users = ready_users::jsonb || to_jsonb(array[$2])
WHERE id = $1
RETURNING ready_users;`,
RetroID, userID,
).Scan(&rawReadyUsers)
if err != nil {
return readyUsers, fmt.Errorf("retro MarkUserReady query error: %v", err)
}

err = json.Unmarshal([]byte(rawReadyUsers), &readyUsers)
if err != nil {
d.Logger.Error("ready_users json error", zap.Error(err))
}

return readyUsers, nil
}

// UnmarkUserReady un-marks a user as ready for next phase
func (d *Service) UnmarkUserReady(RetroID string, userID string) ([]string, error) {
var rawReadyUsers string
readyUsers := make([]string, 0)

err := d.DB.QueryRow(
`UPDATE thunderdome.retro
SET updated_date = NOW(), ready_users = ready_users::jsonb - $2
WHERE id = $1
RETURNING ready_users;`,
RetroID, userID,
).Scan(&rawReadyUsers)
if err != nil {
return readyUsers, fmt.Errorf("retro UnmarkUserReady query error: %v", err)
}

err = json.Unmarshal([]byte(rawReadyUsers), &readyUsers)
if err != nil {
d.Logger.Error("ready_users json error", zap.Error(err))
}

return readyUsers, nil
}
26 changes: 26 additions & 0 deletions internal/http/retro/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ func (b *Service) CreateItem(ctx context.Context, RetroID string, UserID string,
return msg, nil, false
}

// UserMarkReady marks a user as ready to advance to next phase
func (b *Service) UserMarkReady(ctx context.Context, RetroID string, UserID string, EventValue string) ([]byte, error, bool) {
readyUsers, err := b.RetroService.MarkUserReady(RetroID, UserID)
if err != nil {
return nil, err, false
}

updatedReadyUsers, _ := json.Marshal(readyUsers)
msg := createSocketEvent("user_marked_ready", string(updatedReadyUsers), UserID)

return msg, nil, false
}

// UserUnMarkReady unsets a user from ready to advance to next phase
func (b *Service) UserUnMarkReady(ctx context.Context, RetroID string, UserID string, EventValue string) ([]byte, error, bool) {
readyUsers, err := b.RetroService.UnmarkUserReady(RetroID, UserID)
if err != nil {
return nil, err, false
}

updatedReadyUsers, _ := json.Marshal(readyUsers)
msg := createSocketEvent("user_marked_unready", string(updatedReadyUsers), UserID)

return msg, nil, false
}

// GroupItem changes a retro item's group_id
func (b *Service) GroupItem(ctx context.Context, RetroID string, UserID string, EventValue string) ([]byte, error, bool) {
var rs struct {
Expand Down
2 changes: 2 additions & 0 deletions internal/http/retro/retro.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func New(

rs.eventHandlers = map[string]func(context.Context, string, string, string) ([]byte, error, bool){
"create_item": rs.CreateItem,
"user_ready": rs.UserMarkReady,
"user_unready": rs.UserUnMarkReady,
"group_item": rs.GroupItem,
"group_name_change": rs.GroupNameChange,
"group_vote": rs.GroupUserVote,
Expand Down
3 changes: 3 additions & 0 deletions thunderdome/retro.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Retro struct {
Items []*RetroItem `json:"items"`
ActionItems []*RetroAction `json:"actionItems"`
Votes []*RetroVote `json:"votes"`
ReadyUsers []string `json:"readyUsers"`
Facilitators []string `json:"facilitators"`
Format string `json:"format" db:"format"`
Phase string `json:"phase" db:"phase"`
Expand Down Expand Up @@ -102,6 +103,8 @@ type RetroDataSvc interface {
GetActiveRetros(Limit int, Offset int) ([]*Retro, int, error)
GetRetroFacilitatorCode(RetroID string) (string, error)
CleanRetros(ctx context.Context, DaysOld int) error
MarkUserReady(RetroID string, userID string) ([]string, error)
UnmarkUserReady(RetroID string, userID string) ([]string, error)

CreateRetroAction(RetroID string, UserID string, Content string) ([]*RetroAction, error)
UpdateRetroAction(RetroID string, ActionID string, Content string, Completed bool) (Actions []*RetroAction, DeleteError error)
Expand Down
12 changes: 6 additions & 6 deletions ui/src/components/retro/InviteUser.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import ClipboardIcon from '../icons/ClipboardIcon.svelte';
export let hostname = '';
export let retrospectiveId = '';
export let retroId = '';
function copyRetrospectiveLink() {
const retrospectiveLink = document.getElementById('RetrospectiveLink');
Expand All @@ -13,16 +13,16 @@
}
</script>

<h4 class="text-xl mb-2 leading-tight font-bold">Invite a user</h4>
<h4 class="text-xl mb-2 leading-tight font-bold text-center">Invite a user</h4>

<div class="flex flex-wrap items-stretch w-full">
<input
class="flex-shrink flex-grow flex-auto leading-normal w-px flex-1
border-2 h-10 bg-gray-200 border-gray-200 rounded rounded-e-none px-3
appearance-none text-gray-700 focus:outline-none focus:bg-white
focus:border-orange-500"
border-2 h-10 bg-gray-100 dark:bg-gray-900 dark:focus:bg-gray-800 border-gray-200 dark:border-gray-900 rounded rounded-e-none px-3
appearance-none text-gray-700 dark:text-gray-400 focus:outline-none focus:bg-white
focus:border-indigo-500 focus:caret-indigo-500 dark:focus:border-yellow-400 dark:focus:caret-yellow-400"
type="text"
value="{hostname}{appRoutes.retro}/{retrospectiveId}"
value="{hostname}{appRoutes.retro}/{retroId}"
id="RetrospectiveLink"
readonly
/>
Expand Down
37 changes: 35 additions & 2 deletions ui/src/components/retro/UserCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
import UserAvatar from '../user/UserAvatar.svelte';
import LL from '../../i18n/i18n-svelte';
import { user as sessionUser } from '../../stores';
import ThumbsUp from '../icons/ThumbsUp.svelte';
export let user = {};
export let user = { id: '' };
export let votes = [];
export let maxVotes = 3;
export let facilitators = [];
export let readyUsers = [];
export let phase = '';
export let handleAddFacilitator = () => {};
export let handleRemoveFacilitator = () => {};
export let handleUserReady = () => {};
export let handleUserUnReady = () => {};
$: reachedMaxVotes =
votes && votes.filter(v => v.userId === user.id).length === maxVotes;
$: userReady = readyUsers && readyUsers.includes(user.id);
</script>

<div
Expand Down Expand Up @@ -52,9 +58,36 @@
>
</div>
{/if}
{#if phase === 'brainstorm'}
{#if user.id === $sessionUser.id}
<div>
<button
on:click="{!userReady
? handleUserReady($sessionUser.id)
: handleUserUnReady($sessionUser.id)}"
class="inline-block pointer text-gray-300 dark:text-gray-500 {userReady
? 'text-lime-600 dark:text-lime-400'
: ''} hover:text-cyan-400 dark:hover:text-cyan-400"
>
<ThumbsUp
class="inline-block w-8 h-8"
title="Done brainstorming?"
/>
</button>
</div>
{:else}
<div
class="inline-block text-gray-300 dark:text-gray-500 {userReady
? 'text-lime-600 dark:text-lime-400'
: ''}"
>
<ThumbsUp class="inline-block w-8 h-8" title="Done brainstorming?" />
</div>
{/if}
{/if}
{#if phase === 'vote'}
{#if reachedMaxVotes}
<div class="text-green-600 dark:text-green-400">
<div class="text-lime-500 dark:text-lime-400">
{$LL.allVotesIn()}
</div>
{:else}
Expand Down
42 changes: 37 additions & 5 deletions ui/src/pages/retro/Retro.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
brainstormVisibility: 'visible',
facilitatorCode: '',
joinCode: '',
readyUsers: [],
};
let showDeleteRetro = false;
let actionItem = '';
Expand Down Expand Up @@ -158,6 +159,22 @@
}
break;
}
case 'user_marked_ready': {
const readyUser = retro.users.find(w => w.id === parsedEvent.userId);
retro.readyUsers = JSON.parse(parsedEvent.value);
notifications.success(`${readyUser.name} is done brainstorming.`);
break;
}
case 'user_marked_unready': {
const unreadyUser = retro.users.find(w => w.id === parsedEvent.userId);
retro.readyUsers = JSON.parse(parsedEvent.value);
notifications.warning(
`${unreadyUser.name} is no longer done brainstorming.`,
);
break;
}
case 'item_moved': {
const parsedValue = JSON.parse(parsedEvent.value);
const updatedItems = [...retro.items];
Expand Down Expand Up @@ -326,6 +343,13 @@
);
};
const handleUserReady = userId => () => {
sendSocketEvent(`user_ready`, userId);
};
const handleUserUnReady = userId => () => {
sendSocketEvent(`user_unready`, userId);
};
const handleItemGroupChange = (itemId, groupId) => {
sendSocketEvent(
`group_item`,
Expand Down Expand Up @@ -702,7 +726,7 @@
<div class="grow flex">
{#if retro.phase === 'intro'}
<div
class="m-auto w-full md:w-3/4 lg:w-2/3 md:py-14 lg:py-20 dark:text-white"
class="m-auto w-full md:w-3/4 lg:w-2/3 md:mt-14 lg:mt-20 dark:text-white"
>
<h2
class="text-3xl md:text-4xl lg:text-5xl font-rajdhani mb-2 tracking-wide"
Expand Down Expand Up @@ -886,17 +910,25 @@
votes="{retro.votes}"
maxVotes="{retro.maxVotes}"
facilitators="{retro.facilitators}"
readyUsers="{retro.readyUsers}"
handleAddFacilitator="{handleAddFacilitator}"
handleRemoveFacilitator="{handleRemoveFacilitator}"
handleUserReady="{handleUserReady}"
handleUserUnReady="{handleUserUnReady}"
phase="{retro.phase}"
/>
{/if}
{/each}
</div>

<div class="w-full md:w-1/3 p-2 dark:text-white hidden">
<InviteUser hostname="{hostname}" retroId="{retro.id}" />
</div>
{#if retro.phase === 'intro'}
<div class="mt-4 flex w-full p-2 dark:text-white justify-center">
<div
class="w-full md:w-1/2 lg:w-1/3 p-4 bg-white dark:bg-gray-800 shadow-lg rounded-lg"
>
<InviteUser hostname="{hostname}" retroId="{retro.id}" />
</div>
</div>
{/if}
</div>
</div>
{/if}
Expand Down

0 comments on commit 2d4ee5f

Please sign in to comment.