Skip to content

feat: update epic action#81

Merged
hmbanan666 merged 1 commit into
mainfrom
epic-update
Aug 20, 2025
Merged

feat: update epic action#81
hmbanan666 merged 1 commit into
mainfrom
epic-update

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Aug 20, 2025

Summary by CodeRabbit

  • New Features
    • Added an update epic flow with a validated form and success/error toasts.
    • Introduced a modal to edit an epic, triggered from the epic page, prefilled with existing data.
    • Added Russian translations for the update epic title and button.
  • Bug Fixes
    • Edit action now appears only when an epic exists, preventing broken interactions.
  • Chores
    • Backend support added to handle epic updates.

@hmbanan666 hmbanan666 self-assigned this Aug 20, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 20, 2025

Walkthrough

Adds epic update capability: new UpdateEpic form and modal components, integration on epic detail page to open modal, i18n keys for update UI, a PATCH API endpoint to update an epic by ID, and a shared updateEpicSchema/type. Also adjusts the epics list page title to use i18n.

Changes

Cohort / File(s) Summary
Epic update form component
apps/web-app/app/components/form/UpdateEpic.vue
New form SFC to edit epic title/description. Loads epic by prop epicId, validates with updateEpicSchema, PATCHes /api/epic/id/{epicId}, refreshes store, emits submitted/success, shows toasts.
Epic update modal component
apps/web-app/app/components/modal/UpdateEpic.vue
New modal SFC wrapping the form; passes epicId; closes overlay on submitted/success; uses i18n title.
Epic detail page integration
apps/web-app/app/pages/epic/[epicId]/index.vue
Renders edit button only when epic.id exists; opens UpdateEpic modal via overlay.create and passes epicId.
Epics list page i18n title
apps/web-app/app/pages/epic/index.vue
Head title now uses t('app.menu.epics') instead of hard-coded text.
i18n additions
apps/web-app/i18n/locales/ru-RU.json
Adds app.update.epic: { button, title } for Russian locale.
Epic update API endpoint
apps/web-app/server/api/epic/id/[epicId]/index.patch.ts
New PATCH handler: validates body with updateEpicSchema, updates via repository.epic.update, returns { ok, result }, handles errors via errorResolver.
Shared epic update schema
apps/web-app/shared/services/epic.ts
Adds updateEpicSchema with optional title/description constraints and exported UpdateEpic type.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Page as Epic Detail Page
  participant Overlay as Overlay Controller
  participant Modal as ModalUpdateEpic
  participant Form as FormUpdateEpic
  participant API as PATCH /api/epic/id/{epicId}
  participant Repo as repository.epic.update
  participant Store as Epic Store
  participant Toast as Toast

  Page->>Overlay: create(ModalUpdateEpic)
  Page->>Overlay: open({ epicId })
  Overlay->>Modal: mount with { epicId }
  Modal->>Form: render with { epicId }

  Form->>Form: validate(updateEpicSchema)
  Form->>API: submit PATCH { title?, description? }
  API->>Repo: update(epicId, data)
  Repo-->>API: epic
  API-->>Form: { ok: true, result }

  Form->>Store: refresh()
  Form->>Toast: show('toast.epic-updated')
  Form-->>Modal: emit('submitted'), emit('success')
  Modal->>Overlay: closeAll()

  alt error
    API-->>Form: error
    Form->>Toast: show(error)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: action create epic #80 — Earlier addition of the epic detail page edit control; this PR replaces the no-op with a functional modal-driven update flow.

Poem

A hop, a tap, a tidy tweak—
I patch an epic, sleek and chic.
A modal blooms, the fields align,
The schema nods: “Your length is fine.”
With gentle clicks and toasts that sing,
I bounce away—update in spring! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch epic-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (12)
apps/web-app/app/pages/epic/index.vue (1)

95-96: Make document title reactive to locale changes

useHead called with a static object won’t update when the user switches language. Use the functional form so the title recomputes on locale change.

