feat: accounting (#75)

* feat:  accounting

* chore: 🔧 accounting params

* chore: 🔧 remove log

* chore: 🔧 remove null year

* chore: 🔧 disabled payment report

* chore: 🔧 rebalance filters
This commit is contained in:
Anthony Potdevin 2020-06-27 11:44:43 +02:00 committed by GitHub
parent 4b9a568e63
commit 9a860ee141
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 448 additions and 9 deletions

View file

@ -2,12 +2,14 @@ import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { Bakery } from 'src/views/tools/bakery/Bakery';
import { Accounting } from 'src/views/tools/accounting/Accounting';
import { BackupsView } from '../src/views/tools/backups/Backups';
import { MessagesView } from '../src/views/tools/messages/Messages';
import { WalletVersion } from '../src/views/tools/WalletVersion';
const ToolsView = () => (
<>
<Accounting />
<BackupsView />
<MessagesView />
<Bakery />

View file

@ -1,10 +1,13 @@
import { ContextType } from 'server/types/apiTypes';
import { getLnd } from 'server/helpers/helpers';
import { rebalance } from 'balanceofsatoshis/swaps';
import { to } from 'server/helpers/async';
import { logger } from 'server/helpers/logger';
import { AuthType } from 'src/context/AccountContext';
import { rebalance } from 'balanceofsatoshis/swaps';
import { getAccountingReport } from 'balanceofsatoshis/balances';
import request from '@alexbosworth/request';
type RebalanceType = {
auth: AuthType;
avoid?: String[];
@ -19,21 +22,79 @@ type RebalanceType = {
target?: Number;
};
type AccountingType = {
auth: AuthType;
category?: String;
currency?: String;
fiat?: String;
month?: String;
year?: String;
};
export const bosResolvers = {
Query: {
getAccountingReport: async (
_: undefined,
params: AccountingType,
context: ContextType
) => {
const { auth, ...settings } = params;
const lnd = getLnd(auth, context);
const response = await to(
getAccountingReport({
lnd,
logger,
request,
is_csv: true,
...settings,
})
);
return response;
},
},
Mutation: {
bosRebalance: async (
_: undefined,
params: RebalanceType,
context: ContextType
) => {
const { auth, ...extraparams } = params;
const {
auth,
avoid,
in_through,
is_avoiding_high_inbound,
max_fee,
max_fee_rate,
max_rebalance,
node,
out_channels,
out_through,
target,
} = params;
const lnd = getLnd(auth, context);
const filteredParams = {
...(avoid.length > 0 && { avoid }),
...(in_through && { in_through }),
...(is_avoiding_high_inbound && { is_avoiding_high_inbound }),
...(max_fee > 0 && { max_fee }),
...(max_fee_rate > 0 && { max_fee_rate }),
...(max_rebalance > 0 && { max_rebalance }),
...(node && { node }),
...(out_channels.length > 0 && { out_channels }),
...(out_through && { out_through }),
...(target && { target }),
};
logger.info('Rebalance Params: %o', filteredParams);
const response = await to(
rebalance({
lnd,
logger,
...extraparams,
...filteredParams,
})
);

View file

@ -36,6 +36,14 @@ export const generalTypes = gql`
export const queryTypes = gql`
type Query {
getAccountingReport(
auth: authType!
category: String
currency: String
fiat: String
month: String
year: String
): String!
getVolumeHealth(auth: authType!): channelsHealth
getTimeHealth(auth: authType!): channelsTimeHealth
getFeeHealth(auth: authType!): channelsFeeHealth

View file

@ -0,0 +1,3 @@
export const getAccountingReport = jest
.fn()
.mockReturnValue(Promise.resolve({}));

View file

@ -21,6 +21,7 @@ const StyledSingleButton = styled.button<StyledSingleProps>`
background-color: transparent;
color: ${multiSelectColor};
flex-grow: 1;
transition: background-color 0.5s ease;
${({ selected, buttonColor }) =>
selected

View file

@ -0,0 +1,92 @@
import gql from 'graphql-tag';
import * as ApolloReactCommon from '@apollo/react-common';
import * as ApolloReactHooks from '@apollo/react-hooks';
import * as Types from '../../types';
export type GetAccountingReportQueryVariables = Types.Exact<{
auth: Types.AuthType;
category?: Types.Maybe<Types.Scalars['String']>;
currency?: Types.Maybe<Types.Scalars['String']>;
fiat?: Types.Maybe<Types.Scalars['String']>;
month?: Types.Maybe<Types.Scalars['String']>;
year?: Types.Maybe<Types.Scalars['String']>;
}>;
export type GetAccountingReportQuery = { __typename?: 'Query' } & Pick<
Types.Query,
'getAccountingReport'
>;
export const GetAccountingReportDocument = gql`
query GetAccountingReport(
$auth: authType!
$category: String
$currency: String
$fiat: String
$month: String
$year: String
) {
getAccountingReport(
auth: $auth
category: $category
currency: $currency
fiat: $fiat
month: $month
year: $year
)
}
`;
/**
* __useGetAccountingReportQuery__
*
* To run a query within a React component, call `useGetAccountingReportQuery` and pass it any options that fit your needs.
* When your component renders, `useGetAccountingReportQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetAccountingReportQuery({
* variables: {
* auth: // value for 'auth'
* category: // value for 'category'
* currency: // value for 'currency'
* fiat: // value for 'fiat'
* month: // value for 'month'
* year: // value for 'year'
* },
* });
*/
export function useGetAccountingReportQuery(
baseOptions?: ApolloReactHooks.QueryHookOptions<
GetAccountingReportQuery,
GetAccountingReportQueryVariables
>
) {
return ApolloReactHooks.useQuery<
GetAccountingReportQuery,
GetAccountingReportQueryVariables
>(GetAccountingReportDocument, baseOptions);
}
export function useGetAccountingReportLazyQuery(
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<
GetAccountingReportQuery,
GetAccountingReportQueryVariables
>
) {
return ApolloReactHooks.useLazyQuery<
GetAccountingReportQuery,
GetAccountingReportQueryVariables
>(GetAccountingReportDocument, baseOptions);
}
export type GetAccountingReportQueryHookResult = ReturnType<
typeof useGetAccountingReportQuery
>;
export type GetAccountingReportLazyQueryHookResult = ReturnType<
typeof useGetAccountingReportLazyQuery
>;
export type GetAccountingReportQueryResult = ApolloReactCommon.QueryResult<
GetAccountingReportQuery,
GetAccountingReportQueryVariables
>;

View file

@ -0,0 +1,21 @@
import gql from 'graphql-tag';
export const GET_ACCOUNTING_REPORT = gql`
query GetAccountingReport(
$auth: authType!
$category: String
$currency: String
$fiat: String
$month: String
$year: String
) {
getAccountingReport(
auth: $auth
category: $category
currency: $currency
fiat: $fiat
month: $month
year: $year
)
}
`;

View file

@ -45,6 +45,7 @@ export type PermissionsType = {
export type Query = {
__typename?: 'Query';
getAccountingReport: Scalars['String'];
getVolumeHealth?: Maybe<ChannelsHealth>;
getTimeHealth?: Maybe<ChannelsTimeHealth>;
getFeeHealth?: Maybe<ChannelsFeeHealth>;
@ -90,6 +91,15 @@ export type Query = {
getLatestVersion?: Maybe<Scalars['String']>;
};
export type QueryGetAccountingReportArgs = {
auth: AuthType;
category?: Maybe<Scalars['String']>;
currency?: Maybe<Scalars['String']>;
fiat?: Maybe<Scalars['String']>;
month?: Maybe<Scalars['String']>;
year?: Maybe<Scalars['String']>;
};
export type QueryGetVolumeHealthArgs = {
auth: AuthType;
};

View file

@ -93,12 +93,16 @@ export const getPercent = (
return Math.round(percent);
};
export const saveToPc = (jsonData: string, filename: string) => {
export const saveToPc = (
jsonData: string,
filename: string,
isCsv?: boolean
) => {
const fileData = jsonData;
const blob = new Blob([fileData], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = `${filename}.txt`;
link.download = isCsv ? `${filename}.csv` : `${filename}.txt`;
link.href = url;
link.click();
};

View file

@ -231,7 +231,7 @@ export const AdvancedBalance = () => {
{hasAvoid ? <Minus size={18} /> : <Plus size={18} />}
</ColorButton>
</SettingLine>
<SettingLine title={'In Through Channel'}>
<SettingLine title={'Decrease Inbound Of'}>
{hasInChannel ? (
<RebalanceTag>{state.in_through.alias}</RebalanceTag>
) : null}
@ -247,7 +247,7 @@ export const AdvancedBalance = () => {
</ColorButton>
</SettingLine>
{!hasOutChannels && (
<SettingLine title={'Out Through Channel'}>
<SettingLine title={'Increase Inbound Of'}>
{hasOutChannel ? (
<RebalanceTag>{state.out_through.alias}</RebalanceTag>
) : null}
@ -427,7 +427,14 @@ export const AdvancedBalance = () => {
</BetaNotification>
<InputWithDeco title={'Type'} noInput={true}>
<MultiButton>
{renderButton(() => isDetailedSet(false), 'Auto', !isDetailed)}
{renderButton(
() => {
dispatch({ type: 'clearFilters' });
isDetailedSet(false);
},
'Auto',
!isDetailed
)}
{renderButton(() => isDetailedSet(true), 'Detailed', isDetailed)}
</MultiButton>
</InputWithDeco>

View file

@ -1,4 +1,5 @@
import styled from 'styled-components';
import { ResponsiveLine } from 'src/components/generic/Styled';
export const NoWrap = styled.div`
margin-right: 16px;
@ -22,3 +23,7 @@ export const Column = styled.div`
justify-content: center;
align-items: center;
`;
export const ToolsResponsiveLine = styled(ResponsiveLine)`
margin-bottom: 8px;
`;

View file

@ -7,6 +7,7 @@ import {
Card,
Sub4Title,
Separation,
DarkSubTitle,
} from '../../components/generic/Styled';
import { useStatusState } from '../../context/StatusContext';
import { LoadingCard } from '../../components/loading/LoadingCard';
@ -30,7 +31,10 @@ export const WalletVersion = () => {
if (minorVersion < 10) {
return (
<Card>
Update to LND version 0.10.0 or higher to see your wallet build info.
<DarkSubTitle>
Update to LND version 0.10.0 or higher to see your wallet build
info.
</DarkSubTitle>
</Card>
);
}

View file

@ -0,0 +1,221 @@
import * as React from 'react';
import {
CardWithTitle,
SubTitle,
Card,
SingleLine,
DarkSubTitle,
Separation,
} from 'src/components/generic/Styled';
import { useGetAccountingReportLazyQuery } from 'src/graphql/queries/__generated__/getAccountingReport.generated';
import { useAccountState } from 'src/context/AccountContext';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import {
MultiButton,
SingleButton,
} from 'src/components/buttons/multiButton/MultiButton';
import { X } from 'react-feather';
import { saveToPc } from 'src/utils/helpers';
import { ToolsResponsiveLine } from '../Tools.styled';
type ReportType =
| 'chain-fees'
| 'chain-receives'
| 'chain-sends'
| 'forwards'
| 'invoices'
| 'payments';
// type FiatType = 'eur' | 'usd';
type YearType = 2017 | 2018 | 2019 | 2020;
type MonthType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | null;
type StateType = {
type: ReportType;
// fiat?: FiatType;
year?: YearType;
month?: MonthType;
};
export type ActionType =
| {
type: 'type';
report: ReportType;
}
// | {
// type: 'fiat';
// fiat: FiatType;
// }
| {
type: 'year';
year: YearType;
}
| {
type: 'month';
month: MonthType;
};
const initialState: StateType = {
type: 'invoices',
// fiat: 'eur',
year: 2020,
month: null,
};
const reducer = (state: StateType, action: ActionType): StateType => {
switch (action.type) {
case 'type':
return { ...state, type: action.report };
// case 'fiat':
// return { ...state, fiat: action.fiat };
case 'year':
return { ...state, year: action.year };
case 'month':
return { ...state, month: action.month };
default:
return state;
}
};
export const Accounting = () => {
const { auth } = useAccountState();
const [showDetails, setShowDetails] = React.useState(false);
const [state, dispatch] = React.useReducer(reducer, initialState);
const [getReport, { data, loading }] = useGetAccountingReportLazyQuery();
React.useEffect(() => {
if (!loading && data && data.getAccountingReport) {
saveToPc(
data.getAccountingReport,
`accounting-${state.type}-${state.year || ''}-${state.month || ''}`,
true
);
}
}, [data, loading]);
const reportButton = (report: ReportType, title: string) => (
<SingleButton
selected={state.type === report}
onClick={() => !loading && dispatch({ type: 'type', report })}
>
{title}
</SingleButton>
);
// const fiatButton = (fiat: FiatType, title: string) => (
// <SingleButton
// selected={state.fiat === fiat}
// onClick={() => !loading && dispatch({ type: 'fiat', fiat })}
// >
// {title}
// </SingleButton>
// );
const yearButton = (year: YearType) => (
<SingleButton
selected={state.year === year}
onClick={() => !loading && dispatch({ type: 'year', year })}
>
{year}
</SingleButton>
);
const monthButton = (month: MonthType) => (
<SingleButton
selected={state.month === month}
onClick={() => !loading && dispatch({ type: 'month', month })}
>
{month ? month : 'All'}
</SingleButton>
);
const renderDetails = () => (
<>
<Separation />
<ToolsResponsiveLine>
<DarkSubTitle>Type</DarkSubTitle>
<MultiButton>
{reportButton('chain-fees', 'Chain Fees')}
{reportButton('chain-receives', 'Chain Received')}
{reportButton('chain-sends', 'Chain Sent')}
{reportButton('forwards', 'Forwards')}
{/* {reportButton('payments', 'Payments')} */}
{reportButton('invoices', 'Invoices')}
</MultiButton>
</ToolsResponsiveLine>
{/* <ToolsResponsiveLine>
<DarkSubTitle>Fiat</DarkSubTitle>
<MultiButton>
{fiatButton('eur', 'Euro')}
{fiatButton('usd', 'US Dollar')}
</MultiButton>
</ToolsResponsiveLine> */}
<ToolsResponsiveLine>
<DarkSubTitle>Year</DarkSubTitle>
<MultiButton>
{yearButton(2017)}
{yearButton(2018)}
{yearButton(2019)}
{yearButton(2020)}
</MultiButton>
</ToolsResponsiveLine>
<ToolsResponsiveLine>
<DarkSubTitle>Month</DarkSubTitle>
<MultiButton>
{monthButton(null)}
{monthButton(1)}
{monthButton(2)}
{monthButton(3)}
{monthButton(4)}
{monthButton(5)}
{monthButton(6)}
{monthButton(7)}
{monthButton(8)}
{monthButton(9)}
{monthButton(10)}
{monthButton(11)}
{monthButton(12)}
</MultiButton>
</ToolsResponsiveLine>
<ColorButton
loading={loading}
disabled={loading}
onClick={() =>
getReport({
variables: {
auth,
// fiat: state.fiat,
category: state.type,
year: state.year.toString(),
...(state.month && { month: state.month.toString() }),
},
})
}
fullWidth={true}
withMargin={'16px 0 0'}
>
Generate
</ColorButton>
</>
);
return (
<CardWithTitle>
<SubTitle>Accounting</SubTitle>
<Card>
<SingleLine>
<DarkSubTitle>Report</DarkSubTitle>
<ColorButton
arrow={!showDetails}
onClick={() =>
showDetails ? setShowDetails(false) : setShowDetails(true)
}
>
{showDetails ? <X size={18} /> : 'Create'}
</ColorButton>
</SingleLine>
{showDetails && renderDetails()}
</Card>
</CardWithTitle>
);
};