Skip to content

Commit

Permalink
add retro feature
Browse files Browse the repository at this point in the history
  • Loading branch information
JensRavens committed Jan 14, 2025
1 parent 2784c24 commit 37efe03
Show file tree
Hide file tree
Showing 19 changed files with 372 additions and 104 deletions.
2 changes: 1 addition & 1 deletion app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def home
@upcoming_leaves = current_user.leaves.future.not_rejected.chronologic
sprint_feedback = current_user.sprint_feedbacks.find_by(sprint: @sprint) if @sprint
@daily_nerd_message = DailyNerdMessage.find_by(created_at: Time.zone.today.all_day, sprint_feedback:) || sprint_feedback.daily_nerd_messages.build if sprint_feedback
@needs_retro = current_user.sprint_feedbacks.sprint_past.retro_missing.first
@needs_retro_for = SprintFeedback.where(user: current_user).sprint_past.reverse_chronologic.limit(2).find { !_1.retro_completed? }
end

def offline
Expand Down
12 changes: 3 additions & 9 deletions app/controllers/sprint_feedbacks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,11 @@ def create
ui.replace feedback.sprint
end

def edit
def edit_retro
end

def update
if @feedback.update feedback_update_attributes
@user = @feedback.user
ui.close_popover
ui.replace @feedback.sprint
else
render :edit, status: :unprocessable_entity
end
def update_retro
@feedback.update! feedback_update_attributes
end

def destroy
Expand Down
15 changes: 15 additions & 0 deletions app/frontend/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.checkbox {
&__input {
display: block;
width: 20px;
height: 20px;
border: 1px solid currentColor;

&:checked::before {
display: block;
content: 'X';
width: 10px;
height: 10px;
}
}
}
92 changes: 92 additions & 0 deletions app/frontend/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { ReactNode } from 'react';

import { Text } from '../text/text';
import { FormField, useInputId } from '../form_field/form_field';
import classnames from 'classnames';
import './checkbox.scss';

interface Props extends FormField<boolean> {
label?: ReactNode;
}

export function Checkbox({
name,
value,
ariaLabel,
placeholder,
required,
disabled,
readOnly,
inputId,
label,
touched,
errors,
onChange,
onBlur,
onFocus,
}: Props): JSX.Element {
inputId = useInputId(inputId);
return (
<div className="checkbox__container">
<div
className={classnames(
'checkbox',
{
'checkbox--filled': !!value,
'checkbox--readonly': readOnly,
'checkbox--disabled': disabled,
'checkbox--placeholder': placeholder,
},
{ disabled }
)}
>
<div className="checkbox__content">
{label !== undefined && (
<label
className={classnames('checkbox__label', {
'checkbox__label--disabled': disabled,
})}
htmlFor={inputId}
>
{label}
</label>
)}
<Text>
<input
id={inputId}
name={name}
className={classnames('checkbox__input')}
readOnly={readOnly}
checked={value}
type="checkbox"
onChange={(event) => {
if (readOnly) {
event.preventDefault();
return;
}
onChange?.(event.target.checked);
}}
onFocus={() => {
onFocus?.();
}}
onBlur={() => {
onBlur?.();
}}
placeholder={placeholder}
required={required}
disabled={disabled}
aria-label={ariaLabel}
/>
</Text>
</div>
{touched && errors && (
<div className="checkbox__errors">
{errors.map((error) => (
<Text key={error}>{error}</Text>
))}
</div>
)}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion app/frontend/components/number_field/number_field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function NumberField(props: Props): JSX.Element {
<TextField
{...props}
value={props.value.toString()}
onChange={(value) => Number(value)}
onChange={(value) => props.onChange?.(Number(value))}
/>
);
}
13 changes: 12 additions & 1 deletion app/frontend/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"last_payments": "Last payments",
"remaining_holidays": "Remaining holidays",
"number_holidays_left": "You have %{count} days off left",
"payslip_archive": "archive"
"payslip_archive": "archive",
"missing_retro": "Retrospective Notes Missing",
"missing_retro_description": "You did not yet leave a rating for sprint %{title}.",
"leave_retro_notes": "Rate the sprint now"
}
},
"sprints": {
Expand All @@ -18,6 +21,14 @@
"save": "Save"
}
},
"sprint_feedbacks": {
"edit_retro": {
"skip": "Skip rating this sprint",
"rating": "Rating",
"text": "Text",
"save": "Save"
}
},
"users": {
"index": {
"title": "Users",
Expand Down
1 change: 1 addition & 0 deletions app/models/sprint_feedback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class SprintFeedback < ApplicationRecord
validates :retro_rating, numericality: {only_integer: true, in: 1..5}, allow_nil: true

scope :ordered, -> { joins(:user).order("users.email ASC") }
scope :reverse_chronologic, -> { joins(:sprint).order("LOWER(sprints.sprint_during) DESC") }
scope :retro_missing, -> { where(retro_rating: nil, skip_retro: false) }
scope :retro_not_skipped, -> { where(skip_retro: false) }
scope :sprint_past, -> {
Expand Down
8 changes: 8 additions & 0 deletions app/policies/sprint_feedback_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ def create?
hr?
end

def edit_retro?
update?
end

def update_retro?
update?
end

def update?
hr? || record.user == user
end
Expand Down
30 changes: 19 additions & 11 deletions app/views/pages/_daily_nerd_card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,24 @@ export function DailyNerdCard({ id, message }: Props): JSX.Element {
},
});
return (
<Card title="Daily Nerd" icon="📝">
<TextArea
{...fields.message}
label="Message"
placeholder="How was your day? What did you learn?"
/>
<Button
title={id ? 'Update daily nerd message' : 'Create daily nerd message'}
onClick={onSubmit}
/>
</Card>
<Card
title="Daily Nerd"
icon="📝"
subtitle={
<>
<TextArea
{...fields.message}
label="Message"
placeholder="How was your day? What did you learn?"
/>
<Button
title={
id ? 'Update daily nerd message' : 'Create daily nerd message'
}
onClick={onSubmit}
/>
</>
}
/>
);
}
5 changes: 5 additions & 0 deletions app/views/pages/home.props.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@
field :id, null: true
field :message, null: true
end

