feat(governance): diff highlighter for update market proposals (#4260)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
45f0ba7c0e
commit
6a07127185
1
apps/governance/src/components/json-diff/index.ts
Normal file
1
apps/governance/src/components/json-diff/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './json-diff';
|
30
apps/governance/src/components/json-diff/json-diff.spec.tsx
Normal file
30
apps/governance/src/components/json-diff/json-diff.spec.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { render, screen, cleanup } from '@testing-library/react';
|
||||||
|
import { JsonDiff } from './json-diff';
|
||||||
|
|
||||||
|
describe('JsonDiff', () => {
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
render(<JsonDiff left={{}} right={{}} />);
|
||||||
|
expect(screen.getByTestId('json-diff')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the correct message when both objects are identical', () => {
|
||||||
|
render(<JsonDiff left={{}} right={{}} />);
|
||||||
|
expect(screen.getByText('Data is identical')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show the "identical" message when both objects are not identical', () => {
|
||||||
|
render(
|
||||||
|
<JsonDiff
|
||||||
|
left={{
|
||||||
|
name: 'test',
|
||||||
|
}}
|
||||||
|
right={{
|
||||||
|
name: 'test2',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.queryByText('Data is identical')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
46
apps/governance/src/components/json-diff/json-diff.tsx
Normal file
46
apps/governance/src/components/json-diff/json-diff.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { formatters, create } from 'jsondiffpatch';
|
||||||
|
import 'jsondiffpatch/dist/formatters-styles/html.css';
|
||||||
|
import 'jsondiffpatch/dist/formatters-styles/annotated.css';
|
||||||
|
|
||||||
|
export type JsonValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| JsonValue[]
|
||||||
|
| { [key: string]: JsonValue };
|
||||||
|
|
||||||
|
interface JsonDiffProps {
|
||||||
|
left: JsonValue;
|
||||||
|
right: JsonValue;
|
||||||
|
objectHash?: (obj: unknown) => string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JsonDiff = ({ right, left, objectHash }: JsonDiffProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [html, setHtml] = useState<string | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const delta = create({
|
||||||
|
objectHash,
|
||||||
|
}).diff(left, right);
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
const deltaHtml = formatters.html.format(delta, left);
|
||||||
|
|
||||||
|
formatters.html.hideUnchanged();
|
||||||
|
|
||||||
|
setHtml(deltaHtml);
|
||||||
|
} else {
|
||||||
|
setHtml(undefined);
|
||||||
|
}
|
||||||
|
}, [right, left, objectHash]);
|
||||||
|
|
||||||
|
return html ? (
|
||||||
|
<div data-testid="json-diff" dangerouslySetInnerHTML={{ __html: html }} />
|
||||||
|
) : (
|
||||||
|
<p data-testid="json-diff">{t('dataIsIdentical')}</p>
|
||||||
|
);
|
||||||
|
};
|
@ -853,5 +853,7 @@
|
|||||||
"consensusNodes": "consensus nodes",
|
"consensusNodes": "consensus nodes",
|
||||||
"activeNodes": "active nodes",
|
"activeNodes": "active nodes",
|
||||||
"Estimated time to upgrade": "Estimated time to upgrade",
|
"Estimated time to upgrade": "Estimated time to upgrade",
|
||||||
"Upgraded at": "Upgraded at"
|
"Upgraded at": "Upgraded at",
|
||||||
|
"dataIsIdentical": "Data is identical",
|
||||||
|
"updatesToMarket": "Updates to market"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from './proposal-market-changes';
|
@ -0,0 +1,91 @@
|
|||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
import {
|
||||||
|
ProposalMarketChanges,
|
||||||
|
applyImmutableKeysFromEarlierVersion,
|
||||||
|
} from './proposal-market-changes';
|
||||||
|
import type { JsonValue } from '../../../../components/json-diff';
|
||||||
|
|
||||||
|
describe('applyImmutableKeysFromEarlierVersion', () => {
|
||||||
|
it('returns an empty object if any argument is not an object or null', () => {
|
||||||
|
const earlierVersion: JsonValue = null;
|
||||||
|
const updatedVersion: JsonValue = null;
|
||||||
|
expect(
|
||||||
|
applyImmutableKeysFromEarlierVersion(earlierVersion, updatedVersion)
|
||||||
|
).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overrides updatedVersion with values from earlierVersion for immutable keys', () => {
|
||||||
|
const earlierVersion: JsonValue = {
|
||||||
|
decimalPlaces: 2,
|
||||||
|
positionDecimalPlaces: 3,
|
||||||
|
instrument: {
|
||||||
|
name: 'Instrument1',
|
||||||
|
future: {
|
||||||
|
settlementAsset: 'Asset1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedVersion: JsonValue = {
|
||||||
|
decimalPlaces: 3, // should be overridden by 2
|
||||||
|
positionDecimalPlaces: 4, // should be overridden by 3
|
||||||
|
instrument: {
|
||||||
|
name: 'Instrument2', // should be overridden by 'Instrument1'
|
||||||
|
future: {
|
||||||
|
settlementAsset: 'Asset2', // should be overridden by 'Asset1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected: JsonValue = {
|
||||||
|
decimalPlaces: 2,
|
||||||
|
positionDecimalPlaces: 3,
|
||||||
|
instrument: {
|
||||||
|
name: 'Instrument1',
|
||||||
|
future: {
|
||||||
|
settlementAsset: 'Asset1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
applyImmutableKeysFromEarlierVersion(earlierVersion, updatedVersion)
|
||||||
|
).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProposalMarketChanges', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<ProposalMarketChanges
|
||||||
|
originalProposal={{}}
|
||||||
|
latestEnactedProposal={{}}
|
||||||
|
updatedProposal={{}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JsonDiff is not visible when showChanges is false', () => {
|
||||||
|
const { queryByTestId } = render(
|
||||||
|
<ProposalMarketChanges
|
||||||
|
originalProposal={{}}
|
||||||
|
latestEnactedProposal={{}}
|
||||||
|
updatedProposal={{}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JsonDiff is visible when showChanges is true', async () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<ProposalMarketChanges
|
||||||
|
originalProposal={{}}
|
||||||
|
latestEnactedProposal={{}}
|
||||||
|
updatedProposal={{}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
|
||||||
|
expect(getByTestId('json-diff')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,86 @@
|
|||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import set from 'lodash/set';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { JsonDiff } from '../../../../components/json-diff';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||||
|
import { SubHeading } from '../../../../components/heading';
|
||||||
|
import type { JsonValue } from '../../../../components/json-diff';
|
||||||
|
|
||||||
|
const immutableKeys = [
|
||||||
|
'decimalPlaces',
|
||||||
|
'positionDecimalPlaces',
|
||||||
|
'instrument.name',
|
||||||
|
'instrument.future.settlementAsset',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const applyImmutableKeysFromEarlierVersion = (
|
||||||
|
earlierVersion: JsonValue,
|
||||||
|
updatedVersion: JsonValue
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
typeof earlierVersion !== 'object' ||
|
||||||
|
earlierVersion === null ||
|
||||||
|
typeof updatedVersion !== 'object' ||
|
||||||
|
updatedVersion === null
|
||||||
|
) {
|
||||||
|
// If either version is not an object or is null, return null or throw an error
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedVersionCopy = cloneDeep(updatedVersion);
|
||||||
|
|
||||||
|
// Overwrite the immutable keys in the updatedVersionCopy with the earlier values
|
||||||
|
immutableKeys.forEach((key) => {
|
||||||
|
set(updatedVersionCopy, key, get(earlierVersion, key));
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedVersionCopy;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ProposalMarketChangesProps {
|
||||||
|
originalProposal: JsonValue;
|
||||||
|
latestEnactedProposal: JsonValue | undefined;
|
||||||
|
updatedProposal: JsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProposalMarketChanges = ({
|
||||||
|
originalProposal,
|
||||||
|
latestEnactedProposal,
|
||||||
|
updatedProposal,
|
||||||
|
}: ProposalMarketChangesProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [showChanges, setShowChanges] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section data-testid="proposal-market-changes">
|
||||||
|
<CollapsibleToggle
|
||||||
|
toggleState={showChanges}
|
||||||
|
setToggleState={setShowChanges}
|
||||||
|
dataTestId={'proposal-market-changes-toggle'}
|
||||||
|
>
|
||||||
|
<SubHeading title={t('updatesToMarket')} />
|
||||||
|
</CollapsibleToggle>
|
||||||
|
|
||||||
|
{showChanges && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<JsonDiff
|
||||||
|
left={latestEnactedProposal || originalProposal}
|
||||||
|
right={
|
||||||
|
latestEnactedProposal
|
||||||
|
? applyImmutableKeysFromEarlierVersion(
|
||||||
|
latestEnactedProposal,
|
||||||
|
updatedProposal
|
||||||
|
)
|
||||||
|
: applyImmutableKeysFromEarlierVersion(
|
||||||
|
originalProposal,
|
||||||
|
updatedProposal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
@ -17,6 +17,7 @@ import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
|||||||
import type { AssetQuery } from '@vegaprotocol/assets';
|
import type { AssetQuery } from '@vegaprotocol/assets';
|
||||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||||
import { ProposalState } from '@vegaprotocol/types';
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
|
import { ProposalMarketChanges } from '../proposal-market-changes';
|
||||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||||
|
|
||||||
export enum ProposalType {
|
export enum ProposalType {
|
||||||
@ -34,6 +35,10 @@ export interface ProposalProps {
|
|||||||
assetData?: AssetQuery | null;
|
assetData?: AssetQuery | null;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
restData: any;
|
restData: any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
originalMarketProposalRestData?: any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Proposal = ({
|
export const Proposal = ({
|
||||||
@ -42,6 +47,8 @@ export const Proposal = ({
|
|||||||
restData,
|
restData,
|
||||||
newMarketData,
|
newMarketData,
|
||||||
assetData,
|
assetData,
|
||||||
|
originalMarketProposalRestData,
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal,
|
||||||
}: ProposalProps) => {
|
}: ProposalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -154,6 +161,24 @@ export const Proposal = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<ProposalMarketChanges
|
||||||
|
originalProposal={
|
||||||
|
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
||||||
|
?.changes || {}
|
||||||
|
}
|
||||||
|
latestEnactedProposal={
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal
|
||||||
|
?.terms?.updateMarket?.changes || {}
|
||||||
|
}
|
||||||
|
updatedProposal={
|
||||||
|
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||||
asset && (
|
asset && (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Proposal } from '../components/proposal';
|
import { Proposal } from '../components/proposal';
|
||||||
@ -16,7 +16,12 @@ import {
|
|||||||
} from '@vegaprotocol/network-parameters';
|
} from '@vegaprotocol/network-parameters';
|
||||||
|
|
||||||
export const ProposalContainer = () => {
|
export const ProposalContainer = () => {
|
||||||
|
const [
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal,
|
||||||
|
setMostRecentlyEnactedAssociatedMarketProposal,
|
||||||
|
] = useState(undefined);
|
||||||
const params = useParams<{ proposalId: string }>();
|
const params = useParams<{ proposalId: string }>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
params: networkParams,
|
params: networkParams,
|
||||||
loading: networkParamsLoading,
|
loading: networkParamsLoading,
|
||||||
@ -37,9 +42,11 @@ export const ProposalContainer = () => {
|
|||||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: { data: restData },
|
state: { data: restData, loading: restLoading, error: restError },
|
||||||
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
|
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
|
||||||
|
|
||||||
const { data, loading, error, refetch } = useProposalQuery({
|
const { data, loading, error, refetch } = useProposalQuery({
|
||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
errorPolicy: 'ignore',
|
errorPolicy: 'ignore',
|
||||||
@ -47,6 +54,35 @@ export const ProposalContainer = () => {
|
|||||||
skip: !params.proposalId,
|
skip: !params.proposalId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
state: {
|
||||||
|
data: originalMarketProposalRestData,
|
||||||
|
loading: originalMarketProposalRestLoading,
|
||||||
|
error: originalMarketProposalRestError,
|
||||||
|
},
|
||||||
|
} = useFetch(
|
||||||
|
`${ENV.rest}governance?proposalId=${
|
||||||
|
data?.proposal?.terms.change.__typename === 'UpdateMarket' &&
|
||||||
|
data?.proposal.terms.change.marketId
|
||||||
|
}`,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
state: {
|
||||||
|
data: previouslyEnactedMarketProposalsRestData,
|
||||||
|
loading: previouslyEnactedMarketProposalsRestLoading,
|
||||||
|
error: previouslyEnactedMarketProposalsRestError,
|
||||||
|
},
|
||||||
|
} = useFetch(
|
||||||
|
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: newMarketData,
|
data: newMarketData,
|
||||||
loading: newMarketLoading,
|
loading: newMarketLoading,
|
||||||
@ -79,6 +115,39 @@ export const ProposalContainer = () => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
previouslyEnactedMarketProposalsRestData &&
|
||||||
|
data?.proposal?.terms.change.__typename === 'UpdateMarket'
|
||||||
|
) {
|
||||||
|
const change = data?.proposal?.terms?.change as { marketId: string };
|
||||||
|
|
||||||
|
const filteredProposals =
|
||||||
|
// @ts-ignore rest data is not typed
|
||||||
|
previouslyEnactedMarketProposalsRestData.connection.edges.filter(
|
||||||
|
// @ts-ignore rest data is not typed
|
||||||
|
({ node }) =>
|
||||||
|
node?.proposal?.terms?.updateMarket?.marketId === change.marketId
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortedProposals = filteredProposals.sort(
|
||||||
|
// @ts-ignore rest data is not typed
|
||||||
|
(a, b) =>
|
||||||
|
new Date(a?.node?.terms?.enactmentTimestamp).getTime() -
|
||||||
|
new Date(b?.node?.terms?.enactmentTimestamp).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
setMostRecentlyEnactedAssociatedMarketProposal(
|
||||||
|
sortedProposals[sortedProposals.length - 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
previouslyEnactedMarketProposalsRestData,
|
||||||
|
params.proposalId,
|
||||||
|
data?.proposal?.terms.change.__typename,
|
||||||
|
data?.proposal?.terms.change,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(refetch, 2000);
|
const interval = setInterval(refetch, 2000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
@ -87,14 +156,39 @@ export const ProposalContainer = () => {
|
|||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
loading={
|
loading={
|
||||||
loading || newMarketLoading || assetLoading || networkParamsLoading
|
loading ||
|
||||||
|
newMarketLoading ||
|
||||||
|
assetLoading ||
|
||||||
|
networkParamsLoading ||
|
||||||
|
(restLoading ? (restLoading as boolean) : false) ||
|
||||||
|
(originalMarketProposalRestLoading
|
||||||
|
? (originalMarketProposalRestLoading as boolean)
|
||||||
|
: false) ||
|
||||||
|
(previouslyEnactedMarketProposalsRestLoading
|
||||||
|
? (previouslyEnactedMarketProposalsRestLoading as boolean)
|
||||||
|
: false)
|
||||||
|
}
|
||||||
|
error={
|
||||||
|
error ||
|
||||||
|
newMarketError ||
|
||||||
|
assetError ||
|
||||||
|
restError ||
|
||||||
|
originalMarketProposalRestError ||
|
||||||
|
previouslyEnactedMarketProposalsRestError ||
|
||||||
|
networkParamsError
|
||||||
}
|
}
|
||||||
error={error || newMarketError || assetError || networkParamsError}
|
|
||||||
data={{
|
data={{
|
||||||
...data,
|
...data,
|
||||||
...networkParams,
|
...networkParams,
|
||||||
...(newMarketData ? { newMarketData } : {}),
|
...(newMarketData ? { newMarketData } : {}),
|
||||||
...(assetData ? { assetData } : {}),
|
...(assetData ? { assetData } : {}),
|
||||||
|
...(restData ? { restData } : {}),
|
||||||
|
...(originalMarketProposalRestData
|
||||||
|
? { originalMarketProposalRestData }
|
||||||
|
: {}),
|
||||||
|
...(previouslyEnactedMarketProposalsRestData
|
||||||
|
? { previouslyEnactedMarketProposalsRestData }
|
||||||
|
: {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data?.proposal ? (
|
{data?.proposal ? (
|
||||||
@ -104,6 +198,10 @@ export const ProposalContainer = () => {
|
|||||||
restData={restData}
|
restData={restData}
|
||||||
newMarketData={newMarketData}
|
newMarketData={newMarketData}
|
||||||
assetData={assetData}
|
assetData={assetData}
|
||||||
|
originalMarketProposalRestData={originalMarketProposalRestData}
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal={
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ProposalNotFound />
|
<ProposalNotFound />
|
||||||
|
@ -97,3 +97,33 @@
|
|||||||
.react-markdown-container ul li {
|
.react-markdown-container ul li {
|
||||||
list-style: circle;
|
list-style: circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-delta,
|
||||||
|
.jsondiffpatch-delta pre {
|
||||||
|
font-family: 'Roboto Mono', monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-delta pre {
|
||||||
|
padding: 0 0.25em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-added .jsondiffpatch-property-name,
|
||||||
|
.jsondiffpatch-added .jsondiffpatch-value pre,
|
||||||
|
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
|
||||||
|
.jsondiffpatch-textdiff-added {
|
||||||
|
background: theme(colors.vega.green[650]) !important;
|
||||||
|
color: theme(colors.white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-deleted .jsondiffpatch-property-name,
|
||||||
|
.jsondiffpatch-deleted pre,
|
||||||
|
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
|
||||||
|
.jsondiffpatch-textdiff-deleted {
|
||||||
|
background: theme(colors.vega.pink[650]) !important;
|
||||||
|
color: theme(colors.white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-moved .jsondiffpatch-moved-destination {
|
||||||
|
background: theme(colors.vega.yellow[350]) !important;
|
||||||
|
color: theme(colors.vega.dark[200]) !important;
|
||||||
|
}
|
||||||
|
@ -21,7 +21,8 @@ type Action<T> =
|
|||||||
export const useFetch = <T>(
|
export const useFetch = <T>(
|
||||||
url: string,
|
url: string,
|
||||||
options?: RequestInit,
|
options?: RequestInit,
|
||||||
initialFetch = true
|
initialFetch = true,
|
||||||
|
skip?: boolean
|
||||||
): {
|
): {
|
||||||
state: State<T>;
|
state: State<T>;
|
||||||
refetch: (
|
refetch: (
|
||||||
@ -105,10 +106,10 @@ export const useFetch = <T>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cancelRequest.current = false;
|
cancelRequest.current = false;
|
||||||
if (initialFetch) {
|
if (initialFetch && !skip) {
|
||||||
fetchCallback();
|
fetchCallback();
|
||||||
}
|
}
|
||||||
}, [fetchCallback, initialFetch, url]);
|
}, [fetchCallback, initialFetch, skip, url]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Use the cleanup function for avoiding a possibly...
|
// Use the cleanup function for avoiding a possibly...
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"immer": "^9.0.12",
|
"immer": "^9.0.12",
|
||||||
"iso8601-duration": "^2.1.1",
|
"iso8601-duration": "^2.1.1",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
|
"jsondiffpatch": "^0.4.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"next": "13.3.0",
|
"next": "13.3.0",
|
||||||
"pennant": "1.10.0",
|
"pennant": "1.10.0",
|
||||||
|
15
yarn.lock
15
yarn.lock
@ -11274,7 +11274,7 @@ chalk@^1.0.0, chalk@^1.1.3:
|
|||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
supports-color "^2.0.0"
|
supports-color "^2.0.0"
|
||||||
|
|
||||||
chalk@^2.0.0, chalk@^2.4.1:
|
chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||||
@ -12967,6 +12967,11 @@ didyoumean@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||||
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
||||||
|
|
||||||
|
diff-match-patch@^1.0.0:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
|
||||||
|
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
|
||||||
|
|
||||||
diff-sequences@^27.5.1:
|
diff-sequences@^27.5.1:
|
||||||
version "27.5.1"
|
version "27.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
||||||
@ -17431,6 +17436,14 @@ jsonc-parser@3.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
|
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
|
||||||
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
|
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
|
||||||
|
|
||||||
|
jsondiffpatch@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz#9fb085036767f03534ebd46dcd841df6070c5773"
|
||||||
|
integrity sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==
|
||||||
|
dependencies:
|
||||||
|
chalk "^2.3.0"
|
||||||
|
diff-match-patch "^1.0.0"
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
jsonfile@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||||
|
Loading…
Reference in New Issue
Block a user