Explorar el Código

Merge pull request #2707 from DzhideX/add-operations

Add new operations groups to Pioneer
shamil-gadelshin hace 3 años
padre
commit
26d5b44808

+ 1 - 1
pioneer/packages/apps-routing/src/joy-roles.ts

@@ -7,7 +7,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
     Component: Roles,
     display: {
       needsApi: [
-        'query.contentDirectoryWorkingGroup.mint',
+        'query.contentWorkingGroup.mint',
         'query.storageWorkingGroup.mint'
       ]
     },

+ 6 - 6
pioneer/packages/joy-proposals/src/Proposal/Body.tsx

@@ -16,7 +16,7 @@ import { formatBalance } from '@polkadot/util';
 import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent';
 import ReactMarkdown from 'react-markdown';
 import { StakingPolicy } from '@joystream/types/hiring';
-import { WorkingGroup } from '@joystream/types/common';
+import { WorkingGroup, WorkingGroupKey } from '@joystream/types/common';
 import { ApplicationsDetailsByOpening } from '@polkadot/joy-utils/react/components/working-groups/ApplicationDetails';
 import { LeadInfoFromId } from '@polkadot/joy-utils/react/components/working-groups/LeadInfo';
 import { formatReward } from '@polkadot/joy-utils/functions/format';
@@ -269,7 +269,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
         : <ApplicationsDetailsByOpening
           openingId={openingId.toNumber()}
           acceptedIds={[succesfulApplicationId.toNumber()]}
-          group={workingGroup.type}/>,
+          group={workingGroup.type as WorkingGroupKey}/>,
       true
     )
   ],
@@ -280,7 +280,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
       'Lead',
       historical
         ? `#${(leadId as WorkerId).toNumber()}`
-        : <LeadInfoFromId group={(group as WorkingGroup).type} leadId={(leadId as WorkerId).toNumber()}/>,
+        : <LeadInfoFromId group={(group as WorkingGroup).type as WorkingGroupKey} leadId={(leadId as WorkerId).toNumber()}/>,
       true
     )
   ],
@@ -291,7 +291,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
       'Lead',
       historical
         ? `#${(leadId as WorkerId).toNumber()}`
-        : <LeadInfoFromId group={(group as WorkingGroup).type} leadId={(leadId as WorkerId).toNumber()}/>,
+        : <LeadInfoFromId group={(group as WorkingGroup).type as WorkingGroupKey} leadId={(leadId as WorkerId).toNumber()}/>,
       true
     )
   ],
@@ -302,7 +302,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
       'Lead',
       historical
         ? `#${(leadId as WorkerId).toNumber()}`
-        : <LeadInfoFromId group={(group as WorkingGroup).type} leadId={(leadId as WorkerId).toNumber()}/>,
+        : <LeadInfoFromId group={(group as WorkingGroup).type as WorkingGroupKey} leadId={(leadId as WorkerId).toNumber()}/>,
       true
     )
   ],
@@ -321,7 +321,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
         'Lead',
         historical
           ? `#${leadId.toNumber()}`
-          : <LeadInfoFromId group={workingGroup.type} leadId={leadId.toNumber()}/>,
+          : <LeadInfoFromId group={workingGroup.type as WorkingGroupKey} leadId={leadId.toNumber()}/>,
         true
       )
     ];

+ 11 - 1
pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx

@@ -45,7 +45,17 @@ export type FormInnerProps = ProposalFormInnerProps<FormContainerProps, FormValu
 
 const availableGroupsOptions = Object.keys(WorkingGroupDef)
   .filter((wgKey) => wgKey !== 'Gateway') // Gateway group not yet supported!