-useHead({
-  title: t('app.menu.epics'),
-})
+useHead(() => ({
+  title: t('app.menu.epics'),
+}))

Minor related nit (outside this hunk): the import of the RU locale from date-fns is typically either a default import from the deep path or a named import from the root locale module. Current code uses a named import from a deep path, which can break depending on bundler.

  • Option A (preferred): import { ru } from 'date-fns/locale'
  • Option B: import ru from 'date-fns/locale/ru'
apps/web-app/app/components/modal/UpdateEpic.vue (1)

4-8: Close the modal on success only (avoid closing on “submitted”)

Closing on submitted can dismiss the modal even if client/server validation fails. Prefer keeping it open until a confirmed success.

 <FormUpdateEpic
   :epic-id="epicId"
-  @submitted="overlay.closeAll"
   @success="overlay.closeAll"
 />
apps/web-app/server/api/epic/id/[epicId]/index.patch.ts (3)

21-21: Guard against empty updates

Both fields in updateEpicSchema are optional, so {} passes validation and results in a no-op update. Consider rejecting empty payloads with a 400.

-    const epic = await repository.epic.update(epicId, data)
+    if (Object.keys(data).length === 0) {
+      throw createError({
+        statusCode: 400,
+        message: 'Nothing to update',
+      })
+    }
+
+    const epic = await repository.epic.update(epicId, data)

21-26: Return 404 when epic is not found

If the repository returns null/undefined when the epic doesn’t exist, return a 404 instead of { ok: true, result: null }.

-    const epic = await repository.epic.update(epicId, data)
-
-    return {
-      ok: true,
-      result: epic,
-    }
+    const epic = await repository.epic.update(epicId, data)
+    if (!epic) {
+      throw createError({
+        statusCode: 404,
+        message: 'Epic not found',
+      })
+    }
+    return { ok: true, result: epic }

16-19: Use schema.assert for validation in this endpoint

Switching to updateEpicSchema.assert(body) will perform validation and throw a typed error automatically, eliminating the manual instanceof type.errors check and the extra type import.

• File: apps/web-app/server/api/epic/id/[epicId]/index.patch.ts
• Lines: 15–19

Suggested change:

-import { type } from 'arktype'
     const body = await readBody(event)
-    const data = updateEpicSchema(body)
-    if (data instanceof type.errors) {
-      throw data
-    }
+    // validate and throw on error
+    const data = updateEpicSchema.assert(body)

Note: Ensure your ArkType version supports the .assert API before applying. You may optionally apply this pattern across other endpoints to simplify schema validation.

apps/web-app/shared/services/epic.ts (1)

9-14: Remove redundant “| undefined” in optional fields

Using .optional() already covers absence. The | undefined union is redundant and slightly obscures intent.

 export const updateEpicSchema = type({
-  title: type('2 <= string <= 150 | undefined').describe('error.length.invalid').optional(),
-  description: type('string <= 1500 | undefined').describe('error.length.invalid').optional(),
+  title: type('2 <= string <= 150').describe('error.length.invalid').optional(),
+  description: type('string <= 1500').describe('error.length.invalid').optional(),
 })
apps/web-app/app/pages/epic/[epicId]/index.vue (3)

9-17: Overlay-driven Edit action is correctly gated; add i18n + aria-label for accessibility

Good use of v-if guarding and overlay modal integration. Consider moving the hardcoded Russian tooltip into i18n and adding an aria-label to the icon-only button.

-        <UTooltip v-if="epic?.id" :text="`Редактировать «${epic?.title}»`">
+        <UTooltip v-if="epic?.id" :text="t('app.update.epic.button')">
           <UButton
             variant="outline"
             color="neutral"
             size="md"
             icon="i-lucide-pencil"
             class="size-10 justify-center"
+            :aria-label="t('app.update.epic.button')"
             @click="modalUpdateEpic.open({ epicId: epic.id })"
           />
         </UTooltip>

30-31: Simplify condition for comments block

