(dashboards) release as first party feature (#3856)

This commit is contained in:
Matiss Janis Aboltins
2024-11-20 08:31:54 +00:00
committed by GitHub
parent 881410bc74
commit 688de5f604
21 changed files with 154 additions and 217 deletions

View File

@@ -22,8 +22,9 @@ export class ReportsPage {
async goToCustomReportPage() {
await this.pageContent
.getByRole('button', { name: 'Create new custom report' })
.getByRole('button', { name: 'Add new widget' })
.click();
await this.page.getByRole('button', { name: 'New custom report' }).click();
return new CustomReportPage(this.page);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -7,7 +7,6 @@ import {
type TimeFrame,
} from 'loot-core/types/models';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { Button } from '../common/Button2';
import { Select } from '../common/Select';
import { SpaceBetween } from '../common/SpaceBetween';
@@ -62,7 +61,6 @@ export function Header({
children,
}: HeaderProps) {
const { t } = useTranslation();
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const { isNarrowWidth } = useResponsive();
return (
@@ -80,7 +78,7 @@ export function Header({
}}
>
<SpaceBetween gap={isNarrowWidth ? 5 : undefined}>
{isDashboardsFeatureEnabled && mode && (
{mode && (
<Button
variant={mode === 'static' ? 'normal' : 'primary'}
onPress={() => {

View File

@@ -20,7 +20,6 @@ import {
} from 'loot-core/src/types/models';
import { useAccounts } from '../../hooks/useAccounts';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { useNavigate } from '../../hooks/useNavigate';
import { breakpoints } from '../../tokens';
import { Button } from '../common/Button2';
@@ -79,28 +78,16 @@ export function Overview() {
const location = useLocation();
sessionStorage.setItem('url', location.pathname);
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const baseLayout = widgets
.map(widget => ({
i: widget.id,
w: widget.width,
h: widget.height,
minW:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 2 : 3,
minH:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 1 : 2,
...widget,
}))
.filter(item => {
if (isDashboardsFeatureEnabled) {
return true;
}
if (item.type === 'custom-report' && !customReportMap.has(item.meta.id)) {
return false;
}
return true;
});
const baseLayout = widgets.map(widget => ({
i: widget.id,
w: widget.width,
h: widget.height,
minW:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 2 : 3,
minH:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 1 : 2,
...widget,
}));
const layout = baseLayout;
@@ -332,86 +319,82 @@ export function Overview() {
>
{currentBreakpoint === 'desktop' && (
<>
{isDashboardsFeatureEnabled && (
<>
<Button
ref={triggerRef}
variant="primary"
isDisabled={isImporting}
onPress={() => setMenuOpen(true)}
>
<Trans>Add new widget</Trans>
</Button>
<Button
ref={triggerRef}
variant="primary"
isDisabled={isImporting}
onPress={() => setMenuOpen(true)}
>
<Trans>Add new widget</Trans>
</Button>
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
if (item === 'custom-report') {
navigate('/reports/custom');
return;
}
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
if (item === 'custom-report') {
navigate('/reports/custom');
return;
}
function isExistingCustomReport(
name: string,
): name is `custom-report-${string}` {
return name.startsWith('custom-report-');
}
if (isExistingCustomReport(item)) {
const [, reportId] = item.split('custom-report-');
onAddWidget<CustomReportWidget>('custom-report', {
id: reportId,
});
return;
}
function isExistingCustomReport(
name: string,
): name is `custom-report-${string}` {
return name.startsWith('custom-report-');
}
if (isExistingCustomReport(item)) {
const [, reportId] = item.split('custom-report-');
onAddWidget<CustomReportWidget>('custom-report', {
id: reportId,
});
return;
}
if (item === 'markdown-card') {
onAddWidget<MarkdownWidget>(item, {
content: t(
'### Text Widget\n\nEdit this widget to change the **markdown** content.',
),
});
return;
}
if (item === 'markdown-card') {
onAddWidget<MarkdownWidget>(item, {
content: t(
'### Text Widget\n\nEdit this widget to change the **markdown** content.',
),
});
return;
}
onAddWidget(item);
}}
items={[
{
name: 'cash-flow-card' as const,
text: t('Cash flow graph'),
},
{
name: 'net-worth-card' as const,
text: t('Net worth graph'),
},
{
name: 'spending-card' as const,
text: t('Spending analysis'),
},
{
name: 'markdown-card' as const,
text: t('Text widget'),
},
{
name: 'custom-report' as const,
text: t('New custom report'),
},
...(customReports.length
? ([Menu.line] satisfies Array<typeof Menu.line>)
: []),
...customReports.map(report => ({
name: `custom-report-${report.id}` as const,
text: report.name,
})),
]}
/>
</Popover>
</>
)}
onAddWidget(item);
}}
items={[
{
name: 'cash-flow-card' as const,
text: t('Cash flow graph'),
},
{
name: 'net-worth-card' as const,
text: t('Net worth graph'),
},
{
name: 'spending-card' as const,
text: t('Spending analysis'),
},
{
name: 'markdown-card' as const,
text: t('Text widget'),
},
{
name: 'custom-report' as const,
text: t('New custom report'),
},
...(customReports.length
? ([Menu.line] satisfies Array<typeof Menu.line>)
: []),
...customReports.map(report => ({
name: `custom-report-${report.id}` as const,
text: report.name,
})),
]}
/>
</Popover>
{isEditing ? (
<Button
@@ -420,71 +403,59 @@ export function Overview() {
>
<Trans>Finish editing dashboard</Trans>
</Button>
) : isDashboardsFeatureEnabled ? (
) : (
<Button
isDisabled={isImporting}
onPress={() => setIsEditing(true)}
>
<Trans>Edit dashboard</Trans>
</Button>
) : (
<Button
variant="primary"
isDisabled={isImporting}
onPress={() => navigate('/reports/custom')}
>
<Trans>Create new custom report</Trans>
</Button>
)}
{isDashboardsFeatureEnabled && (
<>
<MenuButton
ref={extraMenuTriggerRef}
onPress={() => setExtraMenuOpen(true)}
/>
<Popover
triggerRef={extraMenuTriggerRef}
isOpen={extraMenuOpen}
onOpenChange={() => setExtraMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
switch (item) {
case 'reset':
onResetDashboard();
break;
case 'export':
onExport();
break;
case 'import':
onImport();
break;
}
setExtraMenuOpen(false);
}}
items={[
{
name: 'reset',
text: t('Reset to default'),
disabled: isImporting,
},
Menu.line,
{
name: 'import',
text: t('Import'),
disabled: isImporting,
},
{
name: 'export',
text: t('Export'),
disabled: isImporting,
},
]}
/>
</Popover>
</>
)}
<MenuButton
ref={extraMenuTriggerRef}
onPress={() => setExtraMenuOpen(true)}
/>
<Popover
triggerRef={extraMenuTriggerRef}
isOpen={extraMenuOpen}
onOpenChange={() => setExtraMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
switch (item) {
case 'reset':
onResetDashboard();
break;
case 'export':
onExport();
break;
case 'import':
onImport();
break;
}
setExtraMenuOpen(false);
}}
items={[
{
name: 'reset',
text: t('Reset to default'),
disabled: isImporting,
},
Menu.line,
{
name: 'import',
text: t('Import'),
disabled: isImporting,
},
{
name: 'export',
text: t('Export'),
disabled: isImporting,
},
]}
/>
</Popover>
</>
)}
</View>

View File

@@ -6,7 +6,6 @@ import { Bar, BarChart, LabelList, ResponsiveContainer } from 'recharts';
import { integerToCurrency } from 'loot-core/src/shared/util';
import { type CashFlowWidget } from 'loot-core/src/types/models';
import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { theme } from '../../../style';
import { View } from '../../common/View';
import { PrivacyFilter } from '../../PrivacyFilter';
@@ -98,7 +97,6 @@ export function CashFlowCard({
onMetaChange,
onRemove,
}: CashFlowCardProps) {
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const { t } = useTranslation();
const [start, end] = calculateTimeRange(meta?.timeFrame, defaultTimeFrame);
@@ -121,11 +119,7 @@ export function CashFlowCard({
return (
<ReportCard
isEditing={isEditing}
to={
isDashboardsFeatureEnabled
? `/reports/cash-flow/${widgetId}`
: '/reports/cash-flow'
}
to={`/reports/cash-flow/${widgetId}`}
menuItems={[
{
name: 'rename',

View File

@@ -11,7 +11,6 @@ import { type CustomReportEntity } from 'loot-core/types/models/reports';
import { useAccounts } from '../../../hooks/useAccounts';
import { useCategories } from '../../../hooks/useCategories';
import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { usePayees } from '../../../hooks/usePayees';
import { useSyncedPref } from '../../../hooks/useSyncedPref';
import { SvgExclamationSolid } from '../../../icons/v1';
@@ -38,15 +37,9 @@ export function CustomReportListCards({
report,
onRemove,
}: CustomReportListCardsProps) {
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
// It's possible for a dashboard to reference a non-existing
// custom report
if (!report) {
if (!isDashboardsFeatureEnabled) {
return null;
}
return (
<MissingReportCard isEditing={isEditing} onRemove={onRemove}>
{t('This custom report has been deleted.')}

View File

@@ -7,7 +7,6 @@ import {
type NetWorthWidget,
} from 'loot-core/src/types/models';
import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { styles } from '../../../style';
import { Block } from '../../common/Block';
import { View } from '../../common/View';
@@ -40,7 +39,6 @@ export function NetWorthCard({
onMetaChange,
onRemove,
}: NetWorthCardProps) {
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const { t } = useTranslation();
const { isNarrowWidth } = useResponsive();
@@ -67,11 +65,7 @@ export function NetWorthCard({
return (
<ReportCard
isEditing={isEditing}
to={
isDashboardsFeatureEnabled
? `/reports/net-worth/${widgetId}`
: '/reports/net-worth'
}
to={`/reports/net-worth/${widgetId}`}
menuItems={[
{
name: 'rename',

View File

@@ -13,7 +13,6 @@ import { amountToCurrency } from 'loot-core/src/shared/util';
import { type SpendingWidget } from 'loot-core/types/models';
import { type RuleConditionEntity } from 'loot-core/types/models/rule';
import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { useFilters } from '../../../hooks/useFilters';
import { useNavigate } from '../../../hooks/useNavigate';
import { theme, styles } from '../../../style';
@@ -61,7 +60,6 @@ type SpendingInternalProps = {
};
function SpendingInternal({ widget }: SpendingInternalProps) {
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const dispatch = useDispatch();
const { t } = useTranslation();
@@ -237,26 +235,22 @@ function SpendingInternal({ widget }: SpendingInternalProps) {
>
{!isNarrowWidth && (
<SpaceBetween gap={0}>
{isDashboardsFeatureEnabled && (
<>
<Button
variant={isLive ? 'primary' : 'normal'}
onPress={() => setIsLive(state => !state)}
>
{isLive ? t('Live') : t('Static')}
</Button>
<Button
variant={isLive ? 'primary' : 'normal'}
onPress={() => setIsLive(state => !state)}
>
{isLive ? t('Live') : t('Static')}
</Button>
<View
style={{
width: 1,
height: 28,
backgroundColor: theme.pillBorderDark,
marginRight: 10,
marginLeft: 10,
}}
/>
</>
)}
<View
style={{
width: 1,
height: 28,
backgroundColor: theme.pillBorderDark,
marginRight: 10,
marginLeft: 10,
}}
/>
<SpaceBetween gap={5}>
<Text>

View File

@@ -5,7 +5,6 @@ import * as monthUtils from 'loot-core/src/shared/months';
import { amountToCurrency } from 'loot-core/src/shared/util';
import { type SpendingWidget } from 'loot-core/src/types/models';
import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { styles } from '../../../style/styles';
import { theme } from '../../../style/theme';
import { Block } from '../../common/Block';
@@ -35,7 +34,6 @@ export function SpendingCard({
onMetaChange,
onRemove,
}: SpendingCardProps) {
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const { t } = useTranslation();
const [compare, compareTo] = calculateSpendingReportTimeRange(meta ?? {});
@@ -71,11 +69,7 @@ export function SpendingCard({
return (
<ReportCard
isEditing={isEditing}
to={
isDashboardsFeatureEnabled
? `/reports/spending/${widgetId}`
: '/reports/spending'
}
to={`/reports/spending/${widgetId}`}
menuItems={[
{
name: 'rename',

View File

@@ -78,12 +78,6 @@ export function ExperimentalFeatures() {
<FeatureToggle flag="goalTemplatesEnabled">
<Trans>Goal templates</Trans>
</FeatureToggle>
<FeatureToggle
flag="dashboards"
feedbackLink="https://github.com/actualbudget/actual/issues/3282"
>
<Trans>Customizable reports page (dashboards)</Trans>
</FeatureToggle>
<FeatureToggle
flag="actionTemplating"
feedbackLink="https://github.com/actualbudget/actual/issues/3606"

View File

@@ -4,7 +4,6 @@ import { useSyncedPref } from './useSyncedPref';
const DEFAULT_FEATURE_FLAG_STATE: Record<FeatureFlag, boolean> = {
goalTemplatesEnabled: false,
dashboards: false,
actionTemplating: false,
upcomingLengthAdjustment: false,
contextMenus: false,

View File

@@ -1,5 +1,4 @@
export type FeatureFlag =
| 'dashboards'
| 'goalTemplatesEnabled'
| 'actionTemplating'
| 'upcomingLengthAdjustment'

View File

@@ -0,0 +1,6 @@
---
category: Features
authors: [MatissJanis]
---
Dashboards: release as first party feature.