Skip to content
This repository has been archived by the owner on Mar 12, 2024. It is now read-only.

Commit

Permalink
Support review activity
Browse files Browse the repository at this point in the history
  • Loading branch information
kikakkz committed Dec 7, 2023
1 parent 3c86c0c commit c447780
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 8 deletions.
32 changes: 30 additions & 2 deletions activity/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
mod state;

use self::state::Activity;
use activity::{ActivityError, AnnounceParams, Message, Operation, VoteType};
use activity::{ActivityError, AnnounceParams, CreateParams, Message, Operation, VoteType};
use async_trait::async_trait;
use foundation::FoundationAbi;
use linera_sdk::{
Expand Down Expand Up @@ -101,7 +101,7 @@ impl Contract for Activity {
) -> Result<ExecutionResult<Self::Message>, Self::Error> {
match message {
Message::Create { params } => {
self.create_activity(context.authenticated_signer.unwrap(), params.clone())
self._create_activity(context.authenticated_signer.unwrap(), params.clone())
.await?;
let dest =
Destination::Subscribers(ChannelName::from(SUBSCRIPTION_CHANNEL.to_vec()));
Expand Down Expand Up @@ -134,6 +134,11 @@ impl Contract for Activity {
activity_id,
object_id,
} => {
match self.activity_approved(activity_id).await {
Ok(true) => {}
Ok(false) => return Err(ActivityError::ActivityNotApproved),
Err(err) => return Err(err),
}
match self.votable(activity_id).await {
Ok(true) => {}
Ok(false) => return Err(ActivityError::ActivityNotVotable),
Expand Down Expand Up @@ -252,4 +257,27 @@ impl Activity {
.await?;
Ok(resp)
}

async fn _create_activity(
&mut self,
owner: Owner,
params: CreateParams,
) -> Result<(), ActivityError> {
let activity_id = self.create_activity(owner, params.clone()).await?;
let call = review::ApplicationCall::SubmitActivity {
activity_id,
budget_amount: params.budget_amount,
};
self.call_application(true, Self::review_app_id()?, &call, vec![])
.await?;
Ok(())
}

async fn activity_approved(&mut self, activity_id: u64) -> Result<bool, ActivityError> {
let call = review::ApplicationCall::ActivityApproved { activity_id };
let (approved, _) = self
.call_application(true, Self::review_app_id()?, &call, vec![])
.await?;
Ok(approved)
}
}
3 changes: 3 additions & 0 deletions activity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ pub enum ActivityError {
#[error("Activity not votable")]
ActivityNotVotable,

#[error("Activity not approved")]
ActivityNotApproved,

#[error("Activity object not found")]
ActivityObjectNotFound,

Expand Down
7 changes: 4 additions & 3 deletions activity/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ impl Activity {
&mut self,
owner: Owner,
params: CreateParams,
) -> Result<(), ActivityError> {
) -> Result<u64, ActivityError> {
let activity_id = self.activity_id.get() + 1;
self.activity_id.set(activity_id);
Ok(self.activities.insert(
self.activities.insert(
&activity_id,
ActivityItem {
id: activity_id,
Expand Down Expand Up @@ -65,7 +65,8 @@ impl Activity {
winners: Vec::new(),
finalized: false,
},
)?)
)?;
Ok(activity_id)
}

pub(crate) async fn update_activity(
Expand Down
2 changes: 1 addition & 1 deletion deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ echo -e " Application ID: $BLUE$market_appid$NC"

print $'\U01F4AB' $YELLOW " Deploying Review application ..."
review_bid=`linera --with-wallet 0 publish-bytecode ./target/wasm32-unknown-unknown/release/review_{contract,service}.wasm`
review_appid=`linera --with-wallet 0 create-application $review_bid --json-argument '{"content_approved_threshold":3,"content_rejected_threshold":2,"asset_approved_threshold":2,"asset_rejected_threshold":2,"reviewer_approved_threshold":2,"reviewer_rejected_threshold":2}' --json-parameters "{\"feed_app_id\":\"$feed_appid\",\"credit_app_id\":\"$credit_appid\",\"foundation_app_id\":\"$foundation_appid\",\"market_app_id\":\"$market_appid\"}" --required-application-ids $feed_appid --required-application-ids $credit_appid --required-application-ids $foundation_appid --required-application-ids $market_appid`
review_appid=`linera --with-wallet 0 create-application $review_bid --json-argument '{"content_approved_threshold":3,"content_rejected_threshold":2,"asset_approved_threshold":2,"asset_rejected_threshold":2,"reviewer_approved_threshold":2,"reviewer_rejected_threshold":2,"activity_approved_threshold":2,"activity_rejected_threshold":2}' --json-parameters "{\"feed_app_id\":\"$feed_appid\",\"credit_app_id\":\"$credit_appid\",\"foundation_app_id\":\"$foundation_appid\",\"market_app_id\":\"$market_appid\"}" --required-application-ids $feed_appid --required-application-ids $credit_appid --required-application-ids $foundation_appid --required-application-ids $market_appid`
print $'\U01f499' $LIGHTGREEN " Review application deployed"
echo -e " Bytecode ID: $BLUE$review_bid$NC"
echo -e " Application ID: $BLUE$review_appid$NC"
Expand Down
6 changes: 6 additions & 0 deletions review/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,11 @@ impl Contract for Review {
);
Ok(result)
}
ApplicationCall::ActivityApproved { activity_id } => {
let mut result = ApplicationCallResult::default();
result.value = self.activity_approved(activity_id).await?;
Ok(result)
}
}
}

Expand Down Expand Up @@ -996,6 +1001,7 @@ impl Review {
}
self.reward_credits(owner, Amount::from_tokens(50)).await?;
self.reward_tokens().await?;
// TODO: here we should lock budget from foundation
Ok(())
}

Expand Down
7 changes: 6 additions & 1 deletion review/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl ContractAbi for ReviewAbi {
type ApplicationCall = ApplicationCall;
type SessionCall = ();
type SessionState = ();
type Response = ();
type Response = bool;
}

impl ServiceAbi for ReviewAbi {
Expand All @@ -39,6 +39,8 @@ pub struct InitialState {
pub asset_rejected_threshold: u16,
pub reviewer_approved_threshold: u16,
pub reviewer_rejected_threshold: u16,
pub activity_approved_threshold: u16,
pub activity_rejected_threshold: u16,
}

#[derive(Debug, Deserialize, Serialize, Clone, SimpleObject, Eq, PartialEq)]
Expand Down Expand Up @@ -242,4 +244,7 @@ pub enum ApplicationCall {
activity_id: u64,
budget_amount: Amount,
},
ActivityApproved {
activity_id: u64,
},
}
21 changes: 21 additions & 0 deletions review/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ impl Review {
.set(state.reviewer_approved_threshold);
self.reviewer_rejected_threshold
.set(state.reviewer_rejected_threshold);
self.activity_approved_threshold
.set(state.activity_approved_threshold);
self.activity_rejected_threshold
.set(state.activity_rejected_threshold);
Ok(())
}

Expand All @@ -57,6 +61,8 @@ impl Review {
asset_rejected_threshold: *self.asset_rejected_threshold.get(),
reviewer_approved_threshold: *self.reviewer_approved_threshold.get(),
reviewer_rejected_threshold: *self.reviewer_rejected_threshold.get(),
activity_approved_threshold: *self.activity_approved_threshold.get(),
activity_rejected_threshold: *self.activity_rejected_threshold.get(),
})
}

Expand Down Expand Up @@ -553,6 +559,21 @@ impl Review {
}
Ok(None)
}

pub(crate) async fn activity_approved(&self, activity_id: u64) -> Result<bool, StateError> {
match self.activity_applications.get(&activity_id).await {
Ok(Some(activity)) => {
let approved_threshold = *self.activity_approved_threshold.get();
let reviewer_number = *self.reviewer_number.get();
if activity.approved >= approved_threshold || activity.approved >= reviewer_number {
return Ok(true);
}
Ok(false)
}
Ok(None) => Err(StateError::InvalidActivity),
Err(err) => Err(StateError::ViewError(err)),
}
}
}