epic?.comments.length >= 0 is always true for an array. If the intent is to show the block only when there are comments, check for length > 0. If the intent is simply “comments exist”, the length check is redundant.

-        <div v-if="epic?.comments && epic?.comments.length >= 0" class="text-muted">
+        <div v-if="epic?.comments?.length" class="text-muted">

83-85: Make page title reactive to epic store updates

useHead({ ... }) with a plain object captures the initial value only. Wrap it in a function to keep it reactive and provide a fallback title.

-useHead({
-  title: epic.value?.title,
-})
+useHead(() => ({
+  title: epic.value?.title ?? t('app.menu.epic'),
+}))
apps/web-app/app/components/form/UpdateEpic.vue (3)

16-24: Localize the description placeholder

Hardcoded Russian text should be moved to i18n for consistency.

-        placeholder="Суть. Для чего создается, что будет обсуждаться, что будет сделано?"
+        :placeholder="$t('app.update.epic.description.placeholder')"

Please ensure the key app.update.epic.description.placeholder exists in locales (ru-RU.json and others as needed).


26-34: Prevent double submissions by binding loading state to the submit button

Add a pending state to disable the button and show a loader during submission.

     <UButton
       type="submit"
       variant="solid"
       color="secondary"
       size="xl"
       block
       class="mt-3"
+      :loading="pending"
+      :disabled="pending"
       :label="$t('common.update')"
     />

60-78: Manage pending state and ensure toast lifecycle is balanced

Track pending around the async operation and reset it in finally. Also emit 'submitted' before network call as you do (good).

