|
@@ -4,6 +4,7 @@ import { map, switchMap } from 'rxjs/operators';
|
|
|
import ApiPromise from '@polkadot/api/promise';
|
|
|
import { Balance } from '@polkadot/types/interfaces';
|
|
|
import { GenericAccountId, Option, u32, u128, Vec } from '@polkadot/types';
|
|
|
+import { Constructor } from '@polkadot/types/types';
|
|
|
import { Moment } from '@polkadot/types/interfaces/runtime';
|
|
|
import { QueueTxExtrinsicAdd } from '@polkadot/react-components/Status/types';
|
|
|
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
|
@@ -17,23 +18,26 @@ import { GroupMember } from './elements';
|
|
|
import {
|
|
|
Curator, CuratorId,
|
|
|
CuratorApplication, CuratorApplicationId,
|
|
|
- CuratorInduction,
|
|
|
CuratorRoleStakeProfile,
|
|
|
CuratorOpening, CuratorOpeningId,
|
|
|
Lead, LeadId
|
|
|
} from '@joystream/types/content-working-group';
|
|
|
|
|
|
import {
|
|
|
- WorkerApplication, WorkerApplicationId, WorkerOpening, WorkerOpeningId
|
|
|
+ WorkerApplication, WorkerApplicationId,
|
|
|
+ WorkerOpening, WorkerOpeningId,
|
|
|
+ Worker, WorkerId,
|
|
|
+ WorkerRoleStakeProfile,
|
|
|
+ Lead as LeadOf
|
|
|
} from '@joystream/types/bureaucracy';
|
|
|
|
|
|
-import { Application, Opening } from '@joystream/types/hiring';
|
|
|
+import { Application, Opening, OpeningId } from '@joystream/types/hiring';
|
|
|
import { Stake, StakeId } from '@joystream/types/stake';
|
|
|
-import { Recipient, RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards';
|
|
|
+import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards';
|
|
|
import { ActorInRole, Profile, MemberId, Role, RoleKeys, ActorId } from '@joystream/types/members';
|
|
|
import { createAccount, generateSeed } from '@polkadot/joy-utils/accounts';
|
|
|
|
|
|
-import { WorkingGroupMembership, StorageAndDistributionMembership, GroupLeadStatus } from './tabs/WorkingGroup';
|
|
|
+import { WorkingGroupMembership, GroupLeadStatus } from './tabs/WorkingGroup';
|
|
|
import { WorkingGroupOpening } from './tabs/Opportunities';
|
|
|
import { ActiveRole, OpeningApplication } from './tabs/MyRoles';
|
|
|
|
|
@@ -47,6 +51,7 @@ import {
|
|
|
} from './classifiers';
|
|
|
import { WorkingGroups, AvailableGroups } from './working_groups';
|
|
|
import { Sort, Sum, Zero } from './balances';
|
|
|
+import _ from 'lodash';
|
|
|
|
|
|
type WorkingGroupPair<HiringModuleType, WorkingGroupType> = {
|
|
|
hiringModule: HiringModuleType;
|
|
@@ -58,31 +63,52 @@ type StakePair<T = Balance> = {
|
|
|
role: T;
|
|
|
}
|
|
|
|
|
|
-interface IRoleAccounter {
|
|
|
- role_account: GenericAccountId;
|
|
|
- induction?: CuratorInduction;
|
|
|
- role_stake_profile?: Option<CuratorRoleStakeProfile>;
|
|
|
- reward_relationship: Option<RewardRelationshipId>;
|
|
|
-}
|
|
|
-
|
|
|
-type WGApiMethodType = 'nextOpeningId' | 'openingById' | 'nextApplicationId' | 'applicationById';
|
|
|
+type WGApiMethodType =
|
|
|
+ 'nextOpeningId'
|
|
|
+ | 'openingById'
|
|
|
+ | 'nextApplicationId'
|
|
|
+ | 'applicationById'
|
|
|
+ | 'nextWorkerId'
|
|
|
+ | 'workerById';
|
|
|
type WGApiMethodsMapping = { [key in WGApiMethodType]: string };
|
|
|
-type WGToApiMethodsMapping = { [key in WorkingGroups]: { module: string; methods: WGApiMethodsMapping } };
|
|
|
|
|
|
type GroupApplication = CuratorApplication | WorkerApplication;
|
|
|
type GroupApplicationId = CuratorApplicationId | WorkerApplicationId;
|
|
|
type GroupOpening = CuratorOpening | WorkerOpening;
|
|
|
type GroupOpeningId = CuratorOpeningId | WorkerOpeningId;
|
|
|
+type GroupWorker = Worker | Curator;
|
|
|
+type GroupWorkerId = CuratorId | WorkerId;
|
|
|
+type GroupWorkerStakeProfile = WorkerRoleStakeProfile | CuratorRoleStakeProfile;
|
|
|
+type GroupLead = Lead | LeadOf;
|
|
|
+type GroupLeadWithMemberId = {
|
|
|
+ lead: GroupLead;
|
|
|
+ memberId: MemberId;
|
|
|
+}
|
|
|
+
|
|
|
+type WGApiMapping = {
|
|
|
+ [key in WorkingGroups]: {
|
|
|
+ module: string;
|
|
|
+ methods: WGApiMethodsMapping;
|
|
|
+ openingType: Constructor<GroupOpening>;
|
|
|
+ applicationType: Constructor<GroupApplication>;
|
|
|
+ workerType: Constructor<GroupWorker>;
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
-const wgApiMethodsMapping: WGToApiMethodsMapping = {
|
|
|
+const workingGroupsApiMapping: WGApiMapping = {
|
|
|
[WorkingGroups.StorageProviders]: {
|
|
|
module: 'storageBureaucracy',
|
|
|
methods: {
|
|
|
nextOpeningId: 'nextWorkerOpeningId',
|
|
|
openingById: 'workerOpeningById',
|
|
|
nextApplicationId: 'nextWorkerApplicationId',
|
|
|
- applicationById: 'workerApplicationById'
|
|
|
- }
|
|
|
+ applicationById: 'workerApplicationById',
|
|
|
+ nextWorkerId: 'nextWorkerId',
|
|
|
+ workerById: 'workerById'
|
|
|
+ },
|
|
|
+ openingType: WorkerOpening,
|
|
|
+ applicationType: WorkerApplication,
|
|
|
+ workerType: Worker
|
|
|
},
|
|
|
[WorkingGroups.ContentCurators]: {
|
|
|
module: 'contentWorkingGroup',
|
|
@@ -90,8 +116,13 @@ const wgApiMethodsMapping: WGToApiMethodsMapping = {
|
|
|
nextOpeningId: 'nextCuratorOpeningId',
|
|
|
openingById: 'curatorOpeningById',
|
|
|
nextApplicationId: 'nextCuratorApplicationId',
|
|
|
- applicationById: 'curatorApplicationById'
|
|
|
- }
|
|
|
+ applicationById: 'curatorApplicationById',
|
|
|
+ nextWorkerId: 'nextCuratorId',
|
|
|
+ workerById: 'curatorById'
|
|
|
+ },
|
|
|
+ openingType: CuratorOpening,
|
|
|
+ applicationType: CuratorApplication,
|
|
|
+ workerType: Curator
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -108,8 +139,8 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
}
|
|
|
|
|
|
cachedApiMethodByGroup (group: WorkingGroups, method: WGApiMethodType) {
|
|
|
- const apiModule = wgApiMethodsMapping[group].module;
|
|
|
- const apiMethod = wgApiMethodsMapping[group].methods[method];
|
|
|
+ const apiModule = workingGroupsApiMapping[group].module;
|
|
|
+ const apiMethod = workingGroupsApiMapping[group].methods[method];
|
|
|
|
|
|
return this.cachedApi.query[apiModule][apiMethod];
|
|
|
}
|
|
@@ -133,24 +164,18 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
return stake.value.value;
|
|
|
}
|
|
|
|
|
|
- protected async curatorStake (stakeProfile: CuratorRoleStakeProfile): Promise<Balance> {
|
|
|
+ protected async workerStake (stakeProfile: GroupWorkerStakeProfile): Promise<Balance> {
|
|
|
return this.stakeValue(stakeProfile.stake_id);
|
|
|
}
|
|
|
|
|
|
- protected async curatorTotalReward (relationshipId: RewardRelationshipId): Promise<Balance> {
|
|
|
+ protected async workerTotalReward (relationshipId: RewardRelationshipId): Promise<Balance> {
|
|
|
const relationship = new SingleLinkedMapEntry<RewardRelationship>(
|
|
|
RewardRelationship,
|
|
|
await this.cachedApi.query.recurringRewards.rewardRelationships(
|
|
|
relationshipId
|
|
|
)
|
|
|
);
|
|
|
- const recipient = new SingleLinkedMapEntry<Recipient>(
|
|
|
- Recipient,
|
|
|
- await this.cachedApi.query.recurringRewards.rewardRelationships(
|
|
|
- relationship.value.recipient
|
|
|
- )
|
|
|
- );
|
|
|
- return recipient.value.total_reward_received;
|
|
|
+ return relationship.value.total_reward_received;
|
|
|
}
|
|
|
|
|
|
protected async memberIdFromRoleAndActorId (role: Role, id: ActorId): Promise<MemberId> {
|
|
@@ -180,9 +205,15 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- protected async groupMember (id: CuratorId, curator: IRoleAccounter): Promise<GroupMember> {
|
|
|
- const roleAccount = curator.role_account;
|
|
|
- const memberId = await this.memberIdFromCuratorId(id);
|
|
|
+ protected async groupMember (
|
|
|
+ group: WorkingGroups,
|
|
|
+ id: GroupWorkerId,
|
|
|
+ worker: GroupWorker
|
|
|
+ ): Promise<GroupMember> {
|
|
|
+ const roleAccount = worker.role_account;
|
|
|
+ const memberId = group === WorkingGroups.ContentCurators
|
|
|
+ ? await this.memberIdFromCuratorId(id)
|
|
|
+ : (worker as Worker).member_id;
|
|
|
|
|
|
const profile = await this.cachedApi.query.members.memberProfile(memberId) as Option<Profile>;
|
|
|
if (profile.isNone) {
|
|
@@ -190,41 +221,41 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
}
|
|
|
|
|
|
let stakeValue: Balance = new u128(0);
|
|
|
- if (curator.role_stake_profile && curator.role_stake_profile.isSome) {
|
|
|
- stakeValue = await this.curatorStake(curator.role_stake_profile.unwrap());
|
|
|
+ if (worker.role_stake_profile && worker.role_stake_profile.isSome) {
|
|
|
+ stakeValue = await this.workerStake(worker.role_stake_profile.unwrap());
|
|
|
}
|
|
|
|
|
|
let earnedValue: Balance = new u128(0);
|
|
|
- if (curator.reward_relationship && curator.reward_relationship.isSome) {
|
|
|
- earnedValue = await this.curatorTotalReward(curator.reward_relationship.unwrap());
|
|
|
+ if (worker.reward_relationship && worker.reward_relationship.isSome) {
|
|
|
+ earnedValue = await this.workerTotalReward(worker.reward_relationship.unwrap());
|
|
|
}
|
|
|
|
|
|
return ({
|
|
|
roleAccount,
|
|
|
memberId,
|
|
|
profile: profile.unwrap(),
|
|
|
- title: 'Content curator',
|
|
|
+ title: _.startCase(group).slice(0, -1), // FIXME: Temporary solution (just removes "s" at the end)
|
|
|
stake: stakeValue,
|
|
|
earned: earnedValue
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- protected async areAnyCuratorRolesOpen (): Promise<boolean> {
|
|
|
- const nextId = await this.cachedApi.query.contentWorkingGroup.nextCuratorOpeningId() as CuratorId;
|
|
|
+ protected async areAnyGroupRolesOpen (group: WorkingGroups): Promise<boolean> {
|
|
|
+ const nextId = await this.cachedApiMethodByGroup(group, 'nextOpeningId')() as GroupOpeningId;
|
|
|
|
|
|
// This is chain specfic, but if next id is still 0, it means no openings have been added yet
|
|
|
if (nextId.eq(0)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- const curatorOpenings = new MultipleLinkedMapEntry<CuratorOpeningId, CuratorOpening>(
|
|
|
- CuratorOpeningId,
|
|
|
- CuratorOpening,
|
|
|
- await this.cachedApi.query.contentWorkingGroup.curatorOpeningById()
|
|
|
+ const groupOpenings = new MultipleLinkedMapEntry<GroupOpeningId, GroupOpening>(
|
|
|
+ OpeningId,
|
|
|
+ workingGroupsApiMapping[group].openingType,
|
|
|
+ await this.cachedApiMethodByGroup(group, 'openingById')()
|
|
|
);
|
|
|
|
|
|
- for (let i = 0; i < curatorOpenings.linked_values.length; i++) {
|
|
|
- const opening = await this.opening(curatorOpenings.linked_values[i].opening_id.toNumber());
|
|
|
+ for (let i = 0; i < groupOpenings.linked_values.length; i++) {
|
|
|
+ const opening = await this.opening(groupOpenings.linked_values[i].opening_id.toNumber());
|
|
|
if (opening.is_active) {
|
|
|
return true;
|
|
|
}
|
|
@@ -233,30 +264,64 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- async groupLeadStatus (): Promise<GroupLeadStatus> {
|
|
|
+ protected async areAnyCuratorRolesOpen (): Promise<boolean> {
|
|
|
+ // Backward compatibility
|
|
|
+ return this.areAnyGroupRolesOpen(WorkingGroups.ContentCurators);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected async currentCuratorLead (): Promise<GroupLeadWithMemberId | null> {
|
|
|
const optLeadId = (await this.cachedApi.query.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
|
|
|
|
|
|
- if (optLeadId.isSome) {
|
|
|
- const leadId = optLeadId.unwrap();
|
|
|
- const lead = new SingleLinkedMapEntry<Lead>(
|
|
|
- Lead,
|
|
|
- await this.cachedApi.query.contentWorkingGroup.leadById(leadId)
|
|
|
- );
|
|
|
+ if (!optLeadId.isSome) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const leadId = optLeadId.unwrap();
|
|
|
+ const lead = new SingleLinkedMapEntry<Lead>(
|
|
|
+ Lead,
|
|
|
+ await this.cachedApi.query.contentWorkingGroup.leadById(leadId)
|
|
|
+ );
|
|
|
|
|
|
- const memberId = await this.memberIdFromLeadId(leadId);
|
|
|
+ const memberId = await this.memberIdFromLeadId(leadId);
|
|
|
+
|
|
|
+ return {
|
|
|
+ lead: lead.value,
|
|
|
+ memberId
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ protected async currentStorageLead (): Promise <GroupLeadWithMemberId | null> {
|
|
|
+ const optLead = (await this.cachedApi.query.storageBureaucracy.currentLead()) as Option<LeadOf>;
|
|
|
+
|
|
|
+ if (!optLead.isSome) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ lead: optLead.unwrap(),
|
|
|
+ memberId: optLead.unwrap().member_id
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ async groupLeadStatus (group: WorkingGroups = WorkingGroups.ContentCurators): Promise<GroupLeadStatus> {
|
|
|
+ const currentLead = group === WorkingGroups.ContentCurators
|
|
|
+ ? await this.currentCuratorLead()
|
|
|
+ : await this.currentStorageLead();
|
|
|
+
|
|
|
+ if (currentLead !== null) {
|
|
|
+ const profile = await this.cachedApi.query.members.memberProfile(currentLead.memberId) as Option<Profile>;
|
|
|
|
|
|
- const profile = await this.cachedApi.query.members.memberProfile(memberId) as Option<Profile>;
|
|
|
if (profile.isNone) {
|
|
|
- throw new Error('no profile found');
|
|
|
+ throw new Error(`${group} lead profile not found!`);
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
lead: {
|
|
|
- memberId,
|
|
|
- roleAccount: lead.value.role_account,
|
|
|
+ memberId: currentLead.memberId,
|
|
|
+ roleAccount: currentLead.lead.role_account_id,
|
|
|
profile: profile.unwrap(),
|
|
|
- title: 'Content Lead',
|
|
|
- stage: lead.value.stage
|
|
|
+ title: _.startCase(group) + ' Lead',
|
|
|
+ stage: group === WorkingGroups.ContentCurators ? (currentLead.lead as Lead).stage : undefined
|
|
|
},
|
|
|
loaded: true
|
|
|
};
|
|
@@ -267,10 +332,10 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async curationGroup (): Promise<WorkingGroupMembership> {
|
|
|
- const rolesAvailable = await this.areAnyCuratorRolesOpen();
|
|
|
+ async groupOverview (group: WorkingGroups): Promise<WorkingGroupMembership> {
|
|
|
+ const rolesAvailable = await this.areAnyGroupRolesOpen(group);
|
|
|
|
|
|
- const nextId = await this.cachedApi.query.contentWorkingGroup.nextCuratorId() as CuratorId;
|
|
|
+ const nextId = await this.cachedApiMethodByGroup(group, 'nextWorkerId')() as GroupWorkerId;
|
|
|
|
|
|
// This is chain specfic, but if next id is still 0, it means no curators have been added yet
|
|
|
if (nextId.eq(0)) {
|
|
@@ -280,27 +345,29 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- const values = new MultipleLinkedMapEntry<CuratorId, Curator>(
|
|
|
- CuratorId,
|
|
|
- Curator,
|
|
|
- await this.cachedApi.query.contentWorkingGroup.curatorById()
|
|
|
+ const values = new MultipleLinkedMapEntry<GroupWorkerId, GroupWorker>(
|
|
|
+ ActorId,
|
|
|
+ workingGroupsApiMapping[group].workerType,
|
|
|
+ await this.cachedApiMethodByGroup(group, 'workerById')() as GroupWorker
|
|
|
);
|
|
|
|
|
|
- const members = values.linked_values.filter(value => value.is_active).reverse();
|
|
|
- const memberIds = values.linked_keys.filter((v, k) => values.linked_values[k].is_active).reverse();
|
|
|
+ const workers = values.linked_values.filter(value => value.is_active).reverse();
|
|
|
+ const workerIds = values.linked_keys.filter((v, k) => values.linked_values[k].is_active).reverse();
|
|
|
|
|
|
return {
|
|
|
members: await Promise.all(
|
|
|
- members.map((member, k) => this.groupMember(memberIds[k], member))
|
|
|
+ workers.map((worker, k) => this.groupMember(group, workerIds[k], worker))
|
|
|
),
|
|
|
rolesAvailable
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- storageGroup (): Promise<StorageAndDistributionMembership> {
|
|
|
- return this.promise<StorageAndDistributionMembership>(
|
|
|
- {} as StorageAndDistributionMembership
|
|
|
- );
|
|
|
+ curationGroup (): Promise<WorkingGroupMembership> {
|
|
|
+ return this.groupOverview(WorkingGroups.ContentCurators);
|
|
|
+ }
|
|
|
+
|
|
|
+ storageGroup (): Promise<WorkingGroupMembership> {
|
|
|
+ return this.groupOverview(WorkingGroups.StorageProviders);
|
|
|
}
|
|
|
|
|
|
async opportunitiesByGroup (group: WorkingGroups): Promise<WorkingGroupOpening[]> {
|
|
@@ -344,7 +411,7 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
const nextAppid = (await this.cachedApiMethodByGroup(group, 'nextApplicationId')()) as GroupApplicationId;
|
|
|
for (let i = 0; i < nextAppid.toNumber(); i++) {
|
|
|
const cApplication = new SingleLinkedMapEntry<GroupApplication>(
|
|
|
- group === WorkingGroups.ContentCurators ? CuratorApplication : WorkerApplication,
|
|
|
+ workingGroupsApiMapping[group].applicationType,
|
|
|
await this.cachedApiMethodByGroup(group, 'applicationById')(i)
|
|
|
);
|
|
|
|
|
@@ -382,7 +449,7 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
}
|
|
|
|
|
|
const groupOpening = new SingleLinkedMapEntry<GroupOpening>(
|
|
|
- group === WorkingGroups.ContentCurators ? CuratorOpening : WorkerOpening,
|
|
|
+ workingGroupsApiMapping[group].openingType,
|
|
|
await this.cachedApiMethodByGroup(group, 'openingById')(id)
|
|
|
);
|
|
|
|
|
@@ -598,12 +665,12 @@ export class Transport extends TransportBase implements ITransport {
|
|
|
.map(async (curator, key) => {
|
|
|
let stakeValue: Balance = new u128(0);
|
|
|
if (curator.role_stake_profile && curator.role_stake_profile.isSome) {
|
|
|
- stakeValue = await this.curatorStake(curator.role_stake_profile.unwrap());
|
|
|
+ stakeValue = await this.workerStake(curator.role_stake_profile.unwrap());
|
|
|
}
|
|
|
|
|
|
let earnedValue: Balance = new u128(0);
|
|
|
if (curator.reward_relationship && curator.reward_relationship.isSome) {
|
|
|
- earnedValue = await this.curatorTotalReward(curator.reward_relationship.unwrap());
|
|
|
+ earnedValue = await this.workerTotalReward(curator.reward_relationship.unwrap());
|
|
|
}
|
|
|
|
|
|
return {
|