updates repo implementation (#523)

Co-authored-by: Mads Rasmussen <madsr@hey.com>
This commit is contained in:
Nathan Woulfe
2023-02-15 19:45:39 +10:00
committed by GitHub
parent 273714213c
commit 9f59c190c3
10 changed files with 390 additions and 161 deletions

View File

@@ -25,7 +25,7 @@ import { UmbMediaDetailStore } from './media/media/repository/media.detail.store
import { UmbMediaTreeStore } from './media/media/repository/media.tree.store';
import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store';
import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store';
import { UmbMemberGroupDetailStore } from './members/member-groups/member-group.detail.store';
import { UmbMemberGroupDetailStore } from './members/member-groups/repository/member-group.detail.store';
import { UmbMemberGroupTreeStore } from './members/member-groups/repository/member-group.tree.store';
import { UmbMemberDetailStore } from './members/members/member.detail.store';
import { UmbMemberTreeStore } from './members/members/repository/member.tree.store';

View File

@@ -1,59 +0,0 @@
import { Observable } from 'rxjs';
import { umbMemberGroupData } from '../../../core/mocks/data/member-group.data';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState, createObservablePart } from '@umbraco-cms/observable-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store';
export const UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupDetailStore>('UmbMemberGroupDetailStore');
/**
* @export
* @class UmbMemberGroupDetailStore
* @extends {UmbStoreBase}
* @description - Detail Data Store for Member Groups
*/
export class UmbMemberGroupDetailStore extends UmbStoreBase implements UmbEntityDetailStore<MemberGroupDetails> {
#data = new ArrayState<MemberGroupDetails>([], x => x.key);
public groups = this.#data.asObservable();
constructor(private host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN.toString());
}
getScaffold(entityType: string, parentKey: string | null) {
return {
} as MemberGroupDetails;
}
/**
* @description - Request a Member Group by key. The Member Group is added to the store and is returned as an Observable.
* @param {string} key
* @return {*} {(Observable<MemberGroupDetails>)}
* @memberof UmbMemberGroupDetailStore
*/
getByKey(key: string): Observable<MemberGroupDetails> {
// tryExecuteAndNotify(this.host, MemberGroupResource.getMemberGroupByKey({ key })).then(({ data }) => {
// if (data) {
// this.#data.appendOne(data);
// }
// });
// temp until Resource is updated
const group = umbMemberGroupData.getByKey(key);
if (group) {
this.#data.appendOne(group);
}
return createObservablePart(
this.#data,
(groups) => groups.find((group) => group.key === key) as MemberGroupDetails
);
}
async save(memberGroups: Array<MemberGroupDetails>): Promise<void> {
return null as any;
}
}

View File

@@ -0,0 +1,33 @@
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbStoreBase } from '@umbraco-cms/store';
/**
* @export
* @class UmbMemberGroupDetailStore
* @extends {UmbStoreBase}
* @description - Details Data Store for Member Groups
*/
export class UmbMemberGroupDetailStore
extends UmbStoreBase
{
#data = new ArrayState<MemberGroupDetails>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) {
super(host, UmbMemberGroupDetailStore.name);
}
append(memberGroup: MemberGroupDetails) {
this.#data.append([memberGroup]);
}
remove(uniques: string[]) {
this.#data.remove(uniques);
}
}
export const UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupDetailStore>(
UmbMemberGroupDetailStore.name
);

View File

