From ae6f8696118cc9d4808a8ee9270de2b25850dd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Mon, 22 Jun 2026 16:09:09 +0200 Subject: [PATCH] feat(transactions): release transaction analysis to all users (#579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Removes the `TransactionAnalysis` Pennant feature flag and all its gating. After this PR the transaction analysis feature (analysis drawer + saved filters) is available to every user. ## Changes - Delete `app/Features/TransactionAnalysis.php`. - `TransactionAnalysisController` — drop the `abort_unless(...403)` flag gate and Pennant imports. - `HandleInertiaRequests` — stop sharing the `transactionAnalysis` flag in Inertia props. - `Features` TS type — remove the `transactionAnalysis` field. - `transaction-actions-menu.tsx` / `transaction-filters.tsx` — remove the `features.transactionAnalysis &&` gates so the Analysis button, the analysis drawer, and saved filters always render. - Tests updated: dropped the endpoint-gating test and the "hidden when flag off" UI test; adjusted shared-flag expectations. ## Verification - `./vendor/bin/pest` — 23 passed (affected suites) - Vitest — affected component/page tests pass - Pint, Prettier, ESLint clean ## Notes - The orphaned value left in Pennant's `features` DB table is harmless and not addressed here. --- app/Features/TransactionAnalysis.php | 19 --- .../Api/TransactionAnalysisController.php | 4 - app/Http/Middleware/HandleInertiaRequests.php | 4 - .../transaction-actions-menu.test.tsx | 16 +-- .../transactions/transaction-actions-menu.tsx | 129 ++++++++---------- .../transactions/transaction-filters.tsx | 5 +- .../js/pages/settings/connections.test.tsx | 1 - resources/js/types/index.d.ts | 1 - tests/Feature/InertiaSharedDataTest.php | 1 - tests/Feature/TransactionAnalysisTest.php | 9 -- 10 files changed, 62 insertions(+), 127 deletions(-) delete mode 100644 app/Features/TransactionAnalysis.php diff --git a/app/Features/TransactionAnalysis.php b/app/Features/TransactionAnalysis.php deleted file mode 100644 index 260c4ff7..00000000 --- a/app/Features/TransactionAnalysis.php +++ /dev/null @@ -1,19 +0,0 @@ -user(); - abort_unless(Feature::for($user)->active(TransactionAnalysis::class), 403); - $validated = $request->validated(); $currency = $user->currency_code; diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 2d969681..f7edc8fe 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -5,7 +5,6 @@ namespace App\Http\Middleware; use App\Enums\BankingConnectionStatus; use App\Enums\BankingProvider; use App\Features\CalculateBalancesOnImport; -use App\Features\TransactionAnalysis; use App\Models\BankingConnection; use App\Services\CurrencyOptions; use Illuminate\Foundation\Inspiring; @@ -179,19 +178,16 @@ class HandleInertiaRequests extends Middleware return [ 'cashflow' => true, 'calculateBalancesOnImport' => false, - 'transactionAnalysis' => false, ]; } $features = Feature::for($user)->values([ CalculateBalancesOnImport::class, - TransactionAnalysis::class, ]); return [ 'cashflow' => true, 'calculateBalancesOnImport' => $features[CalculateBalancesOnImport::class] !== false, - 'transactionAnalysis' => $features[TransactionAnalysis::class] !== false, ]; } diff --git a/resources/js/components/transactions/transaction-actions-menu.test.tsx b/resources/js/components/transactions/transaction-actions-menu.test.tsx index b4700d38..08660c7c 100644 --- a/resources/js/components/transactions/transaction-actions-menu.test.tsx +++ b/resources/js/components/transactions/transaction-actions-menu.test.tsx @@ -1,11 +1,9 @@ import { type TransactionFilters } from '@/types/transaction'; import { fireEvent, render, screen } from '@testing-library/react'; import type React from 'react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { TransactionActionsMenu } from './transaction-actions-menu'; -const features = { transactionAnalysis: true }; - vi.mock('@/actions/App/Http/Controllers/TransactionController', () => ({ categorize: { url: () => '/transactions/categorize' }, })); @@ -22,7 +20,6 @@ vi.mock('@inertiajs/react', () => ({ Link: ({ children, href }: { children: React.ReactNode; href: string }) => ( {children} ), - usePage: () => ({ props: { features } }), })); vi.mock('./import-transactions-drawer', () => ({ @@ -61,17 +58,6 @@ function renderMenu(filters: TransactionFilters) { } describe('TransactionActionsMenu analysis button', () => { - beforeEach(() => { - features.transactionAnalysis = true; - }); - - it('is hidden when the TransactionAnalysis feature flag is off', () => { - features.transactionAnalysis = false; - renderMenu(emptyFilters); - - expect(screen.queryByText('Analysis')).not.toBeInTheDocument(); - }); - it('is disabled when no filter is applied', () => { renderMenu(emptyFilters); diff --git a/resources/js/components/transactions/transaction-actions-menu.tsx b/resources/js/components/transactions/transaction-actions-menu.tsx index ccdc2858..f704ed13 100644 --- a/resources/js/components/transactions/transaction-actions-menu.tsx +++ b/resources/js/components/transactions/transaction-actions-menu.tsx @@ -17,7 +17,6 @@ import { useIsMobile } from '@/hooks/use-mobile'; import { useReEvaluateAllTransactions } from '@/hooks/use-re-evaluate-all-transactions'; import { hasActiveFilters } from '@/lib/transaction-filter-serialization'; -import { type SharedData } from '@/types'; import { type Account, type Bank } from '@/types/account'; import { type AutomationRule } from '@/types/automation-rule'; import { type Category } from '@/types/category'; @@ -26,7 +25,7 @@ import { type TransactionFilters, } from '@/types/transaction'; import { __ } from '@/utils/i18n'; -import { Link, usePage } from '@inertiajs/react'; +import { Link } from '@inertiajs/react'; import { BarChart3, ChevronDown, @@ -61,7 +60,6 @@ export function TransactionActionsMenu({ onImportComplete, filters, }: TransactionActionsMenuProps) { - const { features } = usePage().props; const isMobile = useIsMobile(); const [importDrawerOpen, setImportDrawerOpen] = useState(false); const [analysisDrawerOpen, setAnalysisDrawerOpen] = useState(false); @@ -106,65 +104,60 @@ export function TransactionActionsMenu({ return ( <> - {features.transactionAnalysis && - (isMobile ? ( - - - - - - {!canAnalyze && ( - - {__( - 'Apply a filter to enable this button', - )} - - )} - - - ) : ( - - - - - - {!canAnalyze && ( - - {__( - 'Apply a filter to enable this button', - )} - - )} - - - ))} + {isMobile ? ( + + + + + + {!canAnalyze && ( + + {__('Apply a filter to enable this button')} + + )} + + + ) : ( + + + + + + {!canAnalyze && ( + + {__('Apply a filter to enable this button')} + + )} + + + )} @@ -273,13 +266,11 @@ export function TransactionActionsMenu({ onImportComplete={onImportComplete} /> - {features.transactionAnalysis && ( - - )} + ); } diff --git a/resources/js/components/transactions/transaction-filters.tsx b/resources/js/components/transactions/transaction-filters.tsx index 50cb3bba..236ae37f 100644 --- a/resources/js/components/transactions/transaction-filters.tsx +++ b/resources/js/components/transactions/transaction-filters.tsx @@ -1,5 +1,4 @@ import { __ } from '@/utils/i18n'; -import { usePage } from '@inertiajs/react'; import { format } from 'date-fns'; import * as Icons from 'lucide-react'; import { ChevronsUpDown, Tag, X } from 'lucide-react'; @@ -32,7 +31,6 @@ import { toggleCategorySelection, } from '@/lib/category-tree'; import { cn } from '@/lib/utils'; -import { type SharedData } from '@/types'; import { type Account } from '@/types/account'; import { type Category, getCategoryColorClasses } from '@/types/category'; import { getLabelColorClasses, type Label } from '@/types/label'; @@ -60,7 +58,6 @@ export function TransactionFilters({ hideAccountFilter = false, enableSavedFilters = false, }: TransactionFiltersProps) { - const { features } = usePage().props; const [isOpen, setIsOpen] = useState(false); const [categoryDropdownOpen, setCategoryDropdownOpen] = useState(false); const [categorySearch, setCategorySearch] = useState(''); @@ -733,7 +730,7 @@ export function TransactionFilters({ />
- {enableSavedFilters && features.transactionAnalysis && ( + {enableSavedFilters && ( ({ features: { cashflow: true, calculateBalancesOnImport: false, - transactionAnalysis: false, }, }, }), diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index c6a7e3e9..525a0033 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -42,7 +42,6 @@ export interface NavDivider { export interface Features { cashflow: boolean; calculateBalancesOnImport: boolean; - transactionAnalysis: boolean; } export interface ExpiredBankingConnectionNotification { diff --git a/tests/Feature/InertiaSharedDataTest.php b/tests/Feature/InertiaSharedDataTest.php index 120aab4f..a2d2c691 100644 --- a/tests/Feature/InertiaSharedDataTest.php +++ b/tests/Feature/InertiaSharedDataTest.php @@ -43,7 +43,6 @@ test('shared feature flags do not include coinbase flag', function () { expect($props['features'])->toBe([ 'cashflow' => true, 'calculateBalancesOnImport' => false, - 'transactionAnalysis' => false, ]); }); diff --git a/tests/Feature/TransactionAnalysisTest.php b/tests/Feature/TransactionAnalysisTest.php index 2e33bfd9..dca09186 100644 --- a/tests/Feature/TransactionAnalysisTest.php +++ b/tests/Feature/TransactionAnalysisTest.php @@ -1,7 +1,6 @@ user = User::factory()->create(['currency_code' => 'USD']); $this->actingAs($this->user); - Feature::for($this->user)->activate(TransactionAnalysis::class); $this->account = Account::factory()->create([ 'user_id' => $this->user->id, @@ -34,12 +31,6 @@ function makeTransaction(array $attributes = []): Transaction ]); } -test('analysis endpoint is gated behind the TransactionAnalysis feature flag', function () { - Feature::for($this->user)->deactivate(TransactionAnalysis::class); - - $this->getJson('/api/transactions/analysis')->assertForbidden(); -}); - test('analysis response is not cached between users', function () { $this->getJson('/api/transactions/analysis') ->assertOk()