Implement org profile fallback

When an org doesn't have its own profile, but its owner does,
we fallback on that profile.

This allows for "user" orgs, where a user keeps his projects
anchored under an org, but all profile information under the user.
This commit is contained in:
Alexis Sellier
2021-07-21 16:39:58 +02:00
parent 07d2cf48eb
commit 2928e2fe14
8 changed files with 82 additions and 33 deletions
+3 -1
View File
@@ -66,7 +66,9 @@
<span class="badge">contract</span>
{:else if addressType === AddressType.EOA}
<a href={`/users/${address}`} target="_blank">{addressLabel}</a>
{:else if !noBadge}
{:else if profile?.name} <!-- While we're waiting to find out what address type it is -->
<a href={explorerLink(address, config)} target="_blank">{addressLabel}</a>
{:else}
<div class="loading"><Loading small /></div>
{/if}
</div>
+1 -1
View File
@@ -2,7 +2,7 @@
export let name: string;
export let width: number | null = null;
export let height: number | null = null;
export let inline: boolean = false;
export let inline = false;
const icons = [
{
+21
View File
@@ -3,6 +3,7 @@ import type { TransactionResponse } from '@ethersproject/providers';
import type { ContractReceipt } from '@ethersproject/contracts';
import { OperationType } from "@gnosis.pm/safe-core-sdk-types";
import { Profile } from '@app/profile';
import { assert } from '@app/error';
import * as utils from '@app/utils';
import type { Config } from '@app/config';
@@ -141,6 +142,10 @@ export class Org {
return projects;
}
async getProfile(config: Config): Promise<Profile> {
return Org.getProfile(this.address, config);
}
static async getAnchor(orgAddr: string, urn: string, config: Config): Promise<string | null> {
const org = new ethers.Contract(
orgAddr,
@@ -207,6 +212,22 @@ export class Org {
}
}
// Return only org profile if there is one, otherwise tries to get the profile
// of its owner.
static async getProfile(address: string, config: Config): Promise<Profile> {
const profile = await Profile.get(address, config);
if (profile.ens) {
return profile;
}
const org = await Org.get(address, config);
if (org) {
return Profile.get(org.owner, config);
}
return profile;
}
// Return only Orgs that have a specific user as owner
static async getOrgsByOwner(owner: string, config: Config): Promise<Org[]> {
const orgsResult = await utils.querySubgraph(config.orgs.subgraph, GetOrgsByOwner, { owners: [owner] });
+30 -20
View File
@@ -85,6 +85,10 @@
width: 64px;
height: 64px;
}
.title {
display: flex;
align-items: center;
}
.links {
display: flex;
align-items: center;
@@ -127,7 +131,7 @@
{:then org}
{#if org}
<main>
{#await Profile.get(address, config)}
{#await org.getProfile(config)}
<div class="centered">
<Loading center />
</div>
@@ -139,6 +143,9 @@
<div class="info">
<span class="title bold">
{parseEnsLabel(profile.name, config) ?? address}
{#if profile.address === org.owner}
<span class="badge">org</span>
{/if}
</span>
<div class="links">
{#if profile.url}
@@ -176,25 +183,28 @@
{/if}
</div>
<!-- Name -->
<div class="label">Name</div>
<div>
{#if profile.name}
<Link to={`/registrations/${parseEnsLabel(profile.name, config)}`}>{profile.name}</Link>
{:else}
<span class="subtle">Not set</span>
{/if}
</div>
<div>
{#await isAuthorized(org)}
<!-- Loading -->
{:then authorized}
{#if authorized}
<button class="tiny secondary" on:click={setName}>
Set
</button>
<!-- Only show the name if we aren't already using the name of the owner -->
{#if profile.address === org.address}
<div class="label">Name</div>
<div>
{#if profile.name}
<Link to={`/registrations/${parseEnsLabel(profile.name, config)}`}>{profile.name}</Link>
{:else}
<span class="subtle">Not set</span>
{/if}
{/await}
</div>
</div>
<div>
{#await isAuthorized(org)}
<!-- Loading -->
{:then authorized}
{#if authorized}
<button class="tiny secondary" on:click={setName}>
Set
</button>
{/if}
{/await}
</div>
{/if}
</div>
{#await org.getMembers(config)}
@@ -224,7 +234,7 @@
{:then projects}
{#each projects as project}
<div class="project">
<Project {project} org={org.address} {config} seed={profile.seed} />
<Project {project} org={org.address} config={profile.config(config)} />
</div>
{/each}
{:catch err}
+4 -6
View File
@@ -5,7 +5,7 @@
import Loading from '@app/Loading.svelte';
import Modal from '@app/Modal.svelte';
import Avatar from '@app/Avatar.svelte';
import { Profile } from '@app/profile';
import { Org } from '@app/base/orgs/Org';
import Browser from './Browser.svelte';
@@ -16,13 +16,11 @@
export let path: string;
let projectRoot = proj.path({ urn, org, commit });
let getProject = new Promise<string | undefined>(resolve => {
let getProject = new Promise<string | null>(resolve => {
if (org) {
Profile.get(org, config).then((p: Profile) => {
resolve(p.seed);
});
Org.getProfile(org, config).then(p => resolve(p?.seed || null));
} else {
resolve(undefined);
resolve(null);
}
}).then(async (seed) => {
const cfg = seed ? config.withSeed(seed) : config;
+1 -3
View File
@@ -16,15 +16,13 @@
export let project: proj.Project;
export let config: Config;
export let org: string | undefined;
export let seed: string | undefined;
let state: State = { status: Status.Loading };
let info: proj.Info | null = null;
onMount(async () => {
try {
const cfg = seed ? config.withSeed(seed) : config;
const result = await proj.getInfo(project.id, cfg);
const result = await proj.getInfo(project.id, config);
state = { status: Status.Loaded };
info = result;
} catch (err) {
+3 -2
View File
@@ -16,6 +16,7 @@
<style>
main {
padding: 5rem 0;
width: 36rem;
}
main > header {
display: flex;
@@ -66,7 +67,7 @@
</div>
<div class="info">
<span class="title bold">
<Address compact noAvatar noBadge {address} {config} resolve/>
<Address compact noAvatar noBadge {profile} {address} {config} resolve/>
</span>
<div class="links">
{#if profile.url}
@@ -93,7 +94,7 @@
{#await org.getProjects(config) then projects}
{#each projects as project}
<div class="project">
<Project {project} org={org.address} {config} seed={profile.seed} />
<Project {project} org={org.address} config={profile.config(config)} />
</div>
{/each}
{:catch err}
+19
View File
@@ -20,6 +20,16 @@ export class Profile {
this.address = address;
}
// Get the ENS profile
get ens(): Registration | null {
return this.profile.ens;
}
// Get the IDX profle
get idx(): BasicProfile | null {
return this.profile.idx;
}
// Using undefined as return type if nothing to be returned since it works better with <a href> links
get github(): string | undefined {
if (this.profile?.ens?.github) return parseUsername(this.profile.ens.github);
@@ -60,6 +70,15 @@ export class Profile {
return this.profile?.ens?.seedApi ?? undefined;
}
// Return the profile-specific config. This sets various URLs in the config,
// based on profile data.
config(config: Config): Config {
if (this.seed) {
return config.withSeed(this.seed);
}
return config;
}
// Keeping this function private since the desired entrypoint is .get()
private static async lookupAddress(address: string, config: Config): Promise<[IProfile, string]> {
const profile: IProfile = { ens: null, idx: null };