#[derive(Debug, Error)]
Expand Down
62 changes: 62 additions & 0 deletions webui/src/components/ActivityApplicationsKeysQuery.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup lang="ts">
import { provideApolloClient, useQuery } from '@vue/apollo-composable'
import { ApolloClient } from '@apollo/client/core'
import gql from 'graphql-tag'
import { getClientOptions } from 'src/apollo'
import { useBlockStore } from 'src/stores/block'
import { useReviewStore } from 'src/stores/review'
import { computed, onMounted, watch } from 'vue'
import { targetChain } from 'src/stores/chain'
import { useApplicationStore } from 'src/stores/application'
const block = useBlockStore()
const blockHeight = computed(() => block.blockHeight)
const review = useReviewStore()
const application = useApplicationStore()
const reviewApp = computed(() => application.reviewApp)
const options = /* await */ getClientOptions(/* {app, router ...} */)
const apolloClient = new ApolloClient(options)
const ready = () => {
return targetChain.value?.length > 0 && reviewApp.value?.length > 0
}
const getActivityApplicationsKeys = () => {
const { /* result, refetch, fetchMore, */ onResult /*, onError */ } = provideApolloClient(apolloClient)(() => useQuery(gql`
query getActivityApplicationsKeys {
activityApplicationsKeys
}
`, {
endpoint: 'review',
chainId: targetChain.value
}, {
fetchPolicy: 'network-only'
}))
onResult((res) => {
if (res.loading) return
review.activityApplicationsKeys = (res.data as Record<string, Array<number>>).activityApplicationsKeys
})
}
watch(blockHeight, () => {
if (!ready()) return
getActivityApplicationsKeys()
})
watch(reviewApp, () => {
if (!ready()) return
getActivityApplicationsKeys()
})
watch(targetChain, () => {
if (!ready()) return
getActivityApplicationsKeys()
})
onMounted(() => {
if (!ready()) return
getActivityApplicationsKeys()
})
</script>
87 changes: 87 additions & 0 deletions webui/src/components/ActivityApplicationsQuery.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { provideApolloClient, useQuery } from '@vue/apollo-composable'
import { ApolloClient } from '@apollo/client/core'
import gql from 'graphql-tag'
import { getClientOptions } from 'src/apollo'
import { useReviewStore, Activity } from 'src/stores/review'
import { computed, watch, ref, onMounted } from 'vue'
import { useBlockStore } from 'src/stores/block'
import { targetChain } from 'src/stores/chain'
const review = useReviewStore()
const activityApplicationsKeys = computed(() => review.activityApplicationsKeys)
const activityApplications = computed(() => review.activityApplications)
const activityMutateKeys = computed(() => review.activityMutateKeys)
const activityIndex = ref(-1)
const activityApplicationKey = computed(() => activityIndex.value >= 0 ? activityApplicationsKeys.value[activityIndex.value] : undefined)
const block = useBlockStore()
const blockHeight = computed(() => block.blockHeight)
const options = /* await */ getClientOptions(/* {app, router ...} */)
const apolloClient = new ApolloClient(options)
const getActivityApplication = (activityApplicationKey: number, done?: () => void) => {
const { result /*, fetchMore, onResult, onError */ } = provideApolloClient(apolloClient)(() => useQuery(gql`
query getActivityApplication($activityApplicationKey: Int!) {
activityApplications(u64: $activityApplicationKey) {
activityId
budgetAmount
reviewers
approved
rejected
createdAt
}
}
`, {
activityApplicationKey,
endpoint: 'review',
chainId: targetChain.value
}, {
fetchPolicy: 'network-only'
}))
watch(result, () => {
activityApplications.value.set(activityApplicationKey, (result.value as Record<string, Activity>).activityApplications)
done?.()
})
}
watch(activityApplicationKey, () => {
if (!activityApplicationKey.value) {
return
}
const index = activityMutateKeys.value.findIndex((el) => el === activityApplicationKey.value)
if (activityApplications.value.get(activityApplicationKey.value) && index < 0) {
activityIndex.value++
return
}
getActivityApplication(activityApplicationKey.value, () => {
activityIndex.value++
activityMutateKeys.value.splice(index, 1)
})
})
watch(activityApplicationsKeys, () => {
if (activityApplicationsKeys.value.length === 0) {
return
}
activityIndex.value = 0
})
watch(blockHeight, () => {
if (activityApplicationsKeys.value.length === 0) {
return
}
activityIndex.value = 0
})
onMounted(() => {
activityMutateKeys.value.forEach((activityKey) => {
getActivityApplication(activityKey)
})
review.activityMutateKeys = []
})
</script>
21 changes: 21 additions & 0 deletions webui/src/components/ActivityPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ import { useUserStore } from 'src/stores/user'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { Cookies, date } from 'quasar'
import { useReviewStore } from 'src/stores/review'
const review = useReviewStore()
const reviewerNumber = computed(() => review.reviewerNumber)
const approvedThreshold = computed(() => review.activityApprovedThreshold)
const activityApplications = computed(() => review.activityApplications)
const rejectedThreshold = computed(() => review.activityRejectedThreshold)
const user = useUserStore()
const account = computed(() => user.account)
Expand All @@ -48,6 +55,20 @@ const columns = computed(() => [
name: 'VoteStartAt',
label: 'Vote Start At',
field: (row: Activity) => date.formatDate(row.voteStartAt)
}, {
name: 'ReviewState',
label: 'Review State',
field: (row: Activity) => {
const approvers = approvedThreshold.value > reviewerNumber.value ? approvedThreshold.value : reviewerNumber.value
if ((activityApplications.value.get(row.id)?.approved || 0) > approvers) {
return 'Approved'
}
const rejecters = rejectedThreshold.value > reviewerNumber.value ? rejectedThreshold.value : reviewerNumber.value
if ((activityApplications.value.get(row.id)?.rejected || 0) > rejecters) {
return 'Rejected'
}
return `Reviewing (${activityApplications.value.get(row.id)?.approved || 0} approved, ${approvers} needed)`
}
}
])
Expand Down
4 changes: 4 additions & 0 deletions webui/src/components/ReviewStateQuery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const getReviewState = () => {
assetRejectedThreshold
reviewerApprovedThreshold
reviewerRejectedThreshold
activityApprovedThreshold
activityRejectedThreshold
reviewerNumber
}
`, {
Expand All @@ -52,6 +54,8 @@ const getReviewState = () => {
review.assetRejectedThreshold = ret.assetRejectedThreshold
review.reviewerApprovedThreshold = ret.reviewerApprovedThreshold
review.reviewerRejectedThreshold = ret.reviewerRejectedThreshold
review.activityApprovedThreshold = ret.activityApprovedThreshold
review.activityRejectedThreshold = ret.activityRejectedThreshold
review.reviewerNumber = ret.reviewerNumber
})
}
Expand Down

0 comments on commit c447780

Please sign in to comment.