@@ -1,51 +1,51 @@
import { MemberGroupTreeServerDataSource } from './sources/member-group.tree.server.data';
import { UmbMemberGroupTreeStore, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from './member-group.tree.store';
import { UmbMemberGroupDetailServerDataSource } from './sources/member-group.detail.server.data';
import { UmbMemberGroupDetailStore, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN } from './member-group.detail.store';
import { MemberGroupTreeServerDataSource } from './sources/member-group.tree.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification';
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import type { UmbTreeRepository } from '@umbraco-cms/repository';
import type { RepositoryTreeDataSource, UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/repository';
// TODO => Update type when backend updated
export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRepository<any> {
#init!: Promise<unknown>;
export class UmbMemberGroupRepository implements UmbTreeRepository {
#host: UmbControllerHostInterface;
#dataSource: MemberGroupTreeServerDataSource;
#treeSource: RepositoryTreeDataSource;
#treeStore?: UmbMemberGroupTreeStore;
#detailSource: UmbMemberGroupDetailServerDataSource;
#detailStore?: UmbMemberGroupDetailStore;
#notificationService?: UmbNotificationService;
#initResolver?: () => void;
#initialized = false;
constructor(host: UmbControllerHostInterface) {
this.#host = host;
// TODO: figure out how spin up get the correct data source
this.#dataSource = new MemberGroupTreeServerDataSource(this.#host);
this.#treeSource = new MemberGroupTreeServerDataSource(this.#host);
this.#detailSource = new UmbMemberGroupDetailServerDataSource(this.#host);
new UmbContextConsumerController(this.#host, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this.#treeStore = instance;
this.#checkIfInitialized();
});
new UmbContextConsumerController(this.#host, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
this.#detailStore = instance;
});
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance;
this.#checkIfInitialized();
});
}
#init = new Promise<void>((resolve) => {
this.#initialized ? resolve() : (this.#initResolver = resolve);
});
#checkIfInitialized() {
if (this.#treeStore && this.#notificationService) {
this.#initialized = true;
this.#initResolver?.();
}
});
}
async requestRootTreeItems() {
await this.#init;
const { data, error } = await this.#dataSource.getRootItems();
const { data, error } = await this.#treeSource.getRootItems();
if (data) {
this.#treeStore?.appendItems(data.items);
@@ -67,7 +67,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository {
return { data: undefined, error };
}
const { data, error } = await this.#dataSource.getItems(keys);
const { data, error } = await this.#treeSource.getItems(keys);
return { data, error };
}
@@ -87,8 +87,91 @@ export class UmbMemberGroupRepository implements UmbTreeRepository {
return this.#treeStore!.items(keys);
}
// DETAIL
async createDetailsScaffold() {
await this.#init;
return this.#detailSource.createScaffold();
}
async requestByKey(key: string) {
await this.#init;
// TODO: should we show a notification if the key is missing?
// Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice?
if (!key) {
const error: ProblemDetailsModel = { title: 'Key is missing' };
return { error };
}
const { data, error } = await this.#detailSource.get(key);
if (data) {
this.#detailStore?.append(data);
}
return { data, error };
}
async createDetail(detail: MemberGroupDetails) {
await this.#init;
if (!detail.name) {
const error: ProblemDetailsModel = { title: 'Name is missing' };
return { error };
}
const { data, error } = await this.#detailSource.insert(detail);
if (!error) {
const notification = { data: { message: `Member group '${detail.name}' created` } };
this.#notificationService?.peek('positive', notification);
}
return { data, error };
}
async saveDetail(memberGroup: MemberGroupDetails) {
await this.#init;
alert('implement save');
if (!memberGroup || !memberGroup.name) {
const error: ProblemDetailsModel = { title: 'Member group is missing' };
return { error };
}
const { error } = await this.#detailSource.update(memberGroup);
if (!error) {
const notification = { data: { message: `Member group '${memberGroup.name} saved`}};
this.#notificationService?.peek('positive', notification);
}
this.#detailStore?.append(memberGroup);
this.#treeStore?.updateItem(memberGroup.key, { name: memberGroup.name });
return { error };
}
async delete(key: string) {
await this.#init;
if (!key) {
const error: ProblemDetailsModel = { title: 'Key is missing' };
return { error };
}
const { error } = await this.#detailSource.delete(key);
if (!error) {
const notification = { data: { message: `Document deleted` } };
this.#notificationService?.peek('positive', notification);
}
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server.
// Consider notify a workspace if a template is deleted from the store while someone is editing it.
this.#detailStore?.remove([key]);
this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames.
return { error };
}
}

View File

