Closed petersalomonsen closed 1 year ago
Configuration for each community component should be stored to SocialDB ( no need for changes in DevHUB contract )
@petersalomonsen How do you plan to enable editing of that by other community moderators and DevHub moderators? Communities design on SocialDB is not finalized yet: https://github.com/near/near-discovery/discussions/342, so I would suggest to keep using DevHub's contract. It is not hard to add extra fields there.
@frol I was thinking that we could use the permission feature of SocialDB: https://github.com/NearSocial/social-db#permissions. I have never tried this myself, but from my understanding of the documentation this seems to allow granting permission for any public key.
But of course we can use DevHUB contract too, I was just assuming here that using SocialDB was more straightforward.
@petersalomonsen Since we don’t have community accounts, and we don’t want to store community-related keys under the community creator’s account namespace, it is easier to use DevHub contract for now.
P.S. permissions in SocialDB are not revokable, which is another problem to solve as list of moderators is dynamic.
@frol good point that permissions in SocialDB are not revokable. I've changed the description so that we'll use the DevHub contract for now.
Here is the current structure of a community in the neardevhub-contract:
pub struct CommunityFeatureFlags {
pub telegram: bool,
pub github: bool,
pub board: bool,
pub wiki: bool,
}
pub struct Community {
pub admins: Vec<AccountId>,
pub handle: CommunityHandle,
pub name: String,
pub tag: String,
pub description: String,
pub logo_url: String,
pub banner_url: String,
pub bio_markdown: Option<String>,
pub github_handle: Option<String>,
pub telegram_handle: Vec<String>,
pub twitter_handle: Option<String>,
pub website_url: Option<String>,
/// JSON string of github board configuration
pub github: Option<String>,
/// JSON string of kanban board configuration
pub board: Option<String>,
pub wiki1: Option<WikiPage>,
pub wiki2: Option<WikiPage>,
pub features: CommunityFeatureFlags,
}
These are most all L1 fields, they are not grouped by feature, and do not necessarily reflect the structure of the community page itself. This poses as a challenge when considering the ability to dynamically add tabs. Remember this structure while considering the code changes below.
Code changes for implementing this customizable feature would happen in the following files:
A configurator looks like this:
const CommunityWikiPageSchema = {
name: {
label: "Name",
order: 1,
},
content_markdown: {
format: "markdown",
label: "Content",
multiline: true,
order: 2,
},
};
{widget("components.organism.configurator", {
heading: "Wiki page 2",
data: state.communityData?.wiki2,
isSubform: true,
isUnlocked: permissions.can_configure,
onSubmit: (value) => sectionSubmit({ wiki2: value }),
submitLabel: "Accept",
schema: CommunityWikiPageSchema,
})}
Each configurator needs a header, destination in parent state, and a schema in order to dynamically create the form from it.
Currently, in order to show a tab, we check if the feature is enabled on the contract or if the fields necessary exist. Then, we need an icon, a title, and a route, which is a relative term for widgets within the same gigs-board directory. We can use the params object to pass props to the route.
const tabs = [
...(!community?.features.telegram ||
(community?.telegram_handle.length ?? 0) === 0
? []
: [
{
iconClass: "bi bi-telegram",
route: "community.telegram",
title: "Telegram",
},
]),
]
{tabs.map(({ defaultActive, params, route, title }) =>
title ? (
<li className="nav-item" key={title}>
<a
aria-current={defaultActive && "page"}
className={[
"d-inline-flex gap-2",
activeTabTitle === title ? "nav-link active" : "nav-link",
].join(" ")}
href={href(route, { handle, ...(params ?? {}) })}
>
<span>{title}</span>
</a>
</li>
) : null
)}
Since this new feature would be gated by a "upgrade community button", we can prepare the contract to more closely reflect the community UI. For example, a possible implementation would be:
pub struct CommunityFeature {
pub title: String,
pub description: String,
pub icon: String, // bootstrap icon
pub src: String, // widget source of custom component
pub schema: String, // JSON string of schema
pub parameters: String, // JSON string of specific parameters to be passed to component, follows the schema
pub enabled: bool // toggle to make tab visible or not, alternative to deleting
}
pub struct Community {
version: String, // introduce versioning
... existing fields
pub feature_list: Vec<CommunityFeature>
}
This would group the feature fields, replace the feature flags implementation, and make the contract more extendible/modular. Now, gigs-board.entity.community.configurator would be less hard coded, and more reflective of the data stored on the contract. The same goes for the tabs. We can migrate the existing Telegram, Github, Wiki features to fit this new pattern, and so they can be individually added or removed, too.
In summary:
1) Changes to the DevHub contract for tabs to reflect the features 2) Adding button to "upgrade community" to utilize adjusted contract 3) Hardcoding initial custom components and their schemas 4) Adding select button to initialize the configurator based on the schemas 5) Modifying configurator and header "organisms" to be dynamic based on features_list 6) Adding a new wrapper route that renders custom component and passes parameters 7) Swappable view on the wrapper route to configure the component 8) Button to delete/remove/disable a feature/tab 9) Migrating the existing Github/Telegram/Wiki features to utilize adjusted contract
@elliotBraem Thanks for putting it all together. The plan looks solid. The only thing that I don't understand is the role of schema
per CommunityFeature
as I would argue it is a property of the extension component to provide its schema (I would ignore this for now, so I would just move schema
field completely).
I would also suggest to migrate all the current hard-coded fields into a list of CommunityFeature
.
@elliotBraem / @petersalomonsen (and everyone else) – thank you all for all the great discussions.
A couple of updates from my side:
Discussed implementation with @Tguntenaar, taking into consideration the concerns and recommendations from @frol and @petersalomonsen.
Moved the CommunityFeature into a lookup map as recommended, to isolate feature-specific fields like src, schema, title, description, icon, etc. We need to keep the schema associated with the widget in order to create the configurator -- otherwise we can provide a custom configurator as is the case for Github and Telegram Widgets.
Here is a sketch of the overview:
Thomas will be implementing the contract changes and then I will be applying the front end changes. We can keep this as M
Looking good @elliotBraem @Tguntenaar !
I see the point of schema, to be able to use the components.organism.configurator
widget, it needs a schema as input.
Would only add though, that this "plugin management framework" would be slightly cleaner if it wasn't there. For example you could rather create a custom_configurator widget just like the wiki configurator you showed above:
const CommunityWikiPageSchema = {
name: {
label: "Name",
order: 1,
},
content_markdown: {
format: "markdown",
label: "Content",
multiline: true,
order: 2,
},
};
{widget("components.organism.configurator", {
heading: "Wiki page 2",
data: state.communityData?.wiki2,
isSubform: true,
isUnlocked: permissions.can_configure,
onSubmit: (value) => sectionSubmit({ wiki2: value }),
submitLabel: "Accept",
schema: CommunityWikiPageSchema,
})}
and so the framework would not have to include this logic. it would be a decision of the custom configurator to reuse a dynamic form component like the components.organism.configurator
.
I believe a bit simpler and cleaner, less to maintain, and also faster to implement, but just my opinion.
But otherwise I think it's looking good!
@petersalomonsen @Tguntenaar Yeah I think that makes sense. More modular as well.
Then following the custom configurator approach that @carina-akaia has already implemented:
{widget("entity.community.branding-configurator", {
isUnlocked: permissions.can_configure,
link,
onSubmit: sectionSubmit,
values: state.communityData,
})}
I think we should maintain that a configurator receives an onSubmit function and values object in props.
@frol @ori-near I've put this ticket in Review status for PR 61. I know the front end isn't ready yet, but we need the contract to be reviewed and merged in order to continue on the rest of the EPIC.
Is the contract deployed to another account? I guess it should be possible to review both frontend and contract together if deploying to a separate account. Probably better than having to introduce additional contract changes if you see that this is needed when working on the frontend.
@Tguntenaar I have reviewed the contract code and requested changes.
Product User Story
Problem Currently, community admins do not have a consistent and easy way to personalize their community experience. For example, the GitHub and Kanban components always display, and an admin must manage them within the tab and cannot remove them.
User Story As an admin, I need a consistent and intuitive method to add, configure, and remove add-ons for my community via the community settings page.
Acceptance Criteria
Old Technical Notes
Is your feature request related to a problem? Please describe. Currently a DevHUB community only allows built-in pages/components like Telegram and Github, limiting the potential for unique functionalities tailored to the needs of each community. Also the current way of adding Telegram and Github is by registering handles/URLs in the central configuration section, which does not give the experience of a "pluggable" component with its own configuration.
Describe the solution you'd like I would like Telegram, Github and BOS widgets in general to be components that can be added from the configuration page, and also have their own configuration sections. A user experience with a button for adding a "widget" like the following screenshot:
When clicking "Add widget" you should be able to select from a drop-down menu different alternatives of built-in (like Telegram and Github) as well of community provided widgets:
Finally when a custom widget like the "Music tracker" is added, there's a new configuration section in the community admin:
and a new tab with the widget in the community user page:
Example use cases
An example use case is creating a tab with custom activity feed, so let’s say you pick the Feed widget from the as a component to add, and then it has a configuration called filter, you out filter of tag let’s say blogs and the community name, so now you have a feed of blogs for this community. The tab is renamed to "Blog".
You could even add the feed widget again to another tab, and now with a different filter configuration. For example now you want all the posts from
dev-dao
, and so you configure the widget with the filterdev-dao
. Also you rename that tab with the nameDev DAO
.So now you have the Feed widget present twice, but with different settings.
Additional context