Skip to content

Commit

Permalink
[ui/authorization] feature: simple data browsing (peers)
Browse files Browse the repository at this point in the history
Co-authored-by: elf Pavlik <elf-pavlik@hackers4peace.net>
  • Loading branch information
samurex and elf-pavlik committed Oct 26, 2023
1 parent dc0eec0 commit b2bf526
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 73 deletions.
2 changes: 1 addition & 1 deletion packages/api-messages/src/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface DataRegistration extends UniqueId {
shapeTree: IRI;
// TODO dataOwner: IRI;
dataRegistry?: IRI;
count: number;
count?: number;
label?: string; // TODO label should be ensured
}

Expand Down
26 changes: 21 additions & 5 deletions packages/api-messages/src/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,44 @@ export class SocialAgentsRequest extends MessageBase {
export class AddSocialAgentRequest extends MessageBase {
public type = RequestMessageTypes.ADD_SOCIAL_AGENT_REQUEST;

constructor(public webId: IRI, public label: string, public note?: string) {
constructor(
public webId: IRI,
public label: string,
public note?: string
) {
super();
}
}

export class DataRegistriesRequest extends MessageBase {
public type = RequestMessageTypes.DATA_REGISTRIES_REQUEST;

constructor(private lang: string) {
constructor(
private agentId: string,
private lang: string
) {
super();
}
}

export class DescriptionsRequest extends MessageBase {
public type = RequestMessageTypes.DESCRIPTIONS_REQUEST;

constructor(private applicationId: IRI, private lang: string) {
constructor(
private applicationId: IRI,
private lang: string
) {
super();
}
}

export class ListDataInstancesRequest extends MessageBase {
public type = RequestMessageTypes.LIST_DATA_INSTANCES_REQUEST;

constructor(private registrationId: IRI) {
constructor(
private agentId: string,
private registrationId: IRI
) {
super();
}
}
Expand All @@ -81,7 +94,10 @@ export class ApplicationAuthorizationRequest extends MessageBase {
export class ResourceRequest extends MessageBase {
public type = RequestMessageTypes.RESOURCE_REQUEST;

constructor(public id: IRI, private lang: string) {
constructor(
public id: IRI,
private lang: string
) {
super();
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/data-model/src/readable/inheritable-data-grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ export abstract class InheritableDataGrant extends AbstractDataGrant {
get hasInheritingGrantIriList(): string[] {
return this.getSubjectsArray(INTEROP.inheritsFromGrant).map((subject) => subject.value);
}

// TODO: extract to a mixin
get dataRegistryIri(): string {
const dataRegistrationIri = this.getObject('hasDataRegistration').value;
return `${dataRegistrationIri.split('/').slice(0, -2).join('/')}/`;
}
}
6 changes: 6 additions & 0 deletions packages/data-model/src/readable/inherited-data-grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,10 @@ export class InheritedDataGrant extends AbstractDataGrant {
get canCreate(): boolean {
return this.accessMode.includes(ACL.Write.value);
}

// TODO: extract to a mixin
get dataRegistryIri(): string {
const dataRegistrationIri = this.getObject('hasDataRegistration').value;
return `${dataRegistrationIri.split('/').slice(0, -2).join('/')}/`;
}
}
4 changes: 2 additions & 2 deletions packages/service/src/handlers/api-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class ApiHandler extends HttpHandler {
return {
body: {
type: ResponseMessageTypes.DATA_REGISTRIES_RESPONSE,
payload: await getDataRegistries(context.saiSession, body.lang)
payload: await getDataRegistries(body.agentId, body.lang, context.saiSession)
},
status: 200,
headers: {}
Expand All @@ -101,7 +101,7 @@ export class ApiHandler extends HttpHandler {
return {
body: {
type: ResponseMessageTypes.LIST_DATA_INSTANCES_RESPONSE,
payload: await listDataInstances(body.registrationId, context.saiSession)
payload: await listDataInstances(body.agentId, body.registrationId, context.saiSession)
},
status: 200,
headers: {}
Expand Down
55 changes: 51 additions & 4 deletions packages/service/src/services/data-registries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CRUDDataRegistry } from '@janeirodigital/interop-data-model';
import type { CRUDDataRegistry, DataGrant } from '@janeirodigital/interop-data-model';
import type { AuthorizationAgent } from '@janeirodigital/interop-authorization-agent';
import type { DataRegistration, DataRegistry } from '@janeirodigital/sai-api-messages';

Expand All @@ -24,7 +24,54 @@ const buildDataRegistry = async (
};
};

export const getDataRegistries = async (saiSession: AuthorizationAgent, descriptionsLang: string) =>
Promise.all(
saiSession.registrySet.hasDataRegistry.map((registry) => buildDataRegistry(registry, descriptionsLang, saiSession))
const buildDataRegistryForGrant = async (
registryIri: string,
dataGrants: DataGrant[],
descriptionsLang: string,
saiSession: AuthorizationAgent
): Promise<DataRegistry> => {
const registrations: DataRegistration[] = [];
for (const dataGrant of dataGrants) {
// eslint-disable-next-line no-await-in-loop
const shapeTree = await saiSession.factory.readable.shapeTree(dataGrant.registeredShapeTree, descriptionsLang);
registrations.push({
id: dataGrant.hasDataRegistration,
shapeTree: dataGrant.registeredShapeTree,
dataRegistry: registryIri,
label: shapeTree.descriptions[descriptionsLang]?.label
});
}
return {
id: registryIri,
registrations
};
};

export const getDataRegistries = async (agentId: string, descriptionsLang: string, saiSession: AuthorizationAgent) => {
if (agentId === saiSession.webId) {
return Promise.all(
saiSession.registrySet.hasDataRegistry.map((registry) =>
buildDataRegistry(registry, descriptionsLang, saiSession)
)
);
}
const socialAgentRegistration = (await saiSession.findSocialAgentRegistration(agentId)).reciprocalRegistration;
if (!socialAgentRegistration) {
throw new Error(`missing social agent registration: ${agentId}`);
}
if (!socialAgentRegistration.accessGrant) {
throw new Error(`missing access grant for social agent: ${agentId}`);
}
const dataGrantIndex = socialAgentRegistration.accessGrant.hasDataGrant.reduce((acc, dataGrant) => {
if (!acc[dataGrant.dataRegistryIri]) {
acc[dataGrant.dataRegistryIri] = [] as DataGrant[];
}
acc[dataGrant.dataRegistryIri].push(dataGrant);
return acc;
}, {} as Record<string, DataGrant[]>);
return Promise.all(
Object.entries(dataGrantIndex).map(([registryIri, dataGrants]) =>
buildDataRegistryForGrant(registryIri, dataGrants, descriptionsLang, saiSession)
)
);
};
45 changes: 34 additions & 11 deletions packages/service/src/services/descriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import {
DataAuthorizationData,
InheritedDataGrant,
ReadableAccessNeed,
ReadableAccessNeedGroup,
ReadableDataRegistration,
SelectedFromRegistryDataGrant
ReadableAccessNeedGroup
} from '@janeirodigital/interop-data-model';
import type {
AuthorizationAgent,
Expand Down Expand Up @@ -158,19 +156,44 @@ export const getDescriptions = async (
};

export const listDataInstances = async (
agentId: string,
registrationId: string,
saiSession: AuthorizationAgent
): Promise<DataInstance[]> => {
const dataRegistration = await saiSession.factory.readable.dataRegistration(registrationId);
const dataInstances: DataInstance[] = [];
for (const dataInstanceIri of dataRegistration.contains) {
// eslint-disable-next-line no-await-in-loop
const dataInstance = await saiSession.factory.readable.dataInstance(dataInstanceIri);
dataInstances.push({
id: dataInstance.iri,
label: dataInstance.label
});
if (agentId === saiSession.webId) {
const dataRegistration = await saiSession.factory.readable.dataRegistration(registrationId);
for (const dataInstanceIri of dataRegistration.contains) {
// eslint-disable-next-line no-await-in-loop
const dataInstance = await saiSession.factory.readable.dataInstance(dataInstanceIri);
dataInstances.push({
id: dataInstance.iri,
label: dataInstance.label
});
}
} else {
const socialAgentRegistration = (await saiSession.findSocialAgentRegistration(agentId)).reciprocalRegistration;
if (!socialAgentRegistration) {
throw new Error(`missing social agent registration: ${agentId}`);
}
if (!socialAgentRegistration.accessGrant) {
throw new Error(`missing access grant for social agent: ${agentId}`);
}
for (const dataGrant of socialAgentRegistration.accessGrant.hasDataGrant) {
if (dataGrant.hasDataRegistration === registrationId) {
// TODO: optimize not to create crud data instances
// eslint-disable-next-line no-await-in-loop
for await (const instance of dataGrant.getDataInstanceIterator()) {
const dataInstance = await saiSession.factory.readable.dataInstance(instance.iri);
dataInstances.push({
id: dataInstance.iri,
label: dataInstance.label
});
}
}
}
}

return dataInstances;
};

Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"skipLibCheck": true,
"target": "ES2020",
"sourceMap": true,
"declarationMap": true,
"lib": ["ES2020", "DOM"]
}
}
8 changes: 4 additions & 4 deletions ui/authorization/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ async function getAuthorization(clientId: IRI, lang: string): Promise<Authorizat
return response.payload;
}

async function listDataInstances(registrationId: IRI): Promise<DataInstance[]> {
const request = new ListDataInstancesRequest(registrationId);
async function listDataInstances(agentId: IRI, registrationId: IRI): Promise<DataInstance[]> {
const request = new ListDataInstancesRequest(agentId, registrationId);
const data = await getDataFromApi<ListDataInstancesResponseMessage>(request);
const response = new ListDataInstancesResponse(data);
return response.payload;
Expand Down Expand Up @@ -120,8 +120,8 @@ async function listApplications(): Promise<Application[]> {
return response.payload;
}

async function listDataRegistires(lang: string): Promise<DataRegistry[]> {
const request = new DataRegistriesRequest(lang);
async function listDataRegistires(agentId: string, lang: string): Promise<DataRegistry[]> {
const request = new DataRegistriesRequest(agentId, lang);
const data = await getDataFromApi<DataRegistriesResponseMessage>(request);
const response = new DataRegistriesResponse(data);
return response.payload;
Expand Down
7 changes: 5 additions & 2 deletions ui/authorization/src/components/AuthorizeApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ span.label {
<v-list-item
class="ml-3"
v-for="child in accessNeed.children"
:key="child.id"
lines="three"
@click="toggleSelect(accessNeed.id, child.id)"
>
Expand Down Expand Up @@ -82,6 +83,7 @@ span.label {
<v-expansion-panels variant="popout">
<v-expansion-panel
v-for="agent in props.authorizationData.dataOwners"
:key="agent.id"
:disabled="topLevelScope !== 'some'"
>
<v-expansion-panel-title class="d-flex flex-row">
Expand Down Expand Up @@ -125,6 +127,7 @@ span.label {
<v-expansion-panels variant="popout">
<v-expansion-panel
v-for="registration in agent.dataRegistrations"
:key="registration.id"
:disabled="agentsIndex[agent.id].scope !== 'some'"
>
<v-expansion-panel-title class="d-flex flex-row">
Expand Down Expand Up @@ -273,7 +276,7 @@ function buildRegistrationsIndex(data: typeof props.authorizationData.dataOwners
id: registration.id,
agent: agent.id,
scope: 'all',
count: registration.count
count: registration.count!
};
}
}
Expand Down Expand Up @@ -356,7 +359,7 @@ function registrationScopeChanged(agentId: string, registrationId: string, scope
}
async function loadDataInstances(agentId: string, registrationId: string, selected: boolean): Promise<void> {
await appStore.listDataInstances(registrationId);
await appStore.listDataInstances(agentId, registrationId);
addDataInstancesToIndex(agentId, registrationId, appStore.loadedDataInstances[registrationId], selected);
}
Expand Down
12 changes: 6 additions & 6 deletions ui/authorization/src/store/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const useAppStore = defineStore('app', () => {
const application = ref<Partial<Application> | null>(null);
const loadedDataInstances = reactive<Record<string, DataInstance[]>>({});
const applicationList = reactive<Application[]>([]);
const dataRegistryList = reactive<DataRegistry[]>([]);
const dataRegistryList = reactive<Record<string, DataRegistry[]>>({});

const backend = useBackend();

Expand All @@ -42,8 +42,8 @@ export const useAppStore = defineStore('app', () => {
}

// TODO rename list with load
async function listDataInstances(registrationId: string) {
const dataInstances = await backend.listDataInstances(registrationId);
async function listDataInstances(agentId: string, registrationId: string) {
const dataInstances = await backend.listDataInstances(agentId, registrationId);
loadedDataInstances[registrationId] = [...dataInstances];
}

Expand All @@ -66,10 +66,10 @@ export const useAppStore = defineStore('app', () => {
}
}

async function listDataRegistries(lang = 'en') {
async function listDataRegistries(agentId: string, lang = 'en') {
if (!dataRegistryList.length) {
const dataRegistries = await backend.listDataRegistires(lang);
dataRegistryList.push(...dataRegistries);
const dataRegistries = await backend.listDataRegistires(agentId, lang);
dataRegistryList[agentId] = [...dataRegistries];
}
}

Expand Down
4 changes: 3 additions & 1 deletion ui/authorization/src/views/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<span>Peers</span>
</v-btn>

<v-btn :to="{name: 'data-registry-list'}">
<v-btn :to="{name: 'data-registry-list', query: {agent: coreStore.userId}}">
<v-icon>mdi-hexagon-multiple-outline</v-icon>

<span>Data</span>
Expand All @@ -24,4 +24,6 @@
</template>

<script lang="ts" setup>
import { useCoreStore } from '@/store/core';
const coreStore = useCoreStore()
</script>
Loading

0 comments on commit b2bf526

Please sign in to comment.