Merge remote-tracking branch 'origin/main' into feature/user-permission-take-2

This commit is contained in:
Jacob Overgaard
2024-02-27 09:23:54 +01:00
31 changed files with 256 additions and 78 deletions

View File

@@ -76,6 +76,7 @@ export type { CreateTemplateRequestModel } from './models/CreateTemplateRequestM
export type { CreateUserGroupRequestModel } from './models/CreateUserGroupRequestModel';
export type { CreateUserRequestModel } from './models/CreateUserRequestModel';
export type { CreateUserResponseModel } from './models/CreateUserResponseModel';
export type { CreateWebhookRequestModel } from './models/CreateWebhookRequestModel';
export type { CultureAndScheduleRequestModel } from './models/CultureAndScheduleRequestModel';
export type { CultureReponseModel } from './models/CultureReponseModel';
export type { CurrentUserResponseModel } from './models/CurrentUserResponseModel';
@@ -401,6 +402,7 @@ export type { UpdateTemplateRequestModel } from './models/UpdateTemplateRequestM
export type { UpdateUserGroupRequestModel } from './models/UpdateUserGroupRequestModel';
export type { UpdateUserGroupsOnUserRequestModel } from './models/UpdateUserGroupsOnUserRequestModel';
export type { UpdateUserRequestModel } from './models/UpdateUserRequestModel';
export type { UpdateWebhookRequestModel } from './models/UpdateWebhookRequestModel';
export type { UpgradeSettingsResponseModel } from './models/UpgradeSettingsResponseModel';
export type { UserConfigurationResponseModel } from './models/UserConfigurationResponseModel';
export type { UserGroupBaseModel } from './models/UserGroupBaseModel';
@@ -424,6 +426,10 @@ export type { VariantModelBaseModel } from './models/VariantModelBaseModel';
export type { VariantResponseModelBaseModel } from './models/VariantResponseModelBaseModel';
export type { VerifyInviteUserRequestModel } from './models/VerifyInviteUserRequestModel';
export type { VerifyResetPasswordTokenRequestModel } from './models/VerifyResetPasswordTokenRequestModel';
export type { WebhookEventResponseModel } from './models/WebhookEventResponseModel';
export type { WebhookItemResponseModel } from './models/WebhookItemResponseModel';
export type { WebhookModelBaseModel } from './models/WebhookModelBaseModel';
export type { WebhookResponseModel } from './models/WebhookResponseModel';
export { AuditLogResource } from './services/AuditLogResource';
export { CultureResource } from './services/CultureResource';
@@ -470,3 +476,4 @@ export { TrackedReferenceResource } from './services/TrackedReferenceResource';
export { UpgradeResource } from './services/UpgradeResource';
export { UserResource } from './services/UserResource';
export { UserGroupResource } from './services/UserGroupResource';
export { WebhookResource } from './services/WebhookResource';

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WebhookModelBaseModel } from './WebhookModelBaseModel';
export type CreateWebhookRequestModel = (WebhookModelBaseModel & {
id?: string | null;
events: Array<string>;
});

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WebhookModelBaseModel } from './WebhookModelBaseModel';
export type UpdateWebhookRequestModel = (WebhookModelBaseModel & {
events: Array<string>;
});

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WebhookEventResponseModel = {
eventName: string;
eventType: string;
alias: string;
};

View File

@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WebhookItemResponseModel = {
enabled: boolean;
name: string;
events: string;
url: string;
types: string;
};

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WebhookModelBaseModel = {
enabled: boolean;
url: string;
contentTypeKeys: Array<string>;
headers: Record<string, string>;
};

View File

@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WebhookEventResponseModel } from './WebhookEventResponseModel';
import type { WebhookModelBaseModel } from './WebhookModelBaseModel';
export type WebhookResponseModel = (WebhookModelBaseModel & {
id: string;
events: Array<WebhookEventResponseModel>;
});

View File

@@ -227,7 +227,7 @@ export class DocumentResource {
}
/**
* @returns any Success
* @returns void
* @throws ApiError
*/
public static putDocumentByIdDomains({
@@ -236,7 +236,7 @@ export class DocumentResource {
}: {
id: string,
requestBody?: UpdateDomainsRequestModel,
}): CancelablePromise<any> {
}): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/document/{id}/domains',
@@ -246,7 +246,10 @@ export class DocumentResource {
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
409: `Conflict`,
},
});
}