field :needs_retro_for, null: true, value: -> { @needs_retro_for } do
field :id
field :title, value: -> { sprint.title }
end
19 changes: 18 additions & 1 deletion app/views/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default function Home({
payslips,
remainingHolidays,
dailyNerdMessage,
needsRetroFor,
},
}: PageProps<'pages/home'>): JSX.Element {
const t = useTranslate();
Expand All @@ -26,8 +27,24 @@ export default function Home({
<Text type="headline">
{t('pages.home.hello', { name: currentUser.displayName })}
</Text>
{dailyNerdMessage && <DailyNerdCard {...dailyNerdMessage} />}
<Columns>
{needsRetroFor && (
<Card
icon="🚀"
title={t('pages.home.missing_retro')}
subtitle={
<Stack>
{t('pages.home.missing_retro_description', {
title: needsRetroFor.title,
})}
<Link href={`/en/sprint_feedbacks/${needsRetroFor.id}`}>
{t('pages.home.leave_retro_notes')}
</Link>
</Stack>
}
/>
)}
{dailyNerdMessage && <DailyNerdCard {...dailyNerdMessage} />}
{upcomingLeaves.length > 0 && (
<Card
icon="🏝️"
Expand Down
10 changes: 10 additions & 0 deletions app/views/sprint_feedbacks/edit_retro.props.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

render "components/current_user"

field :feedback, value: -> { @feedback } do
field :id
field :retro_rating, Integer, null: true
field :retro_text, null: true
field :skip_retro, Boolean
end
74 changes: 74 additions & 0 deletions app/views/sprint_feedbacks/edit_retro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { PageProps } from '../../../data.d';
import { useTranslate } from '../../frontend/util/dependencies';
import { Stack } from '../../frontend/components/stack/stack';
import { Button } from '../../frontend/components/button/button';
import { useForm } from '@nerdgeschoss/react-use-form-library';
import { TextField } from '../../frontend/components/text_field/text_field';
import { useReaction } from '../../frontend/sprinkles/reaction';
import { useModalInfo } from '../../frontend/components/modal/modal';
import { NumberField } from '../../frontend/components/number_field/number_field';
import { Checkbox } from '../../frontend/components/checkbox/checkbox';

export default function ({
data: { feedback },
}: PageProps<'sprint_feedbacks/edit_retro'>): JSX.Element {
const t = useTranslate();
const reaction = useReaction();
const modal = useModalInfo();
const { fields, valid, onSubmit } = useForm({
model: {
retroRating: feedback.retroRating ?? 5,
retroText: feedback.retroText ?? '',
skipRetro: feedback.skipRetro,
},
validations: {
retroRating: ({ model }) =>
model.skipRetro || model.retroRating ? [] : ['required'],
retroText: ({ model }) =>
model.skipRetro || model.retroText ? [] : ['required'],
},
onSubmit: async ({ model }) => {
const sprint_feedback = model.skipRetro
? { skipRetro: true, retroText: null, retroRating: null }
: {
skipRetro: false,
retroText: model.retroText,
retroRating: model.retroRating,
};
await reaction.call({
path: `/sprint_feedbacks/${feedback.id}/update_retro`,
method: 'POST',
params: { sprint_feedback },
refresh: true,
});
modal.close();
},
});

return (
<Stack>
<Checkbox
{...fields.skipRetro}
label={t('sprint_feedbacks.edit_retro.skip')}
/>
{!fields.skipRetro.value && (
<>
<NumberField
{...fields.retroRating}
label={t('sprint_feedbacks.edit_retro.rating')}
/>
<TextField
{...fields.retroText}
label={t('sprint_feedbacks.edit_retro.text')}
/>
</>
)}
<Button
title={t('sprint_feedbacks.edit_retro.save')}
disabled={!valid}
onClick={onSubmit}
/>
</Stack>
);
}
2 changes: 2 additions & 0 deletions app/views/sprint_feedbacks/show.props.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
field :tracked_hours, Float
field :billable_hours, Float

field :permit_edit_retro_notes, Boolean, value: -> { helpers.policy(self).update? }

field :sprint do
field :id
field :title
Expand Down
Loading

0 comments on commit 37efe03

Please sign in to comment.