Skip to content

Commit

Permalink
Get CSV grain integration production-ready (#3320)
Browse files Browse the repository at this point in the history
  • Loading branch information
blueridger committed Feb 11, 2022
1 parent 86189db commit b5298c0
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 32 deletions.
23 changes: 22 additions & 1 deletion packages/grainIntegration-csv/distributionToCsv.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// @flow

const HARDCODED_DECIMAL_PRECISION = 18;
const ZEROS = Array.from(Array(HARDCODED_DECIMAL_PRECISION + 1))
.map(() => "0")
.join("");

/*:: type PayoutDistributions = $ReadOnlyArray<[string, string]>;*/
/*:: type IntegrationConfig = {
currency: {tokenAddress?: string},
Expand All @@ -14,7 +19,23 @@ module.exports = function payoutDistributionToCsv(
prefix = "erc20," + (config.currency.tokenAddress || "") + ",";
let csvString = "";
for (const [payoutAddress, amount] of payoutDistributions) {
csvString += prefix + `${payoutAddress},${amount}\n`;
const amountWithZerosPrefix = ZEROS + amount;
const beforeDecimal = amountWithZerosPrefix.slice(
0,
amountWithZerosPrefix.length - HARDCODED_DECIMAL_PRECISION
);
const afterDecimal = amountWithZerosPrefix.slice(
amountWithZerosPrefix.length - HARDCODED_DECIMAL_PRECISION
);
let formattedAmount = (beforeDecimal + "." + afterDecimal).replace(
/^0+|0+$/g,
""
);
if (formattedAmount.startsWith("."))
formattedAmount = "0" + formattedAmount;
if (formattedAmount.endsWith(".")) formattedAmount = formattedAmount + "0";

csvString += prefix + `${payoutAddress},${formattedAmount}\n`;
}
return csvString;
};
15 changes: 9 additions & 6 deletions packages/grainIntegration-csv/payoutDistributionToCsv.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,39 @@ const payoutDistributionToCsv = require("./distributionToCsv");
describe("payoutDistributionToCsv", () => {
it("serializes entries into a csv when gnosis config is absent", () => {
const mockDistributions = [
["abc", "123"],
["abc", "99123456789012345678"],
["def", "456"],
["lol", "1000000000000000000"],
];
const result = payoutDistributionToCsv(mockDistributions, {
currency: { tokenAddress: "0x1234" },
integration: undefined,
});
expect(result).toBe(`abc,123\ndef,456\n`);
expect(result).toBe(`abc,99.123456789012345678\ndef,0.000000000000000456\nlol,1.0\n`);
});
it("serializes entries into a csv with gnosis prefix", () => {
const mockDistributions = [
["abc", "123"],
["abc", "99123456789012345678"],
["def", "456"],
["lol", "1000000000000000000"],
];
const result = payoutDistributionToCsv(mockDistributions, {
currency: { tokenAddress: "0x1234" },
integration: { gnosis: true },
});
expect(result).toBe(`erc20,0x1234,abc,123\nerc20,0x1234,def,456\n`);
expect(result).toBe(`erc20,0x1234,abc,99.123456789012345678\nerc20,0x1234,def,0.000000000000000456\nerc20,0x1234,lol,1.0\n`);
});
it("serializes entries into a csv with gnosis prefix when the tokenAddress is absent", () => {
const mockDistributions = [
["abc", "123"],
["abc", "99123456789012345678"],
["def", "456"],
["lol", "1000000000000000000"],
];
const result = payoutDistributionToCsv(mockDistributions, {
currency: {},
integration: { gnosis: true },
});
expect(result).toBe(`erc20,,abc,123\nerc20,,def,456\n`);
expect(result).toBe(`erc20,,abc,99.123456789012345678\nerc20,,def,0.000000000000000456\nerc20,,lol,1.0\n`);
});
it("returns an empty string when receiving an empty array", () => {
const result = payoutDistributionToCsv([], {
Expand Down
2 changes: 1 addition & 1 deletion packages/sourcecred/src/api/instance/localInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class LocalInstance extends ReadInstance implements Instance {
const configName = config.grainConfig.integration?.name;
if (!configName) return;
const configUpdate = result.configUpdate;
if (Object.keys(configUpdate).length > 0) {
if (configUpdate && Object.keys(configUpdate).length > 0) {
const grainConfigPath = pathJoin(...GRAIN_PATH);
const currentConfig = await loadJson<any>(
this._storage,
Expand Down
2 changes: 1 addition & 1 deletion packages/sourcecred/src/api/main/grain.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function grain(input: GrainInput): Promise<GrainOutput> {

const distributions = applyDistributions(
input.grainConfig,
input.credGrainView,
input.credGrainView.withNewLedger(configuredLedger),
configuredLedger,
+Date.now(),
input.allowMultipleDistributionsPerInterval || false
Expand Down
8 changes: 5 additions & 3 deletions packages/sourcecred/src/cli/grain.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ const grainCommand: Command = async (args, std) => {
distributions
);

for (const result of results) {
await instance.writeGrainIntegrationOutput(result);
await instance.updateGrainIntegrationConfig(result, grainInput);
if (!simulation) {
for (const result of results) {
await instance.writeGrainIntegrationOutput(result);
await instance.updateGrainIntegrationConfig(result, grainInput);
}
}

let totalDistributed = G.ZERO;
Expand Down
46 changes: 34 additions & 12 deletions packages/sourcecred/src/core/credGrainView.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,29 @@ export class CredGrainView {
return new CredGrainView(json.participants, json.intervals);
}

withNewLedger(ledger: Ledger): CredGrainView {
const participants = ledger.accounts().map((account) => {
const grainEarnedPerInterval = CredGrainView._calculateGrainEarnedPerInterval(
account,
this._intervals
);
const participant = this._participants.find(
(p) => p.identity.id === account.identity.id
);
if (!participant)
throw new Error(
`CredGrainView.withNewLedger: new ledger has identity ID [${account.identity.id}] that is not in already in the CredGrainView.`
);
return {
...participant,
active: account.active,
grainEarned: account.paid,
grainEarnedPerInterval,
};
});
return new CredGrainView(participants, this._intervals);
}

/**
Creates a CredGrainView using the output of the CredRank API.
*/
Expand Down Expand Up @@ -360,7 +383,7 @@ Creates a CredGrainView using the output of the CredEquate API.
Math.max(...ledgerCredTimestamps, Date.now())
);
const participantsMap = new Map();
ledger.accounts().forEach((account) => {
const participantPrototypes = ledger.accounts().map((account) => {
const grainEarnedPerInterval = this._calculateGrainEarnedPerInterval(
account,
intervals
Expand All @@ -375,6 +398,7 @@ Creates a CredGrainView using the output of the CredEquate API.
for (const alias of account.identity.aliases) {
participantsMap.set(alias.address, participant);
}
return participant;
});

for (const scoredContribution of scoredContributions) {
Expand All @@ -389,17 +413,15 @@ Creates a CredGrainView using the output of the CredEquate API.
}
}

const participants = Array.from(new Set(participantsMap.values())).map(
(p) => {
return {
...p,
cred: p.credPerInterval.reduce((a, b, index) => {
if (!b) p.credPerInterval[index] = 0;
return a + (b ?? 0);
}, 0),
};
}
);
const participants = participantPrototypes.map((p) => {
return {
...p,
cred: p.credPerInterval.reduce((a, b, index) => {
if (!b) p.credPerInterval[index] = 0;
return a + (b ?? 0);
}, 0),
};
});

return new CredGrainView(participants, intervals);
}
Expand Down
38 changes: 30 additions & 8 deletions packages/sourcecred/src/ui/components/AccountOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,26 @@ export const AccountOverview = ({
[]
);

const sortingOptions = [ACTIVE_SORT, BALANCE_SORT, EARNED_SORT];
const sortingOptions = useMemo(
() =>
ledger.accounting().enabled
? [ACTIVE_SORT, BALANCE_SORT, EARNED_SORT]
: [ACTIVE_SORT, EARNED_SORT],
[ledger]
);
const initialSort = useMemo(
() => (ledger.accounting().enabled ? BALANCE_SORT : EARNED_SORT),
[ledger]
);

const tsAccounts = useTableState(
{data: accounts},
{
initialRowsPerPage: PAGINATION_OPTIONS[0],
initialSort: {
sortName: BALANCE_SORT.name,
sortName: initialSort.name,
sortOrder: SortOrders.DESC,
sortFn: BALANCE_SORT.fn,
sortFn: initialSort.fn,
},
}
);
Expand Down Expand Up @@ -163,7 +173,12 @@ export const AccountOverview = ({
</TableHead>
<TableBody>
{tsAccounts.currentPage.map((a) =>
AccountRow(a, currencySuffix, decimalsToDisplay)
AccountRow(
a,
currencySuffix,
decimalsToDisplay,
ledger.accounting().enabled
)
)}
</TableBody>

Expand Down Expand Up @@ -192,15 +207,22 @@ export const AccountOverview = ({
);
};

const AccountRow = (account: Account, suffix: string, decimals: number) => (
const AccountRow = (
account: Account,
suffix: string,
decimals: number,
accountingEnabled: boolean
) => (
<TableRow key={account.identity.id}>
<TableCell component="th" scope="row">
<IdentityDetails id={account.identity.id} name={account.identity.name} />
</TableCell>
<TableCell align="right">{account.active ? "✅" : "🛑"}</TableCell>
<TableCell align="right">
{G.format(account.balance, decimals, suffix)}
</TableCell>
{accountingEnabled && (
<TableCell align="right">
{G.format(account.balance, decimals, suffix)}
</TableCell>
)}
<TableCell align="right">
{G.format(account.paid, decimals, suffix)}
</TableCell>
Expand Down

0 comments on commit b5298c0

Please sign in to comment.