Prechádzať zdrojové kódy

Merge branch 'sumer' into query_node_mappings

ondratra 3 rokov pred
rodič
commit
b54029b7cc

+ 19 - 19
pioneer/packages/joy-roles/src/elements.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from 'react';
 import moment from 'moment';
-import { Card, Icon, Image, Label, Statistic, Button } from 'semantic-ui-react';
+import { Card, Icon, Image, Label, Statistic, Button, Transition } from 'semantic-ui-react';
 import { Link } from 'react-router-dom';
 
 import { Balance } from '@polkadot/types/interfaces';
@@ -8,7 +8,6 @@ import { formatBalance } from '@polkadot/util';
 import Identicon from '@polkadot/react-identicon';
 import { IMembership, MemberId } from '@joystream/types/members';
 import { GenericAccountId } from '@polkadot/types';
-import { WorkerId } from '@joystream/types/working-group';
 import { WorkingGroups } from './working_groups';
 import { RewardRelationship } from '@joystream/types/recurring-rewards';
 import { formatReward } from '@polkadot/joy-utils/functions/format';
@@ -49,26 +48,17 @@ export type GroupMember = {
   title: string;
   stake?: Balance;
   rewardRelationship?: RewardRelationship;
+  storage?: string;
 }
 
-export type GroupLead = {
-  memberId: MemberId;
-  workerId?: WorkerId; // In case of "working-group" module
-  roleAccount: GenericAccountId;
-  profile: IMembership;
-  title: string;
-  stake?: Balance;
-  rewardRelationship?: RewardRelationship;
-}
-
-export function GroupLeadView (props: GroupLead) {
+export function GroupLeadView (props: GroupMember) {
   let avatar = <Identicon value={props.roleAccount.toString()} size={50} />;
 
   if (typeof props.profile.avatar_uri !== 'undefined' && props.profile.avatar_uri.toString() !== '') {
     avatar = <Image src={props.profile.avatar_uri.toString()} circular className='avatar' />;
   }
 
-  const { stake, rewardRelationship } = props;
+  const { stake, rewardRelationship, storage } = props;
 
   return (
     <Card color='grey' className='staked-card'>
@@ -91,7 +81,7 @@ export function GroupLeadView (props: GroupLead) {
           </Label>
         </Card.Description>
       </Card.Content>
-      <GroupMemberDetails {...{ stake, rewardRelationship }} />
+      <GroupMemberDetails {...{ stake, rewardRelationship, storage }} />
     </Card>
   );
 }
@@ -106,6 +96,7 @@ const StakeAndReward = styled.div`
 type GroupMemberDetailsProps = {
   rewardRelationship?: RewardRelationship;
   stake?: Balance;
+  storage?: string
 }
 
 export function GroupMemberDetails (props: GroupMemberDetailsProps) {
@@ -150,15 +141,24 @@ export function GroupMemberDetails (props: GroupMemberDetailsProps) {
     );
   }
 
+  if (props.storage) {
+    details.push(
+      <Label>
+        Worker Storage
+        <Label.Detail style={{ marginTop: '5px', marginLeft: 0, display: 'block', wordBreak: 'break-word', wordWrap: 'break-word' }}>{props.storage}</Label.Detail>
+      </Label>
+    );
+  }
+
   return (
     <Card.Content extra>
-      { showDetails && (
+      <Transition visible={showDetails} animation='fade down' duration={500}>
         <Card.Description>
           <StakeAndReward>
             {details.map((detail, index) => <div key={index}>{detail}</div>)}
           </StakeAndReward>
         </Card.Description>
-      ) }
+      </Transition>
       <Button onClick={ () => setShowDetails((v) => !v) } size='tiny' fluid>
         { showDetails ? 'Hide' : 'Show'} details
       </Button>
@@ -173,7 +173,7 @@ export function GroupMemberView (props: GroupMember) {
     avatar = <Image src={props.profile.avatar_uri.toString()} circular className='avatar' />;
   }
 
-  const { stake, rewardRelationship } = props;
+  const { stake, rewardRelationship, storage } = props;
 
   return (
     <Card color='grey' className='staked-card'>
@@ -189,7 +189,7 @@ export function GroupMemberView (props: GroupMember) {
           </Label>
         </Card.Meta>
       </Card.Content>
-      <GroupMemberDetails {...{ stake, rewardRelationship }} />
+      <GroupMemberDetails {...{ stake, rewardRelationship, storage }} />
     </Card>
   );
 }

+ 20 - 23
pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx

@@ -4,45 +4,41 @@ import { Controller } from '@polkadot/joy-utils/react/helpers';
 import { View } from '@polkadot/joy-utils/react/hocs';
 
 import { ITransport } from '../transport';
+import { AvailableGroups } from '../working_groups';
 
-import { ContentCurators,
-  WorkingGroupMembership,
-  StorageProviders } from './WorkingGroup';
+import { WorkingGroupMembership,
+  ContentCurators,
+  StorageProviders,
+  OperationsGroup } from './WorkingGroup';
 
 import styled from 'styled-components';
-import { normalizeError } from '@polkadot/joy-utils/functions/misc';
 
 type State = {
-  contentCurators?: WorkingGroupMembership;
+  curators?: WorkingGroupMembership;
   storageProviders?: WorkingGroupMembership;
+  operationsGroup?: WorkingGroupMembership;
 }
 
 export class WorkingGroupsController extends Controller<State, ITransport> {
-  constructor (transport: ITransport, initialState: State = {}) {
+  constructor (transport: ITransport) {
     super(transport, {});
   }
 
   refreshState () {
-    this.getCurationGroup();
-    this.getStorageGroup();
+    void this.getGroups();
   }
 
-  getCurationGroup () {
-    this.transport.curationGroup()
-      .then((value: WorkingGroupMembership) => {
-        this.setState({ contentCurators: value });
-        this.dispatch();
-      })
-      .catch((e) => this.onError(normalizeError(e)));
-  }
+  async getGroups () {
+    const newState: Partial<State> = {};
 
-  getStorageGroup () {
-    this.transport.storageGroup()
-      .then((value: WorkingGroupMembership) => {
-        this.setState({ storageProviders: value });
-        this.dispatch();
+    await Promise.all(
+      AvailableGroups.map(async (group) => {
+        newState[group] = await this.transport.groupOverview(group);
       })
-      .catch((e) => this.onError(normalizeError(e)));
+    );
+
+    this.setState(newState);
+    this.dispatch();
   }
 }
 
@@ -58,8 +54,9 @@ const WorkingGroupsOverview = styled.div`
 export const WorkingGroupsView = View<WorkingGroupsController, State>(
   ({ state }) => (
     <WorkingGroupsOverview>
-      <ContentCurators {...state.contentCurators}/>
+      <ContentCurators {...state.curators}/>
       <StorageProviders {...state.storageProviders}/>
+      <OperationsGroup {...state.operationsGroup}/>
     </WorkingGroupsOverview>
   )
 );

+ 36 - 11
pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx

@@ -1,8 +1,8 @@
-import React from 'react';
-import { Button, Card, Icon, Message, SemanticICONS } from 'semantic-ui-react';
+import React, { useState } from 'react';
+import { Button, Card, Icon, Message, SemanticICONS, Transition } from 'semantic-ui-react';
 import { Link } from 'react-router-dom';
 
-import { GroupLeadView, GroupMember, GroupMemberView, GroupLead } from '../elements';
+import { GroupLeadView, GroupMember, GroupMemberView } from '../elements';
 import { Loadable } from '@polkadot/joy-utils/react/hocs';
 
 import { WorkingGroups } from '../working_groups';
@@ -65,7 +65,7 @@ type GroupOverviewOuterProps = Partial<WorkingGroupMembership> & {
 
 type GroupOverviewProps = GroupOverviewOuterProps & {
   group: WorkingGroups;
-  description: string;
+  description: string | JSX.Element;
   customGroupName?: string;
   customJoinTitle?: string;
   customJoinDesc?: string;
@@ -93,20 +93,28 @@ const GroupOverview = Loadable<GroupOverviewProps>(
     const joinDesc = customJoinDesc || `There are openings for new ${groupName}. This is a great way to support Joystream!`;
     const becomeLeadTitle = customBecomeLeadTitle || `Become ${groupName} Lead!`;
     const becomeLeadDesc = customBecomeLeadDesc || `An opportunity to become ${groupName} Leader is currently available! This is a great way to support Joystream!`;
+    const [showMembers, setShowMembers] = useState(false);
 
     return (
       <GroupOverviewSection>
         <h2>{ groupName }</h2>
         <p>{ description }</p>
-        <Card.Group style={{ alignItems: 'flex-start' }}>
-          { workers!.map((worker, key) => (
-            <GroupMemberView key={key} {...worker} />
-          )) }
-        </Card.Group>
+        <Button onClick={() => setShowMembers((v) => !v)}>
+          { showMembers ? 'Hide' : 'Show' } members
+        </Button>
+        <Transition visible={showMembers} animation='fade down' duration={500}>
+          <span>
+            <Card.Group style={{ alignItems: 'flex-start' }}>
+              { workers!.map((worker, key) => (
+                <GroupMemberView key={key} {...worker} />
+              )) }
+            </Card.Group>
+            { leadStatus && <CurrentLead groupName={groupName} {...leadStatus}/> }
+          </span>
+        </Transition>
         { workerRolesAvailable
           ? <JoinRole group={group} title={joinTitle} description={joinDesc} />
           : <NoRolesAvailable /> }
-        { leadStatus && <CurrentLead groupName={groupName} {...leadStatus}/> }
         { leadRolesAvailable && <JoinRole group={group} lead title={becomeLeadTitle} description={becomeLeadDesc} /> }
       </GroupOverviewSection>
     );
@@ -134,12 +142,29 @@ export const StorageProviders = (props: GroupOverviewOuterProps) => (
   />
 );
 
+export const OperationsGroup = (props: GroupOverviewOuterProps) => (
+  <GroupOverview
+    group={WorkingGroups.Operations}
+    description={
+      <span>
+        {"Operations Working Group encompases all the activites that don't require privilages on chain, for example:"}
+        <ul>
+          <li>Development</li>
+          <li>Management</li>
+          <li>Marketing</li>
+        </ul>
+      </span>
+    }
+    {...props}
+  />
+);
+
 const LeadSection = styled.div`
   margin-top: 1rem;
 `;
 
 export type GroupLeadStatus = {
-  lead?: GroupLead;
+  lead?: GroupMember;
   loaded: boolean;
 }
 

+ 4 - 0
pioneer/packages/joy-roles/src/transport.mock.ts

@@ -117,6 +117,10 @@ export class Transport extends MockTransportBase implements ITransport {
     });
   }
 
+  groupOverview (group: WorkingGroups): Promise<WorkingGroupMembership> {
+    return this.storageGroup();
+  }
+
   currentOpportunities (): Promise<Array<WorkingGroupOpening>> {
     return this.simulateApiResponse<Array<WorkingGroupOpening>>(
       [

+ 12 - 54
pioneer/packages/joy-roles/src/transport.substrate.ts

@@ -49,15 +49,10 @@ type StakePair<T = Balance> = {
   role: T;
 }
 
-type GroupLeadWithMemberId = {
-  lead: Worker;
-  memberId: MemberId;
-  workerId: WorkerId;
-}
-
 const apiModuleByGroup = {
   [WorkingGroups.StorageProviders]: 'storageWorkingGroup',
-  [WorkingGroups.ContentCurators]: 'contentDirectoryWorkingGroup'
+  [WorkingGroups.ContentCurators]: 'contentDirectoryWorkingGroup',
+  [WorkingGroups.Operations]: 'operationsWorkingGroup'
 } as const;
 
 export class Transport extends BaseTransport implements ITransport {
@@ -142,6 +137,8 @@ export class Transport extends BaseTransport implements ITransport {
 
     const rewardRelationship = await this.workerRewardRelationship(worker);
 
+    const storage = await this.queryCachedByGroup(group).workerStorage(id);
+
     return ({
       roleAccount,
       group,
@@ -150,7 +147,8 @@ export class Transport extends BaseTransport implements ITransport {
       profile,
       title: workerRoleNameByGroup[group],
       stake: stakeValue,
-      rewardRelationship
+      rewardRelationship,
+      storage: this.api.createType('Text', storage).toString()
     });
   }
 
@@ -177,55 +175,15 @@ export class Transport extends BaseTransport implements ITransport {
     return false;
   }
 
-  protected async groupLead (group: WorkingGroups): Promise <GroupLeadWithMemberId | null> {
-    const optLeadId = (await this.queryCachedByGroup(group).currentLead()) as Option<WorkerId>;
-
-    if (!optLeadId.isSome) {
-      return null;
-    }
-
-    const leadWorkerId = optLeadId.unwrap();
-    const leadWorker = await this.queryCachedByGroup(group).workerById(leadWorkerId) as Worker;
-
-    if (leadWorker.isEmpty) {
-      return null;
-    }
-
-    return {
-      lead: leadWorker,
-      memberId: leadWorker.member_id,
-      workerId: leadWorkerId
-    };
-  }
-
   async groupLeadStatus (group: WorkingGroups = WorkingGroups.ContentCurators): Promise<GroupLeadStatus> {
-    const currentLead = await this.groupLead(group);
-
-    if (currentLead !== null) {
-      const profile = await this.cacheApi.query.members.membershipById(currentLead.memberId) as Membership;
-
-      if (profile.handle.isEmpty) {
-        throw new Error(`${group} lead profile not found!`);
-      }
+    const optLeadId = await this.queryCachedByGroup(group).currentLead() as Option<WorkerId>;
 
-      const rewardRelationshipId = currentLead.lead.reward_relationship;
-      const rewardRelationship = rewardRelationshipId.isSome
-        ? await this.rewardRelationshipById(rewardRelationshipId.unwrap())
-        : undefined;
-      const stake = currentLead.lead.role_stake_profile.isSome
-        ? await this.workerStake(currentLead.lead.role_stake_profile.unwrap())
-        : undefined;
+    if (optLeadId.isSome) {
+      const leadId = optLeadId.unwrap();
+      const leadWorker = await this.queryCachedByGroup(group).workerById(leadId) as Worker;
 
       return {
-        lead: {
-          memberId: currentLead.memberId,
-          workerId: currentLead.workerId,
-          roleAccount: currentLead.lead.role_account_id,
-          profile,
-          title: _.startCase(group) + ' Lead',
-          stake,
-          rewardRelationship
-        },
+        lead: await this.groupMember(group, leadId, leadWorker),
         loaded: true
       };
     } else {
@@ -540,7 +498,7 @@ export class Transport extends BaseTransport implements ITransport {
 
           return {
             workerId: id,
-            name: (groupLead?.workerId && groupLead.workerId.eq(id))
+            name: (groupLead?.workerId && groupLead.workerId === id.toNumber())
               ? _.startCase(group) + ' Lead'
               : workerRoleNameByGroup[group],
             reward: earnedValue,

+ 1 - 0
pioneer/packages/joy-roles/src/transport.ts

@@ -9,6 +9,7 @@ import { WorkingGroups } from './working_groups';
 
 export interface ITransport {
   groupLeadStatus: (group: WorkingGroups) => Promise<GroupLeadStatus>;
+  groupOverview: (group: WorkingGroups) => Promise<WorkingGroupMembership>;
   curationGroup: () => Promise<WorkingGroupMembership>;
   storageGroup: () => Promise<WorkingGroupMembership>;
   currentOpportunities: () => Promise<Array<WorkingGroupOpening>>;

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

@@ -1,14 +1,17 @@
 export enum WorkingGroups {
   ContentCurators = 'curators',
-  StorageProviders = 'storageProviders'
+  StorageProviders = 'storageProviders',
+  Operations = 'operationsGroup'
 }
 
 export const AvailableGroups: readonly WorkingGroups[] = [
   WorkingGroups.ContentCurators,
-  WorkingGroups.StorageProviders
+  WorkingGroups.StorageProviders,
+  WorkingGroups.Operations
 ] as const;
 
 export const workerRoleNameByGroup: { [key in WorkingGroups]: string } = {
   [WorkingGroups.ContentCurators]: 'Content Curator',
-  [WorkingGroups.StorageProviders]: 'Storage Provider'
+  [WorkingGroups.StorageProviders]: 'Storage Provider',
+  [WorkingGroups.Operations]: 'Operations Group Worker'
 };

+ 6 - 4
types/src/index.ts

@@ -94,9 +94,11 @@ type CreateInterface<T extends Codec> =
   | T
   | (T extends Option<infer S> ? undefined | null | S | CreateInterface_NoOption<S> : CreateInterface_NoOption<T>)
 
-export function createType<TypeName extends keyof InterfaceTypes>(
-  type: TypeName,
-  value: InterfaceTypes[TypeName] extends Codec ? CreateInterface<InterfaceTypes[TypeName]> : any
-): InterfaceTypes[TypeName] {
+export type AnyTypeName = keyof InterfaceTypes
+
+export function createType<TN extends AnyTypeName, T extends InterfaceTypes[TN] = InterfaceTypes[TN]>(
+  type: TN,
+  value: CreateInterface<T>
+): InterfaceTypes[TN] {
   return registry.createType(type, value)
 }