-  .map((wgKey) => ({ text: wgKey + ' Working Group', value: wgKey }));
+  .map((wgKey) => {
+    let text = `${wgKey} Working Group`;
+
+    if (wgKey.toLowerCase().includes('operations')) {
+      const workingGroupType = wgKey.slice('operations'.length);
+
+      text = `Operations Working Group ${workingGroupType}`;
+    }
+
+    return { text, value: wgKey };
+  });
 
 export const GenericWorkingGroupProposalForm: React.FunctionComponent<FormInnerProps> = (props) => {
   const {

+ 12 - 3
pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx

@@ -9,14 +9,20 @@ import { AvailableGroups } from '../working_groups';
 import { WorkingGroupMembership,
   ContentCurators,
   StorageProviders,
-  OperationsGroup } from './WorkingGroup';
+  OperationsGroupAlpha,
+  OperationsGroupBeta,
+  OperationsGroupGamma,
+  Distribution } from './WorkingGroup';
 
 import styled from 'styled-components';
 
 type State = {
   curators?: WorkingGroupMembership;
   storageProviders?: WorkingGroupMembership;
-  operationsGroup?: WorkingGroupMembership;
+  operationsGroupAlpha?: WorkingGroupMembership;
+  operationsGroupBeta?: WorkingGroupMembership;
+  operationsGroupGamma?: WorkingGroupMembership;
+  distribution?: WorkingGroupMembership;
 }
 
 export class WorkingGroupsController extends Controller<State, ITransport> {
@@ -56,7 +62,10 @@ export const WorkingGroupsView = View<WorkingGroupsController, State>(
     <WorkingGroupsOverview>
       <ContentCurators {...state.curators}/>
       <StorageProviders {...state.storageProviders}/>
-      <OperationsGroup {...state.operationsGroup}/>
+      <OperationsGroupAlpha {...state.operationsGroupAlpha}/>
+      <OperationsGroupBeta {...state.operationsGroupBeta}/>
+      <OperationsGroupGamma {...state.operationsGroupGamma}/>
+      <Distribution {...state.distribution}/>
     </WorkingGroupsOverview>
   )
 );

+ 37 - 2
pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx

@@ -73,6 +73,10 @@ type GroupOverviewProps = GroupOverviewOuterProps & {
   customBecomeLeadDesc?: string;
 }
 
+interface OperationsGroupProps extends GroupOverviewOuterProps{
+  group: WorkingGroups;
+}
+
 const GroupOverview = Loadable<GroupOverviewProps>(
   ['workers', 'leadStatus'],
   ({
@@ -142,9 +146,8 @@ export const StorageProviders = (props: GroupOverviewOuterProps) => (
   />
 );
 
-export const OperationsGroup = (props: GroupOverviewOuterProps) => (
+export const OperationsGroup = (props: OperationsGroupProps) => (
   <GroupOverview
-    group={WorkingGroups.Operations}
     description={
       <span>
         {"Operations Working Group encompases all the activites that don't require privilages on chain, for example:"}
@@ -159,6 +162,38 @@ export const OperationsGroup = (props: GroupOverviewOuterProps) => (
   />
 );
 
+export const OperationsGroupAlpha = (props: GroupOverviewOuterProps) => (
+  <OperationsGroup
+    group={WorkingGroups.OperationsAlpha}
+    {...props}
+  />
+);
+
+export const OperationsGroupBeta = (props: GroupOverviewOuterProps) => (
+  <OperationsGroup
+    group={WorkingGroups.OperationsBeta}
+    {...props}
+  />
+);
+
+export const OperationsGroupGamma = (props: GroupOverviewOuterProps) => (
+  <OperationsGroup
+    group={WorkingGroups.OperationsGamma}
+    {...props}
+  />
+);
+
+export const Distribution = (props: GroupOverviewOuterProps) => (
+  <GroupOverview
+    group={WorkingGroups.Distribution}
+    description={
+      'Distribution Working Group is responsible for running and maintaining distributor nodes' +
+      ' that deliver large volumes of upstream data to a large number of simultaneous end users.'
+    }
+    {...props}
+  />
+);
+
 const LeadSection = styled.div`
   margin-top: 1rem;
 `;

+ 5 - 2
pioneer/packages/joy-roles/src/transport.substrate.ts

@@ -51,8 +51,11 @@ type StakePair<T = Balance> = {
 
 const apiModuleByGroup = {
   [WorkingGroups.StorageProviders]: 'storageWorkingGroup',
-  [WorkingGroups.ContentCurators]: 'contentDirectoryWorkingGroup',
-  [WorkingGroups.Operations]: 'operationsWorkingGroup'
+  [WorkingGroups.ContentCurators]: 'contentWorkingGroup',
+  [WorkingGroups.OperationsAlpha]: 'operationsWorkingGroupAlpha',
+  [WorkingGroups.OperationsBeta]: 'operationsWorkingGroupBeta',
+  [WorkingGroups.OperationsGamma]: 'operationsWorkingGroupGamma',
+  [WorkingGroups.Distribution]: 'distributionWorkingGroup'
 } as const;
 
 export class Transport extends BaseTransport implements ITransport {

+ 12 - 3
pioneer/packages/joy-roles/src/working_groups.ts

@@ -1,17 +1,26 @@
 export enum WorkingGroups {
   ContentCurators = 'curators',
   StorageProviders = 'storageProviders',
-  Operations = 'operationsGroup'
+  OperationsAlpha = 'operationsGroupAlpha',
+  OperationsBeta = 'operationsGroupBeta',
+  OperationsGamma = 'operationsGroupGamma',
+  Distribution = 'distribution'
 }
 
 export const AvailableGroups: readonly WorkingGroups[] = [
   WorkingGroups.ContentCurators,
   WorkingGroups.StorageProviders,
-  WorkingGroups.Operations
+  WorkingGroups.OperationsAlpha,
+  WorkingGroups.OperationsBeta,
+  WorkingGroups.OperationsGamma,
+  WorkingGroups.Distribution
 ] as const;
 
 export const workerRoleNameByGroup: { [key in WorkingGroups]: string } = {
   [WorkingGroups.ContentCurators]: 'Content Curator',
   [WorkingGroups.StorageProviders]: 'Storage Provider',
-  [WorkingGroups.Operations]: 'Operations Group Worker'
+  [WorkingGroups.OperationsAlpha]: 'Operations Group Alpha Worker',
+  [WorkingGroups.OperationsBeta]: 'Operations Group Beta Worker',
+  [WorkingGroups.OperationsGamma]: 'Operations Group Gamma Worker',
+  [WorkingGroups.Distribution]: 'Distribution'
 };

+ 62 - 106
pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx

@@ -5,7 +5,7 @@ import { useWindowDimensions } from '../../../joy-utils/src/react/hooks';
 
 import { TokenomicsData, StatusServerData } from '@polkadot/joy-utils/src/types/tokenomics';
 
-import { COLORS } from './index';
+import { NON_WORKING_GROUPS, WORKING_GROUPS } from '../tokenomicsGroupsData';
 
 const round = (num: number): number => Math.round((num + Number.EPSILON) * 100) / 100;
 
@@ -121,25 +121,24 @@ const SpendingAndStakeTableRow: React.FC<{
   );
 };
 
-type TokenomicsGroup =
-  'validators' |
-  'council' |
-  'storageProviders' |
-  'contentCurators' |
-  'operations'
+type TokenomicsGroup = typeof WORKING_GROUPS[number]['groupType'] | typeof NON_WORKING_GROUPS[number]['groupType'];
 
 const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
   const { width } = useWindowDimensions();
 
   const displayStatusData = (group: TokenomicsGroup, dataType: 'rewardsPerWeek' | 'totalStake', lead = false): string | undefined => {
-    if ((group === 'storageProviders' || group === 'contentCurators') && lead) {
+    if (WORKING_GROUPS.map(({ groupType }) => groupType).includes(group as any) && lead) {
       return statusData === null
         ? 'Data currently unavailable...'
-        : (data && statusData) && `${(data[group].lead[dataType] * Number(statusData.price)).toFixed(2)}`;
+        : data &&
+            statusData &&
+            `${(
+              data[group as typeof WORKING_GROUPS[number]['groupType']].lead[dataType] * Number(statusData.price)
+            ).toFixed(2)}`;
     } else {
       return statusData === null
         ? 'Data currently unavailable...'
-        : (data && statusData) && `${(data[group][dataType] * Number(statusData.price)).toFixed(2)}`;
+        : data && statusData && `${(data[group][dataType] * Number(statusData.price)).toFixed(2)}`;
     }
   };
 
@@ -160,102 +159,59 @@ const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; status
       </Table.Header>
 
       <Table.Body>
-        <SpendingAndStakeTableRow
-          role={width <= 1050 ? 'Validators' : 'Validators (Nominators)'}
-          helpContent='The current set of active Validators (and Nominators), and the sum of the sets projected rewards and total stakes (including Nominators).'
-          numberOfActors={data && `${data.validators.number} (${data.validators.nominators.number})`}
-          groupEarning={data && `${Math.round(data.validators.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('validators', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.validators.rewardsShare * 100)}`}
-          groupStake={data && `${data.validators.totalStake}`}
-          groupStakeDollar={displayStatusData('validators', 'totalStake')}
-          stakeShare={data && `${round(data.validators.stakeShare * 100)}`}
-          color={COLORS.VALIDATOR}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Council' : 'Council Members'}
-          helpContent='The current Council Members, and the sum of their projected rewards and total stakes (including voters/backers).'
-          numberOfActors={data && `${data.council.number}`}
-          groupEarning={data && `${Math.round(data.council.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('council', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.council.rewardsShare * 100)}`}
-          groupStake={data && `${data.council.totalStake}`}
-          groupStakeDollar={displayStatusData('council', 'totalStake')}
-          stakeShare={data && `${round(data.council.stakeShare * 100)}`}
-          color={COLORS.COUNCIL_MEMBER}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Storage' : 'Storage Providers'}
-          helpContent='The current Storage Providers, and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.storageProviders.number}`}
-          groupEarning={data && `${Math.round(data.storageProviders.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.storageProviders.rewardsShare * 100)}`}
-          groupStake={data && `${data.storageProviders.totalStake}`}
-          groupStakeDollar={displayStatusData('storageProviders', 'totalStake')}
-          stakeShare={data && `${round(data.storageProviders.stakeShare * 100)}`}
-          color={COLORS.STORAGE_PROVIDER}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'S. Lead' : width <= 1050 ? 'Storage Lead' : 'Storage Provider Lead'}
-          helpContent='Current Storage Provider Lead, and their projected reward and stake.'
-          numberOfActors={data && `${data.storageProviders.lead.number}`}
-          groupEarning={data && `${Math.round(data.storageProviders.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek', true)}
-          earningShare={data && `${round(data.storageProviders.lead.rewardsShare * 100)}`}
-          groupStake={data && `${data.storageProviders.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('storageProviders', 'totalStake', true)}
-          stakeShare={data && `${round(data.storageProviders.lead.stakeShare * 100)}`}
-          color={COLORS.STORAGE_LEAD}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Curators' : 'Content Curators'}
-          helpContent='The current Content Curators, and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.contentCurators.number}`}
-          groupEarning={data && `${Math.round(data.contentCurators.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.contentCurators.rewardsShare * 100)}`}
-          groupStake={data && `${data.contentCurators.totalStake}`}
-          groupStakeDollar={displayStatusData('contentCurators', 'totalStake')}
-          stakeShare={data && `${round(data.contentCurators.stakeShare * 100)}`}
-          color={COLORS.CONTENT_CURATOR}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'C. Lead' : 'Curators Lead'}
-          helpContent='Current Content Curators Lead, and their projected reward and stake.'
-          numberOfActors={data && `${data.contentCurators.lead.number}`}
-          groupEarning={data && `${Math.round(data.contentCurators.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek', true)}
-          earningShare={data && `${round(data.contentCurators.lead.rewardsShare * 100)}`}
-          groupStake={data && `${data.contentCurators.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('contentCurators', 'totalStake', true)}
-          stakeShare={data && `${round(data.contentCurators.lead.stakeShare * 100)}`}
-          color={COLORS.CURATOR_LEAD}
-        />
-        <SpendingAndStakeTableRow
-          role='Operations'
-          helpContent='The current Operations members, and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.operations.number}`}
-          groupEarning={data && `${Math.round(data.operations.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('operations', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.operations.rewardsShare * 100)}`}
-          groupStake={data && `${data.operations.totalStake}`}
-          groupStakeDollar={displayStatusData('operations', 'totalStake')}
-          stakeShare={data && `${round(data.operations.stakeShare * 100)}`}
-          color={COLORS.OPERATIONS}
-        />
-        <SpendingAndStakeTableRow
-          role='Operations Lead'
-          helpContent='Current Operations Lead, and their projected reward and stake.'
-          numberOfActors={data && `${data.operations.lead.number}`}
-          groupEarning={data && `${Math.round(data.operations.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('operations', 'rewardsPerWeek', true)}
-          earningShare={data && `${round(data.operations.lead.rewardsShare * 100)}`}
-          groupStake={data && `${data.operations.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('operations', 'totalStake', true)}
-          stakeShare={data && `${round(data.operations.lead.stakeShare * 100)}`}
-          color={COLORS.OPERATIONS_LEAD}
-        />
+        {NON_WORKING_GROUPS.map(({ groupType, titleCutoff, shortTitle, title, helpText, color }) => {
+          let numberOfActors = data && `${data[groupType].number}`;
+
+          if (groupType === 'validators' && data) {
+            numberOfActors = `${data.validators.number} (${data.validators.nominators.number})`;
+          }
+
+          return (
+            <SpendingAndStakeTableRow
+              key={groupType}
+              role={width <= titleCutoff ? shortTitle : title}
+              helpContent={helpText}
+              numberOfActors={numberOfActors}
+              groupEarning={data && `${Math.round(data[groupType].rewardsPerWeek)}`}
+              groupEarningDollar={displayStatusData(groupType, 'rewardsPerWeek')}
+              earningShare={data && `${round(data[groupType].rewardsShare * 100)}`}
+              groupStake={data && `${data[groupType].totalStake}`}
+              groupStakeDollar={displayStatusData(groupType, 'totalStake')}
+              stakeShare={data && `${round(data[groupType].stakeShare * 100)}`}
+              color={color}
+            />
+          );
+        })}
+        {WORKING_GROUPS.map(({ groupType, titleCutoff, shortTitle, title, helpText, color, lead }) => {
+          return (
+            <React.Fragment key={groupType}>
+              <SpendingAndStakeTableRow
+                role={width <= titleCutoff ? shortTitle : title}
+                helpContent={helpText}
+                numberOfActors={data && `${data[groupType].number}`}
+                groupEarning={data && `${Math.round(data[groupType].rewardsPerWeek)}`}
+                groupEarningDollar={displayStatusData(groupType, 'rewardsPerWeek')}
+                earningShare={data && `${round(data[groupType].rewardsShare * 100)}`}
+                groupStake={data && `${data[groupType].totalStake}`}
+                groupStakeDollar={displayStatusData(groupType, 'totalStake')}
+                stakeShare={data && `${round(data[groupType].stakeShare * 100)}`}
+                color={color}
+              />
+              <SpendingAndStakeTableRow
+                role={width <= lead.titleCutoff ? lead.shortTitle : lead.title}
+                helpContent={lead.helpText}
+                numberOfActors={data && `${data[groupType].lead.number}`}
+                groupEarning={data && `${Math.round(data[groupType].lead.rewardsPerWeek)}`}
+                groupEarningDollar={displayStatusData(groupType, 'rewardsPerWeek', true)}
+                earningShare={data && `${round(data[groupType].lead.rewardsShare * 100)}`}
+                groupStake={data && `${data[groupType].lead.totalStake}`}
+                groupStakeDollar={displayStatusData(groupType, 'totalStake', true)}
+                stakeShare={data && `${round(data[groupType].lead.stakeShare * 100)}`}
+                color={lead.color}
+              />
+            </React.Fragment>
+          );
+        })}
         <SpendingAndStakeTableRow
           role='TOTAL'
           active={true}

+ 58 - 83
pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx

@@ -4,7 +4,8 @@ import PieChart from '../../../react-components/src/Chart/PieChart';
 import styled from 'styled-components';
 
 import { TokenomicsData } from '@polkadot/joy-utils/src/types/tokenomics';
-import { COLORS } from './index';
+
+import { WORKING_GROUPS, NON_WORKING_GROUPS } from '../tokenomicsGroupsData';
 
 const StyledPieChart = styled(PieChart)`
   width:15rem;
@@ -29,88 +30,62 @@ const ChartContainer = styled('div')`
 const TokenomicsCharts: React.FC<{data?: TokenomicsData; className?: string}> = ({ data, className }) => {
   return (
     <div className={className}>
-      {data ? <ChartContainer>
-        <StyledPieChart
-          values={[{
-            colors: [COLORS.VALIDATOR],
-            label: 'Validators',
-            value: data.validators.rewardsShare * 100
-          }, {
-            colors: [COLORS.COUNCIL_MEMBER],
-            label: 'Council',
-            value: data.council.rewardsShare * 100
-          }, {
-            colors: [COLORS.STORAGE_PROVIDER],
-            label: 'Storage Providers',
-            value: data.storageProviders.rewardsShare * 100
-          }, {
-            colors: [COLORS.STORAGE_LEAD],
-            label: 'Storage Lead',
-            value: data.storageProviders.lead.rewardsShare * 100
-          }, {
-            colors: [COLORS.CONTENT_CURATOR],
-            label: 'Content Curators',
-            value: data.contentCurators.rewardsShare * 100
-          }, {
-            colors: [COLORS.CURATOR_LEAD],
-            label: 'Content Curators Lead',
-            value: data.contentCurators.lead.rewardsShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS],
-            label: 'Operations',
-            value: data.operations.rewardsShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS_LEAD],
-            label: 'Operations Lead',
-            value: data.operations.lead.rewardsShare * 100
-          }
-          ]} />
-        <Label as='div'>
-          <Icon name='money' />
-          <span style={{ fontWeight: 600 }}>Spending</span>
-        </Label>
-      </ChartContainer> : <Icon name='circle notched' loading/>}
-      {data ? <ChartContainer>
-        <StyledPieChart
-          values={[{
-            colors: [COLORS.VALIDATOR],
-            label: 'Validators',
-            value: data.validators.stakeShare * 100
-          }, {
-            colors: [COLORS.COUNCIL_MEMBER],
-            label: 'Council',
-            value: data.council.stakeShare * 100
-          }, {
-            colors: [COLORS.STORAGE_PROVIDER],
-            label: 'Storage Providers',
-            value: data.storageProviders.stakeShare * 100
-          }, {
-            colors: [COLORS.STORAGE_LEAD],
-            label: 'Storage Lead',
-            value: data.storageProviders.lead.stakeShare * 100
-          }, {
-            colors: [COLORS.CONTENT_CURATOR],
-            label: 'Content Curators',
-            value: data.contentCurators.stakeShare * 100
-          }, {
-            colors: [COLORS.CURATOR_LEAD],
-            label: 'Content Curators Lead',
-            value: data.contentCurators.lead.stakeShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS],
-            label: 'Operations',
-            value: data.operations.stakeShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS_LEAD],
-            label: 'Operations Lead',
-            value: data.operations.lead.stakeShare * 100
-          }
-          ]} />
-        <Label as='div'>
-          <Icon name='block layout' />
-          <span style={{ fontWeight: 600 }}>Stake</span>
-        </Label>
-      </ChartContainer> : <Icon name='circle notched' loading/>}
+      {data ? (
+        <ChartContainer>
+          <StyledPieChart
+            values={[
+              ...WORKING_GROUPS.map(({ color, title, groupType, lead }) => [{
+                colors: [color],
+                label: title,
+                value: data[groupType].rewardsShare * 100
+              }, {
+                colors: [lead.color],
+                label: lead.title,
+                value: data[groupType].lead.rewardsShare * 100
+              }]).flat(),
+              ...NON_WORKING_GROUPS.map(({ color, shortTitle, groupType }) => ({
+                colors: [color],
+                label: shortTitle,
+                value: data[groupType].rewardsShare * 100
+              }))
+            ]}
+          />
+          <Label as='div'>
+            <Icon name='money' />
+            <span style={{ fontWeight: 600 }}>Spending</span>
+          </Label>
+        </ChartContainer>
+      ) : (
+        <Icon name='circle notched' loading />
+      )}
+      {data ? (
+        <ChartContainer>
+          <StyledPieChart
+            values={[
+              ...WORKING_GROUPS.map(({ color, title, groupType, lead }) => [{
+                colors: [color],
+                label: title,
+                value: data[groupType].stakeShare * 100
+              }, {
+                colors: [lead.color],
+                label: lead.title,
+                value: data[groupType].lead.stakeShare * 100
+              }]).flat(),
+              ...NON_WORKING_GROUPS.map(({ color, shortTitle, groupType }) => ({
+                colors: [color],
+                label: shortTitle,
+                value: data[groupType].stakeShare * 100
+              }))
+            ]}
+          />
+          <Label as='div'>
+            <Icon name='block layout' />
+            <span style={{ fontWeight: 600 }}>Stake</span>
+          </Label>
+        </ChartContainer>
+      ) : (
+        <Icon name='circle notched' loading />
+      )}
     </div>
   );
 };

+ 0 - 12
pioneer/packages/joy-tokenomics/src/Overview/index.tsx

@@ -38,17 +38,6 @@ const StyledTokenomicsCharts = styled(TokenomicsCharts)`
   }
 `;
 
-const COLORS = {
-  VALIDATOR: '#ff9800',
-  COUNCIL_MEMBER: '#ffc107',
-  STORAGE_PROVIDER: '#ffeb3b',
-  STORAGE_LEAD: '#cddc39',
-  CONTENT_CURATOR: '#8bc34a',
-  CURATOR_LEAD: '#4caf50',
-  OPERATIONS: '#009688',
-  OPERATIONS_LEAD: '#00bcd4'
-};
-
 const Overview: React.FC = () => {
   const transport = useTransport();
   const [statusDataValue, statusDataError] = usePromise<StatusServerData | undefined>(() => fetch('https://status.joystream.org/status').then((res) => res.json().then((data) => data as StatusServerData)), undefined, []);
@@ -68,4 +57,3 @@ const Overview: React.FC = () => {
 };
 
 export default Overview;
-export { COLORS };

+ 113 - 0
pioneer/packages/joy-tokenomics/src/tokenomicsGroupsData.ts

@@ -0,0 +1,113 @@
+export const NON_WORKING_GROUPS = [
+  {
+    groupType: 'validators' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Validators',
+    title: 'Validators (Nominators)',
+    helpText:
+      'The current set of active Validators (and Nominators), and the sum of the sets projected rewards and total stakes (including Nominators).',
+    color: '#ff9800'
+  },
+  {
+    groupType: 'council' as const,
+    titleCutoff: 1015,
+    shortTitle: 'Council',
+    title: 'Council Members',
+    helpText:
+      'The current Council Members, and the sum of their projected rewards and total stakes (including voters/backers).',
+    color: '#ffc107'
+  }
+];
+
+export const WORKING_GROUPS = [
+  {
+    groupType: 'storageProviders' as const,
+    titleCutoff: 1015,
+    shortTitle: 'Storage',
+    title: 'Storage Providers',
+    helpText: 'The current Storage Providers, and the sum of their projected rewards and stakes.',
+    color: '#ffeb3b',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'S. Lead',
+      title: 'Storage Lead',
+      helpText: 'Current Storage Provider Lead, and their projected reward and stake.',
+      color: '#cddc39'
+    }
+  },
+  {
+    groupType: 'contentCurators' as const,
+    titleCutoff: 1015,
+    shortTitle: 'Curators',
+    title: 'Content Curators',
+    helpText: 'The current Content Curators, and the sum of their projected rewards and stakes.',
+    color: '#8bc34a',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'C. Lead',
+      title: 'Curators Lead',
+      helpText: 'Current Content Curators Lead, and their projected reward and stake.',
+      color: '#4caf50'
+    }
+  },
+  {
+    groupType: 'operationsAlpha' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Operations A.',
+    title: 'Operations Alpha',
+    helpText: 'The current Operations Group Alpha members, and the sum of their projected rewards and stakes.',
+    color: '#009688',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Operations A. Lead',
+      title: 'Operations Alpha Lead',
+      helpText: 'Current Operations Group Alpha Lead, and their projected reward and stake.',
+      color: '#00bcd4'
+    }
+  },
+  {
+    groupType: 'operationsBeta' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Operations B.',
+    title: 'Operations Beta',
+    helpText: 'The current Operations Group Beta members, and the sum of their projected rewards and stakes.',
+    color: '#03a9f4',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Operations B. Lead',
+      title: 'Operations Beta Lead',
+      helpText: 'Current Operations Group Beta Lead, and their projected reward and stake.',
+      color: '#2196f3'
+    }
+  },
+  {
+    groupType: 'operationsGamma' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Operations G.',
+    title: 'Operations Gamma',
+    helpText: 'The current Operations Group Gamma members, and the sum of their projected rewards and stakes.',
+    color: '#3f51b5',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Operations G. Lead',
+      title: 'Operations Gamma Lead',
+      helpText: 'Current Operations Group Gamma Lead, and their projected reward and stake.',
+      color: '#673ab7'
+    }
+  },
+  {
+    groupType: 'distribution' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Distribution',
+    title: 'Distribution',
+    helpText: 'The current Distribution Group members, and the sum of their projected rewards and stakes.',
+    color: '#9c27b0',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Distr. Lead',
+      title: 'Distribution Lead',
+      helpText: 'Current Distribution Group Lead, and their projected reward and stake.',
+      color: '#e91e63'
+    }
+  }
+];

+ 6 - 3
pioneer/packages/joy-utils/src/consts/workingGroups.ts

@@ -1,7 +1,10 @@
 import { WorkingGroupKey } from '@joystream/types/common';
 export const apiModuleByGroup: { [k in WorkingGroupKey]: string } = {
   Storage: 'storageWorkingGroup',
-  Content: 'contentDirectoryWorkingGroup',
-  Operations: 'operationsWorkingGroup',
-  Gateway: 'gatewayWorkingGroup'
+  Content: 'contentWorkingGroup',
+  OperationsAlpha: 'operationsWorkingGroupAlpha',
+  OperationsBeta: 'operationsWorkingGroupBeta',
+  OperationsGamma: 'operationsWorkingGroupGamma',
+  Gateway: 'gatewayWorkingGroup',
+  Distribution: 'distributionWorkingGroup'
 };

+ 8 - 2
pioneer/packages/joy-utils/src/transport/tokenomics.ts

@@ -228,7 +228,10 @@ export default class TokenomicsTransport extends BaseTransport {
     const workingGroupsData = {
       storageProviders: await this.getWorkingGroupData('Storage'),
       curators: await this.getWorkingGroupData('Content'),
-      operations: await this.getWorkingGroupData('Operations')
+      operationsAlpha: await this.getWorkingGroupData('OperationsAlpha'),
+      operationsBeta: await this.getWorkingGroupData('OperationsBeta'),
+      operationsGamma: await this.getWorkingGroupData('OperationsGamma'),
+      distribution: await this.getWorkingGroupData('Distribution')
     };
     const { numberOfValidators, numberOfNominators, totalValidatorStake, validatorRewardsPerWeek, totalIssuance } =
       await this.getValidatorData();
@@ -293,7 +296,10 @@ export default class TokenomicsTransport extends BaseTransport {
       },
       storageProviders: resolveGroupData(workingGroupsData.storageProviders),
       contentCurators: resolveGroupData(workingGroupsData.curators),
-      operations: resolveGroupData(workingGroupsData.operations)
+      operationsAlpha: resolveGroupData(workingGroupsData.operationsAlpha),
+      operationsBeta: resolveGroupData(workingGroupsData.operationsBeta),
+      operationsGamma: resolveGroupData(workingGroupsData.operationsGamma),
+      distribution: resolveGroupData(workingGroupsData.distribution)
     };
   }
 }

+ 4 - 1
pioneer/packages/joy-utils/src/types/tokenomics.ts

@@ -37,7 +37,10 @@ export type TokenomicsData = {
   };
   storageProviders: WorkingGroupTokenomicsData;
   contentCurators: WorkingGroupTokenomicsData;
-  operations: WorkingGroupTokenomicsData;
+  operationsAlpha: WorkingGroupTokenomicsData;
+  operationsBeta: WorkingGroupTokenomicsData;
+  operationsGamma: WorkingGroupTokenomicsData;
+  distribution: WorkingGroupTokenomicsData;
 }
 
 export type StatusServerData = {