View File

@@ -0,0 +1,132 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CreateWebhookRequestModel } from '../models/CreateWebhookRequestModel';
import type { UpdateWebhookRequestModel } from '../models/UpdateWebhookRequestModel';
import type { WebhookItemResponseModel } from '../models/WebhookItemResponseModel';
import type { WebhookResponseModel } from '../models/WebhookResponseModel';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class WebhookResource {
/**
* @returns string Created
* @throws ApiError
*/
public static postWebhook({
requestBody,
}: {
requestBody?: CreateWebhookRequestModel,
}): CancelablePromise<string> {
return __request(OpenAPI, {
method: 'POST',
url: '/umbraco/management/api/v1/webhook',
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Generated-Resource',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static getWebhookById({
id,
}: {
id: string,
}): CancelablePromise<WebhookResponseModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
'id': id,
},
errors: {
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static putWebhookById({
id,
requestBody,
}: {
id: string,
requestBody?: UpdateWebhookRequestModel,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
'id': id,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static deleteWebhookById({
id,
}: {
id: string,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
'id': id,
},
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static getWebhookItem({
ids,
}: {
ids?: Array<string>,
}): CancelablePromise<Array<WebhookItemResponseModel>> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/webhook/item',
query: {
'ids': ids,
},
errors: {
401: `The resource is protected and requires an authentication token`,
},
});
}
}

View File

@@ -20,6 +20,6 @@ export interface NumberRangeValueType {
max?: number;
}
export interface UmbReferenceById {
id: string;
export interface UmbReferenceByUnique {
unique: string;
}

View File

@@ -1,5 +1,5 @@
import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
@@ -23,11 +23,15 @@ export class UmbPropertyEditorUITextBoxElement extends UmbLitElement implements
@state()
private _maxChars?: number;
@state()
private _placeholder?: string;
@property({ attribute: false })
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
this._type = config?.getValueByAlias<UuiInputTypeType>('inputType') ?? this.#defaultType;
this._inputMode = config?.getValueByAlias('inputMode');
this._maxChars = config?.getValueByAlias('maxChars');
this._placeholder = config?.getValueByAlias('placeholder');
}
private onChange(e: Event) {
@@ -41,6 +45,7 @@ export class UmbPropertyEditorUITextBoxElement extends UmbLitElement implements
return html`<uui-input
.value=${this.value ?? ''}
.type=${this._type}
placeholder=${ifDefined(this._placeholder)}
inputMode=${ifDefined(this._inputMode)}
maxlength=${ifDefined(this._maxChars)}
@input=${this.onChange}></uui-input>`;

View File

@@ -21,7 +21,7 @@ export class UmbDocumentWorkspaceHasCollectionCondition extends UmbBaseControlle
this.observe(
context.contentTypeCollection,
(collection) => {
this.permitted = !!collection?.id;
this.permitted = !!collection?.unique;
this.#onChange();
},
'observeCollection',

View File

@@ -43,6 +43,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
template: null,
documentType: {
unique: '',
collection: null,
},
isTrashed: false,
values: [],
@@ -113,7 +114,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
template: data.template ? { unique: data.template.id } : null,
documentType: {
unique: data.documentType.id,
collection: data.documentType.collection ?? undefined,
collection: data.documentType.collection ? { unique: data.documentType.collection.id } : null,
},
isTrashed: data.isTrashed,
};

View File

@@ -40,7 +40,7 @@ const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => {
documentType: {
unique: item.documentType.id,
icon: item.documentType.icon,
collection: item.documentType.collection ?? undefined,
collection: item.documentType.collection ? { unique: item.documentType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,6 +1,6 @@
import type { UmbDocumentEntityType } from '../../entity.js';
import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbDocumentItemModel {
entityType: UmbDocumentEntityType;
@@ -11,7 +11,7 @@ export interface UmbDocumentItemModel {
documentType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbDocumentItemVariantModel>;
}

View File

@@ -55,7 +55,7 @@ const mapper = (item: DocumentTreeItemResponseModel): UmbDocumentTreeItemModel =
documentType: {
unique: item.documentType.id,
icon: item.documentType.icon,
collection: item.documentType.collection ?? undefined,
collection: item.documentType.collection ? { unique: item.documentType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,7 +1,7 @@
import type { UmbDocumentEntityType, UmbDocumentRootEntityType } from '../entity.js';
import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree';
import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbDocumentTreeItemModel extends UmbUniqueTreeItemModel {
entityType: UmbDocumentEntityType;
@@ -11,7 +11,7 @@ export interface UmbDocumentTreeItemModel extends UmbUniqueTreeItemModel {
documentType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbDocumentTreeItemVariantModel>;
}

View File

@@ -1,13 +1,13 @@
import type { UmbDocumentEntityType } from './entity.js';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import { DocumentVariantStateModel as UmbDocumentVariantState } from '@umbraco-cms/backoffice/external/backend-api';
export { UmbDocumentVariantState };
export interface UmbDocumentDetailModel {
documentType: {
unique: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
entityType: UmbDocumentEntityType;
isTrashed: boolean;

View File

@@ -79,6 +79,7 @@ export class UmbDocumentWorkspaceContext
this.#getDataPromise = this.repository.createScaffold(parentUnique, {
documentType: {
unique: documentTypeUnique,
collection: null,
},
});
const { data } = await this.#getDataPromise;

View File

@@ -21,7 +21,7 @@ export class UmbMediaWorkspaceHasCollectionCondition extends UmbBaseController i
this.observe(
context.contentTypeCollection,
(collection) => {
this.permitted = !!collection?.id;
this.permitted = !!collection?.unique;
this.#onChange();
},
'observeCollection',

View File

@@ -92,7 +92,7 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
urls: data.urls,
mediaType: {
unique: data.mediaType.id,
collection: data.mediaType.collection || null,
collection: data.mediaType.collection ? { unique: data.mediaType.collection.id } : null,
},
isTrashed: data.isTrashed,
};

View File

@@ -37,7 +37,7 @@ const mapper = (item: MediaItemResponseModel): UmbMediaItemModel => {
mediaType: {
unique: item.mediaType.id,
icon: item.mediaType.icon,
collection: item.mediaType.collection ?? undefined,
collection: item.mediaType.collection ? { unique: item.mediaType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,4 +1,4 @@
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbMediaItemModel {
unique: string;
@@ -6,7 +6,7 @@ export interface UmbMediaItemModel {
mediaType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbMediaItemVariantModel>;
name: string; // TODO: get correct variant name

View File

@@ -54,7 +54,7 @@ const mapper = (item: MediaTreeItemResponseModel): UmbMediaTreeItemModel => {
mediaType: {
unique: item.mediaType.id,
icon: item.mediaType.icon,
collection: item.mediaType.collection ?? undefined,
collection: item.mediaType.collection ? { unique: item.mediaType.collection.id } : null,
},
name: item.variants[0]?.name, // TODO: this is not correct. We need to get it from the variants. This is a temp solution.
variants: item.variants.map((variant) => {

View File

@@ -42,7 +42,7 @@ export class UmbMediaTreeStore extends UmbUniqueTreeStore {
hasChildren: false,
variants: item.variants,
isTrashed: item.isTrashed,
mediaType: { unique: item.mediaType.unique, icon: '' },
mediaType: { unique: item.mediaType.unique, icon: '', collection: null },
noAccess: false,
};

View File

@@ -1,5 +1,5 @@
import type { UmbMediaEntityType, UmbMediaRootEntityType } from '../entity.js';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree';
export interface UmbMediaTreeItemModel extends UmbUniqueTreeItemModel {
@@ -9,7 +9,7 @@ export interface UmbMediaTreeItemModel extends UmbUniqueTreeItemModel {
mediaType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbMediaTreeItemVariantModel>;
}

View File

@@ -1,12 +1,12 @@
import type { UmbMediaEntityType } from './entity.js';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type { MediaUrlInfoModel, MediaValueModel } from '@umbraco-cms/backoffice/external/backend-api';
export interface UmbMediaDetailModel {
mediaType: {
unique: string;
collection: UmbReferenceById | null;
collection: UmbReferenceByUnique | null;
};
entityType: UmbMediaEntityType;
isTrashed: boolean;

View File

@@ -36,7 +36,7 @@ const mapper = (item: MemberItemResponseModel): UmbMemberItemModel => {
memberType: {
unique: item.memberType.id,
icon: item.memberType.icon,
collection: item.memberType.collection ?? undefined,
collection: item.memberType.collection ? { unique: item.memberType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,11 +1,11 @@
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbMemberItemModel {
unique: string;
memberType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbMemberVariantItemModel>;
}

View File

@@ -1,7 +1,6 @@
import type { UmbUserGroupDetailModel } from '../index.js';
import { UMB_USER_GROUP_ENTITY_TYPE } from '../index.js';
import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from './user-group-workspace.context.js';
import type { UmbUserInputElement } from '@umbraco-cms/backoffice/user';
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit';
@@ -20,9 +19,6 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
@state()
private _userGroup?: UmbUserGroupDetailModel;
@state()
private _userUniques?: Array<string>;
#workspaceContext?: typeof UMB_USER_GROUP_WORKSPACE_CONTEXT.TYPE;
constructor() {
@@ -31,11 +27,6 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.observe(this.#workspaceContext.data, (userGroup) => (this._userGroup = userGroup), 'umbUserGroupObserver');
this.observe(
this.#workspaceContext.userUniques,
(userUniques) => (this._userUniques = userUniques),
'umbUserIdsObserver',
);
});
}
@@ -57,12 +48,6 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
this.#workspaceContext?.updateProperty('mediaStartNode', { unique: target.selectedIds[0] });
}
#onUsersChange(event: UmbChangeEvent) {
event.stopPropagation();
const target = event.target as UmbUserInputElement;
this.#workspaceContext?.updateUserKeys(target.selectedIds);
}
#onNameChange(event: UUIInputEvent) {
if (event instanceof UUIInputEvent) {
const target = event.composedPath()[0] as UUIInputElement;
@@ -152,15 +137,11 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
}
#renderRightColumn() {
return html`<uui-box>
<div slot="headline"><umb-localize key="sections_users"></umb-localize></div>
<umb-user-input @change=${this.#onUsersChange} .selectedIds=${this._userUniques ?? []}></umb-user-input>
</uui-box>
<uui-box headline="Actions">
<umb-entity-action-list
.entityType=${UMB_USER_GROUP_ENTITY_TYPE}
.unique=${this._userGroup?.unique}></umb-entity-action-list
></uui-box>`;
return html` <uui-box headline="Actions">
<umb-entity-action-list
.entityType=${UMB_USER_GROUP_ENTITY_TYPE}
.unique=${this._userGroup?.unique}></umb-entity-action-list
></uui-box>`;
}
static styles = [

View File

@@ -1,10 +1,9 @@
import { UmbUserGroupDetailRepository } from '../repository/detail/index.js';
import { UmbUserRepository } from '../../user/repository/user.repository.js';
import type { UmbUserGroupDetailModel } from '../types.js';
import type { UmbUserPermissionModel } from '@umbraco-cms/backoffice/user-permission';
import type { UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace';
import { UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace';
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
@@ -30,15 +29,8 @@ export class UmbUserGroupWorkspaceContext
readonly fallbackPermissions = this.#data.asObservablePart((data) => data?.fallbackPermissions || []);
readonly permissions = this.#data.asObservablePart((data) => data?.permissions || []);
#userUniques = new UmbArrayState<string>([], (x) => x);
userUniques = this.#userUniques.asObservable();
#userRepository: UmbUserRepository;
constructor(host: UmbControllerHost) {
super(host, 'Umb.Workspace.UserGroup');
this.#userRepository = new UmbUserRepository(host);
}
async create() {
@@ -78,18 +70,6 @@ export class UmbUserGroupWorkspaceContext
await this.repository.save(this.#data.value);
} else return;
//TODO: This next user-group section kinda works. But it will overwrite the entire user-group list on the user.
//TODO: instead we need to get all the users by their id's to get their user groups.
//TODO: these user-groups need to be updated together with the new user-group id.
//TODO: or the new user-group id needs to be removed from the existing list.
const userUniques = this.#userUniques.getValue();
const userGroupUniques = [this.#data.getValue()?.unique ?? ''];
if (userUniques.length > 0 && userGroupUniques.length > 0) {
await this.#userRepository.setUserGroups(userUniques, userGroupUniques);
}
// If it went well, then its not new anymore?.
this.setIsNew(false);
}
@@ -107,10 +87,6 @@ export class UmbUserGroupWorkspaceContext
this.#data.update({ [alias]: value });
}
updateUserKeys(keys: Array<string>) {
this.#userUniques.setValue(keys);
}
/**
* Gets the user group user permissions.
* @memberof UmbUserGroupWorkspaceContext