@@ -1,7 +0,0 @@
import type { DataSourceResponse } from '@umbraco-cms/models';
import { EntityTreeItemModel, PagedEntityTreeItemModel } from '@umbraco-cms/backend-api';
export interface MemberGroupTreeDataSource {
getRootItems(): Promise<DataSourceResponse<PagedEntityTreeItemModel>>;
getItems(key: Array<string>): Promise<DataSourceResponse<EntityTreeItemModel[]>>;
}

View File

@@ -0,0 +1,129 @@
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { RepositoryDetailDataSource } from '@umbraco-cms/repository';
/**
* @description - A data source for the MemberGroup detail that fetches data from the server
* @export
* @class UmbMemberGroupDetailServerDataSource
* @implements {MemberGroupDetailDataSource}
*/
// TODO => Provide type when it is available
export class UmbMemberGroupDetailServerDataSource implements RepositoryDetailDataSource<any> {
#host: UmbControllerHostInterface;
constructor(host: UmbControllerHostInterface) {
this.#host = host;
}
/**
* @description - Creates a new MemberGroup scaffold
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async createScaffold() {
const data: MemberGroupDetails = {
name: '',
} as MemberGroupDetails;
return { data };
}
/**
* @description - Fetches a MemberGroup with the given key from the server
* @param {string} key
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
get(key: string) {
//return tryExecuteAndNotify(this.#host, MemberGroupResource.getMemberGroup({ key })) as any;
// TODO: use backend cli when available.
return tryExecuteAndNotify(this.#host, fetch(`/umbraco/management/api/v1/member-group/${key}`)) as any;
}
/**
* @description - Updates a MemberGroup on the server
* @param {MemberGroupDetails} memberGroup
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async update(memberGroup: MemberGroupDetails) {
if (!memberGroup.key) {
const error: ProblemDetailsModel = { title: 'Member Group key is missing' };
return { error };
}
const payload = { key: memberGroup.key, requestBody: memberGroup };
//return tryExecuteAndNotify(this.#host, MemberGroupResource.putMemberGroupByKey(payload));
// TODO: use backend cli when available.
return tryExecuteAndNotify(
this.#host,
fetch(`/umbraco/management/api/v1/member-group/${memberGroup.key}`, {
method: 'PUT',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json',
},
})
) as any;
}
/**
* @description - Inserts a new MemberGroup on the server
* @param {MemberGroupDetails} data
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async insert(data: MemberGroupDetails) {
const requestBody = {
name: data.name,
};
//return tryExecuteAndNotify(this.#host, MemberGroupResource.postMemberGroup({ requestBody }));
// TODO: use backend cli when available.
return tryExecuteAndNotify(
this.#host,
fetch(`/umbraco/management/api/v1/member-group/`, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json',
},
})
) as any;
}
/**
* @description - Deletes a MemberGroup on the server
* @param {string} key
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async trash(key: string) {
return this.delete(key);
}
/**
* @description - Deletes a MemberGroup on the server
* @param {string} key
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async delete(key: string) {
if (!key) {
const error: ProblemDetailsModel = { title: 'Key is missing' };
return { error };
}
//return await tryExecuteAndNotify(this.#host, MemberGroupResource.deleteMemberGroupByKey({ key }));
// TODO: use backend cli when available.
return tryExecuteAndNotify(
this.#host,
fetch(`/umbraco/management/api/v1/member-group/${key}`, {
method: 'DELETE',
})
) as any;
}
}

View File

@@ -1,6 +1,6 @@
import { MemberGroupTreeDataSource } from '.';
import { MemberGroupResource, ProblemDetailsModel } from '@umbraco-cms/backend-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { RepositoryTreeDataSource } from '@umbraco-cms/repository';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
/**
@@ -9,7 +9,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/resources';
* @class MemberGroupTreeServerDataSource
* @implements {MemberGroupTreeDataSource}
*/
export class MemberGroupTreeServerDataSource implements MemberGroupTreeDataSource {
export class MemberGroupTreeServerDataSource implements RepositoryTreeDataSource {
#host: UmbControllerHostInterface;
/**
@@ -22,31 +22,42 @@ export class MemberGroupTreeServerDataSource implements MemberGroupTreeDataSourc
}
/**
* Fetches the root items for the tree from the server
* @return {*}
* @memberof MemberGroupTreeServerDataSource
*/
async getRootItems() {
return tryExecuteAndNotify(this.#host, MemberGroupResource.getTreeMemberGroupRoot({}));
}
* Fetches the root items for the tree from the server
* @return {*}
* @memberof MemberGroupTreeServerDataSource
*/
async getRootItems() {
return tryExecuteAndNotify(this.#host, MemberGroupResource.getTreeMemberGroupRoot({}));
}
/**
* Fetches the items for the given keys from the server
* @param {Array<string>} keys
* @return {*}
* @memberof MemberGroupTreeServerDataSource
*/
async getItems(keys: Array<string>) {
if (keys) {
const error: ProblemDetailsModel = { title: 'Keys are missing' };
return { error };
}
/**
* Fetches the children of a given parent key from the server
* @param {(string | null)} parentKey
* @return {*}
* @memberof MemberGroupTreeServerDataSource
*/
async getChildrenOf(parentKey: string | null) {
// Not implemented for this tree
return {};
}
return tryExecuteAndNotify(
this.#host,
MemberGroupResource.getTreeMemberGroupItem({
key: keys,
})
);
}
/**
* Fetches the items for the given keys from the server
* @param {Array<string>} keys
* @return {*}
* @memberof MemberGroupTreeServerDataSource
*/
async getItems(keys: Array<string>) {
if (!keys || keys.length === 0) {
const error: ProblemDetailsModel = { title: 'Keys are missing' };
return { error };
}
return tryExecuteAndNotify(
this.#host,
MemberGroupResource.getTreeMemberGroupItem({
key: keys,
})
);
}
}

View File

@@ -1,33 +1,68 @@
import { UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN } from '../member-group.detail.store';
import { UmbWorkspaceEntityContextInterface } from '../../../../backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface';
import { UmbEntityWorkspaceManager } from '../../../../backoffice/shared/components/workspace/workspace-context/entity-manager-controller';
import { UmbWorkspaceContext } from '../../../../backoffice/shared/components/workspace/workspace-context/workspace-context';
import { UmbMemberGroupRepository } from '../repository/member-group.repository';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { ObjectState } from '@umbraco-cms/observable-api';
type EntityType = MemberGroupDetails;
export class UmbWorkspaceMemberGroupContext
extends UmbWorkspaceContext
implements UmbWorkspaceEntityContextInterface<MemberGroupDetails | undefined>
implements UmbWorkspaceEntityContextInterface<EntityType | undefined>
{
#manager = new UmbEntityWorkspaceManager(this._host, 'memberGroup', UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN);
#host: UmbControllerHostInterface;
#repo: UmbMemberGroupRepository;
public readonly data = this.#manager.state.asObservable();
public readonly name = this.#manager.state.getObservablePart((state) => state?.name);
#data = new ObjectState<EntityType | undefined>(undefined);
data = this.#data.asObservable();
name = this.#data.getObservablePart((data) => data?.name);
setPropertyValue(alias: string, value: string) {
return;
constructor(host: UmbControllerHostInterface) {
super(host);
this.#host = host;
this.#repo = new UmbMemberGroupRepository(this.#host);
}
getData() {
return this.#data.getValue();
}
getEntityKey() {
return this.getData()?.key || '';
}
getEntityType() {
return 'member-group';
}
setName(name: string) {
this.#manager.state.update({name});
this.#data.update({ name });
}
getEntityType = this.#manager.getEntityType;
getUnique = this.#manager.getEntityKey;
getEntityKey = this.#manager.getEntityKey;
getStore = this.#manager.getStore;
getData = this.#manager.getData;
load = this.#manager.load;
create = this.#manager.create;
save = this.#manager.save;
destroy = this.#manager.destroy;
setPropertyValue(alias: string, value: string) {
// Not implemented for this context - member groups have no properties
return;
}
async load(entityKey: string) {
const { data } = await this.#repo.requestByKey(entityKey);
if (data) {
this.#data.next(data);
}
}
async createScaffold() {
const { data } = await this.#repo.createDetailsScaffold();
if (!data) return;
this.#data.next(data);
}
async save() {
if (!this.#data.value) return;
this.#repo.saveDetail(this.#data.value);
}
public destroy(): void {
this.#data.complete();
}
}

View File

@@ -2,7 +2,6 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { distinctUntilChanged } from 'rxjs';
import { UmbWorkspaceEntityElement } from '../../../../backoffice/shared/components/workspace/workspace-entity-element.interface';
import { UmbWorkspaceMemberGroupContext } from './member-group-workspace.context';
import { UmbLitElement } from '@umbraco-cms/element';
@@ -29,23 +28,28 @@ export class UmbMemberGroupWorkspaceElement extends UmbLitElement implements Umb
}
`,
];
private _workspaceContext: UmbWorkspaceMemberGroupContext = new UmbWorkspaceMemberGroupContext(this);
public load(entityKey: string) {
this._workspaceContext.load(entityKey);
}
public create(parentKey: string | null) {
this._workspaceContext.create(parentKey);
}
@state()
_unique?: string;
@state()
private _memberGroupName = '';
#workspaceContext: UmbWorkspaceMemberGroupContext = new UmbWorkspaceMemberGroupContext(this);
constructor() {
super();
public load(entityKey: string) {
this.#workspaceContext.load(entityKey);
this._unique = entityKey;
}
this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (memberGroup) => {
public create() {
this.#workspaceContext.createScaffold();
}
async connectedCallback() {
super.connectedCallback();
this.observe(this.#workspaceContext.data, (memberGroup) => {
if (memberGroup && memberGroup.name !== this._memberGroupName) {
this._memberGroupName = memberGroup.name ?? '';
}
@@ -58,7 +62,7 @@ export class UmbMemberGroupWorkspaceElement extends UmbLitElement implements Umb
const target = event.composedPath()[0] as UUIInputElement;
if (typeof target?.value === 'string') {
this._workspaceContext.setName(target.value);
this.#workspaceContext.setName(target.value);
}
}
}
@@ -66,7 +70,7 @@ export class UmbMemberGroupWorkspaceElement extends UmbLitElement implements Umb
render() {
return html`
<umb-workspace-layout alias="Umb.Workspace.MemberGroup">
<uui-input id="header" slot="header" .value=${this._memberGroupName} @input="${this._handleInput}"></uui-input>
<uui-input id="header" slot="header" .value=${this._unique} @input="${this._handleInput}"></uui-input>
</umb-workspace-layout>
`;
}

View File

@@ -1,7 +1,6 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { distinctUntilChanged } from 'rxjs';
import { UmbWorkspaceMemberGroupContext } from '../../member-group-workspace.context';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { UmbLitElement } from '@umbraco-cms/element';
@@ -33,24 +32,25 @@ export class UmbWorkspaceViewMemberGroupInfoElement extends UmbLitElement {
];
@state()
_memberGroup?: MemberGroupDetails;
private _memberGroup?: MemberGroupDetails;
private _workspaceContext?: UmbWorkspaceMemberGroupContext;
#workspaceContext?: UmbWorkspaceMemberGroupContext;
constructor() {
super();
// TODO: Figure out if this is the best way to consume the context or if it can be strongly typed with an UmbContextToken
this.consumeContext<UmbWorkspaceMemberGroupContext>('umbWorkspaceContext', (memberGroupContext) => {
this._workspaceContext = memberGroupContext;
this._observeMemberGroup();
this.consumeContext<UmbWorkspaceMemberGroupContext>('umbWorkspaceContext', (instance) => {
this.#workspaceContext = instance;
console.log(instance);
this.#observeMemberGroup();
});
}
private _observeMemberGroup() {
if (!this._workspaceContext) return;
#observeMemberGroup() {
if (!this.#workspaceContext) return;
this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (memberGroup) => {
this.observe(this.#workspaceContext.data, (memberGroup) => {
if (!memberGroup) return;
// TODO: handle if model is not of the type wanted.