-async function onSubmit(event: FormSubmitEvent<UpdateEpic>) {
-  const toastId = actionToast.start()
-  emit('submitted')
-
-  try {
-    await $fetch(`/api/epic/id/${epicId}`, {
-      method: 'PATCH',
-      body: event.data,
-    })
-
-    await epicStore.update()
-
-    actionToast.success(toastId, t('toast.epic-updated'))
-    emit('success')
-  } catch (error) {
-    console.error(error)
-    actionToast.error(toastId)
-  }
-}
+const pending = ref(false)
+
+async function onSubmit(event: FormSubmitEvent<UpdateEpic>) {
+  const toastId = actionToast.start()
+  emit('submitted')
+  pending.value = true
+
+  try {
+    await $fetch(`/api/epic/id/${epicId}`, {
+      method: 'PATCH',
+      body: event.data,
+    })
+    await epicStore.update()
+    actionToast.success(toastId, t('toast.epic-updated'))
+    emit('success')
+  } catch (error) {
+    console.error(error)
+    actionToast.error(toastId)
+  } finally {
+    pending.value = false
+  }
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 972fccf and 40a16f8.

📒 Files selected for processing (7)
  • apps/web-app/app/components/form/UpdateEpic.vue (1 hunks)
  • apps/web-app/app/components/modal/UpdateEpic.vue (1 hunks)
  • apps/web-app/app/pages/epic/[epicId]/index.vue (2 hunks)
  • apps/web-app/app/pages/epic/index.vue (1 hunks)
  • apps/web-app/i18n/locales/ru-RU.json (1 hunks)
  • apps/web-app/server/api/epic/id/[epicId]/index.patch.ts (1 hunks)
  • apps/web-app/shared/services/epic.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web-app/server/api/epic/id/[epicId]/index.patch.ts (1)
apps/web-app/shared/services/epic.ts (1)
  • updateEpicSchema (9-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
apps/web-app/i18n/locales/ru-RU.json (2)

288-292: LGTM — consistent with existing i18n patterns

Keys and wording align with the rest of the update section. No issues.


288-292: No additional locales detected—no further action needed

Only apps/web-app/i18n/locales/ru-RU.json exists in the i18n/locales directory, so there are no other locale files requiring the new app.update.epic keys.

apps/web-app/app/components/modal/UpdateEpic.vue (1)

1-11: Solid modal wrapper and event wiring

Props, slot usage, and overlay integration look clean. Using $t directly in the template is consistent with other components.

apps/web-app/server/api/epic/id/[epicId]/index.patch.ts (1)

5-13: Confirm auth/permissions are enforced upstream

This endpoint updates epics. Verify it’s behind appropriate auth/permission middleware (e.g., user is authorized to edit this epic), either via route rules or a preceding handler.

apps/web-app/app/pages/epic/[epicId]/index.vue (2)

72-72: Importing ModalUpdateEpic from #components looks correct

The import path aligns with Nuxt auto-generated component exports.


80-82: Overlay controller creation is appropriate

Using overlay.create(ModalUpdateEpic) and invoking open({ epicId }) is idiomatic for your overlay modal pattern.

Comment on lines +2 to +7
<UForm
:validate="createValidator(updateEpicSchema)"
:state="state"
class="flex flex-col gap-3"
@submit="onSubmit"
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Validate availability of createValidator

createValidator isn’t imported here. If it’s not globally auto-imported, this will throw at runtime. Please confirm it’s provided by your project’s auto-imports, or import it explicitly.

Run this script to locate its definition and ensure it’s exported/auto-imported:


🏁 Script executed:

#!/bin/bash
# Find where `createValidator` is defined/exported and how it's imported elsewhere
rg -n -C2 --type=ts --type=vue '\bcreateValidator\b'

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Search for any definition or import of `createValidator` across the repo
rg -n 'createValidator' .

Length of output: 3791


🏁 Script executed:

#!/bin/bash
# Show the first 20 lines of UpdateEpic.vue and CreateProduct.vue for import context
head -n 20 apps/web-app/app/components/form/UpdateEpic.vue
echo "----"
head -n 20 apps/web-app/app/components/form/CreateProduct.vue

Length of output: 1266


🏁 Script executed:

#!/bin/bash
echo "---- UpdateEpic.vue (first 200 lines) ----"
head -n200 apps/web-app/app/components/form/UpdateEpic.vue
echo ""
echo "---- CreateProduct.vue (first 200 lines) ----"
head -n200 apps/web-app/app/components/form/CreateProduct.vue
echo ""
echo "---- Search for AutoImport in Vite config ----"
rg -n "AutoImport" -g "vite.config.*"
rg -n "autoImport" -g "vite.config.*"
rg -n "unplugin-auto-import" -g "vite.config.*"

Length of output: 4546


🏁 Script executed:

#!/bin/bash
# Check if unplugin-auto-import is listed in dependencies
rg -n 'unplugin-auto-import' package.json

# Search Nuxt config for auto-import plugin configuration
rg -n 'unplugin-auto-import' -g 'nuxt.config.*'
rg -n 'autoImport' -g 'nuxt.config.*'

Length of output: 127


Import createValidator in UpdateEpic.vue

It’s defined in apps/web-app/app/utils/ui.ts but isn’t auto-imported, so you’ll get a runtime error. Add an explicit import (or configure auto-imports) in your <script setup>.

• File: apps/web-app/app/components/form/UpdateEpic.vue
• At the top of the <script setup lang="ts"> block

Suggested diff:

<script setup lang="ts">
+ import { createValidator } from '../../utils/ui'
  import type { UpdateEpic } from '#shared/services/epic'
  import type { FormSubmitEvent } from '@nuxt/ui'
  import { updateEpicSchema } from '#shared/services/epic'
  …
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<UForm
:validate="createValidator(updateEpicSchema)"
:state="state"
class="flex flex-col gap-3"
@submit="onSubmit"
>
<script setup lang="ts">
import { createValidator } from '../../utils/ui'
import type { UpdateEpic } from '#shared/services/epic'
import type { FormSubmitEvent } from '@nuxt/ui'
import { updateEpicSchema } from '#shared/services/epic'
// …
</script>
🤖 Prompt for AI Agents
In apps/web-app/app/components/form/UpdateEpic.vue around lines 2 to 7, the
template uses createValidator but the symbol is not imported, causing a runtime
error; open the <script setup lang="ts"> block at the top of the file and add an
explicit import for createValidator from the module apps/web-app/app/utils/ui.ts
(or enable/configure your auto-imports to include it), then save and rebuild to
ensure the component resolves createValidator at runtime.

Comment on lines +55 to +58
const state = ref<Partial<UpdateEpic>>({
title: epic.value?.title,
description: epic.value?.description ?? undefined,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Form state won’t prefill if the epic arrives after mount

state is initialized from epic.value once. If the store loads asynchronously, inputs remain empty. Initialize reactively once when the epic becomes available.

-const state = ref<Partial<UpdateEpic>>({
-  title: epic.value?.title,
-  description: epic.value?.description ?? undefined,
-})
+const state = reactive<Partial<UpdateEpic>>({
+  title: epic.value?.title,
+  description: epic.value?.description ?? undefined,
+})
+
+// Initialize from store once when epic becomes available to avoid overriding user edits
+const initialized = ref(false)
+watch(epic, (val) => {
+  if (val && !initialized.value) {
+    state.title = val.title
+    state.description = val.description ?? undefined
+    initialized.value = true
+  }
+}, { immediate: true })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const state = ref<Partial<UpdateEpic>>({
title: epic.value?.title,
description: epic.value?.description ?? undefined,
})
-const state = ref<Partial<UpdateEpic>>({
- title: epic.value?.title,
- description: epic.value?.description ?? undefined,
const state = reactive<Partial<UpdateEpic>>({
title: epic.value?.title,
description: epic.value?.description ?? undefined,
})
// Initialize from store once when epic becomes available to avoid overriding user edits
const initialized = ref(false)
watch(epic, (val) => {
if (val && !initialized.value) {
state.title = val.title
state.description = val.description ?? undefined
initialized.value = true
}
}, { immediate: true })
🤖 Prompt for AI Agents
In apps/web-app/app/components/form/UpdateEpic.vue around lines 55–58, the
reactive form state is only initialized once from epic.value so inputs stay
empty if epic loads after mount; add a watcher on epic (watch(epic, ..., {
immediate: true })) that sets state.title and state.description from epic.value
when epic becomes available, but only assign each field if it is currently
undefined/null to avoid clobbering user edits.

result: epic,
}
} catch (error) {
throw errorResolver(error)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure errorResolver is in scope (or import it)

errorResolver isn’t imported here. If it’s not globally available in the server runtime, this will throw at runtime.


🏁 Script executed:

#!/bin/bash
# Find definition/imports of errorResolver to confirm availability
rg -nP '(^\s*(export\s+)?(const|function)\s+errorResolver\b)|(\berrorResolver\s*=\s*)|from\s+.*errorResolver' -C2

Length of output: 1528


🏁 Script executed:

#!/bin/bash
# List import statements to verify if errorResolver is imported
rg -n '^import ' 'apps/web-app/server/api/epic/id/[epicId]/index.patch.ts'

Length of output: 222


Add missing import for errorResolver

The call to

throw errorResolver(error)

in apps/web-app/server/api/epic/id/[epicId]/index.patch.ts will fail at runtime because errorResolver isn’t imported. Please add, for example:

// at the top of apps/web-app/server/api/epic/id/[epicId]/index.patch.ts
import { errorResolver } from '../../../utils/error'

—or, if you’re using path aliases:

import { errorResolver } from '~~/server/utils/error'
🤖 Prompt for AI Agents
In apps/web-app/server/api/epic/id/[epicId]/index.patch.ts around line 28, the
call to throw errorResolver(error) will fail because errorResolver is not
imported; add the appropriate import at the top of the file (either the relative
path import from ../../../utils/error or your project alias import such as
~~/server/utils/error) so errorResolver is defined before use.

@hmbanan666 hmbanan666 merged commit b9ac586 into main Aug 20, 2025
8 checks passed
@hmbanan666 hmbanan666 deleted the epic-update branch August 20, 2025 08:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant