@memberjunction/core-entities 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom/UserViewEntity.d.ts +178 -0
- package/dist/custom/UserViewEntity.js +506 -0
- package/dist/custom/UserViewEntity.js.map +1 -0
- package/dist/generated/entity_subclasses.d.ts +5192 -0
- package/dist/generated/entity_subclasses.js +7636 -0
- package/dist/generated/entity_subclasses.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/package.json +25 -0
- package/readme.md +3 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { BaseInfo, EntityInfo, EntityFieldInfo, UserInfo, EntitySaveOptions } from "@memberjunction/core";
|
|
2
|
+
import { UserViewEntity } from "../generated/entity_subclasses";
|
|
3
|
+
export declare class UserViewEntityExtended extends UserViewEntity {
|
|
4
|
+
private _ViewEntityInfo;
|
|
5
|
+
/**
|
|
6
|
+
* This is a read-only property that returns the filters for this view. This information
|
|
7
|
+
* is persisted in a JSON format in the FilterState column of the UserViewEntity table. To access
|
|
8
|
+
* the filters easily, use this property.
|
|
9
|
+
* @readonly
|
|
10
|
+
* @type {ViewFilterInfo[]}
|
|
11
|
+
* @memberof UserViewEntitySubclass
|
|
12
|
+
*/
|
|
13
|
+
get Filter(): ViewFilterInfo[];
|
|
14
|
+
/**
|
|
15
|
+
* This is a read-only property that returns the columns for this view. This information
|
|
16
|
+
* is persisted in a JSON format in the GridState column of the UserViewEntity table. To access
|
|
17
|
+
* the columns easily, use this property.
|
|
18
|
+
*/
|
|
19
|
+
get Columns(): ViewColumnInfo[];
|
|
20
|
+
/**
|
|
21
|
+
* The entity info for the entity that this view is based on
|
|
22
|
+
* @readonly
|
|
23
|
+
* @type {EntityInfo}
|
|
24
|
+
* @memberof UserViewEntitySubclass
|
|
25
|
+
*/
|
|
26
|
+
get ViewEntityInfo(): EntityInfo;
|
|
27
|
+
get ViewSortInfo(): ViewSortInfo[];
|
|
28
|
+
get OrderByClause(): string;
|
|
29
|
+
LoadFromData(data: any): boolean;
|
|
30
|
+
Load(ID: number, EntityRelationshipsToLoad?: string[]): Promise<boolean>;
|
|
31
|
+
Save(options?: EntitySaveOptions): Promise<boolean>;
|
|
32
|
+
SetDefaultsFromEntity(e: EntityInfo): Promise<void>;
|
|
33
|
+
NewRecord(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* This method is used to update the view's where clause based on the following logic.
|
|
36
|
+
* 1) If the view has a regular Filter State (which is typically set by an end-user in a UI), the FilterState will be processed and a WHERE clause will be generated
|
|
37
|
+
* 2) If SmartFilterEnabled === 1 and the view has a SmartFilterPrompt, the SmartFilterPrompt will be processed by AI and the SmartFilterWhereClause will be generated. SmartFilterWhereClause will only be generated whenever the SmartFilterPrompt changes.
|
|
38
|
+
* 3) If CustomWhereClause === 1, this function will NOT modify the WhereClause because the sysadmin has set CustomWhereClause === 1 which means we don't want any changes to this particular view's WhereClause
|
|
39
|
+
* IMPORTANT NOTE: This method does not save the record. You still call .Save() to save the record as desired. If you want to get the new WhereClause based on the FilterState but not
|
|
40
|
+
* update the FilterState column, call the GenerateWhereClause() method.
|
|
41
|
+
* KEY ASSUMPTION: The server code must set a property in the MJGlobal.Properties array with a key of OPENAI_API_KEY to use the AI functionality. If this property is not set, the AI functionality will not work.
|
|
42
|
+
*/
|
|
43
|
+
UpdateWhereClause(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* This method will use AI to return a valid WHERE clause based on the provided prompt. This is automatically called at the right time if the view has SmartFilterEnabled turned on and the SmartFilterPrompt is set. If you want
|
|
46
|
+
* to call this directly to get back a WHERE clause for other purposes you can call this method directly and provide both a prompt and the entity that the view is based on.
|
|
47
|
+
* @param prompt
|
|
48
|
+
*/
|
|
49
|
+
GenerateSmartFilterWhereClause(prompt: string, entityInfo: EntityInfo): Promise<string>;
|
|
50
|
+
Set(FieldName: string, Value: any): void;
|
|
51
|
+
/**
|
|
52
|
+
* Create a where clause for SQL from the structured filter state JSON information
|
|
53
|
+
* @param FilterState A string containing a valid Filter State JSON string - this uses the format that the Kendo Filter component uses which is generic and can be used anywhere
|
|
54
|
+
* with/without kendo
|
|
55
|
+
* @param EntityInfo The entity info for the entity that the UserView is based on
|
|
56
|
+
* @returns a string that represents a valid SQL WHERE clause
|
|
57
|
+
* @memberof UserViewEntitySubclass
|
|
58
|
+
* @example Example Filter State JSON below
|
|
59
|
+
FilterState = `{
|
|
60
|
+
"logic": "or",
|
|
61
|
+
"filters": [{
|
|
62
|
+
"field": "Name",
|
|
63
|
+
"operator": "startswith",
|
|
64
|
+
"value": "A"
|
|
65
|
+
}, {
|
|
66
|
+
"logic": "or",
|
|
67
|
+
"filters": [{
|
|
68
|
+
"field": "TotalRevenue",
|
|
69
|
+
"operator": "gt",
|
|
70
|
+
"value": 10000000
|
|
71
|
+
}, {
|
|
72
|
+
"field": "NumberEmployees",
|
|
73
|
+
"operator": "gte",
|
|
74
|
+
"value": 25
|
|
75
|
+
}, {
|
|
76
|
+
"field": "InformationTechnologyExpense",
|
|
77
|
+
"operator": "gte",
|
|
78
|
+
"value": 500000
|
|
79
|
+
}, {
|
|
80
|
+
"logic": "and",
|
|
81
|
+
"filters": [{
|
|
82
|
+
"field": "City",
|
|
83
|
+
"operator": "eq",
|
|
84
|
+
"value": "Chicago"
|
|
85
|
+
}, {
|
|
86
|
+
"field": "ActivityCount",
|
|
87
|
+
"operator": "gte",
|
|
88
|
+
"value": 5
|
|
89
|
+
}]
|
|
90
|
+
}]
|
|
91
|
+
}, {
|
|
92
|
+
"field": "LatestActivityDate",
|
|
93
|
+
"operator": "gte",
|
|
94
|
+
"value": "2023-01-01T06:00:00.000Z"
|
|
95
|
+
}]
|
|
96
|
+
}`;
|
|
97
|
+
*/
|
|
98
|
+
protected GenerateWhereClause(FilterState: string, entityInfo: EntityInfo): string;
|
|
99
|
+
private wrapQuotes;
|
|
100
|
+
private convertFilterToSQL;
|
|
101
|
+
private processFilterGroup;
|
|
102
|
+
}
|
|
103
|
+
export declare class ViewInfo {
|
|
104
|
+
/**
|
|
105
|
+
* Returns a list of views for the specified user. If no user is specified, the current user is used.
|
|
106
|
+
* @param contextUser optional, the user to use for context when loading the view
|
|
107
|
+
* @param entityId optional, the entity ID to filter the views by, if not provided, there is no filter on EntityID and all views for the user are returned
|
|
108
|
+
* @returns an array of UserViewEntityBase objects
|
|
109
|
+
* @memberof ViewInfo
|
|
110
|
+
* @static
|
|
111
|
+
* @async
|
|
112
|
+
*/
|
|
113
|
+
static GetViewsForUser(entityId?: number, contextUser?: UserInfo): Promise<UserViewEntityExtended[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Returns a view ID for a given viewName
|
|
116
|
+
* @param viewName Name of the view to lookup the ID for
|
|
117
|
+
* @returns the ID of the User View record that matches the provided name, if found
|
|
118
|
+
*/
|
|
119
|
+
static GetViewID(viewName: string): Promise<number>;
|
|
120
|
+
/**
|
|
121
|
+
* Loads a new entity object for User Views for the specified viewId and returns it if successful.
|
|
122
|
+
* @param viewId record ID for the view to load
|
|
123
|
+
* @param contextUser optional, the user to use for context when loading the view
|
|
124
|
+
* @returns UserViewEntityBase (or a subclass of it)
|
|
125
|
+
* @throws Error if the view cannot be loaded
|
|
126
|
+
* @memberof ViewInfo
|
|
127
|
+
* @static
|
|
128
|
+
* @async
|
|
129
|
+
*/
|
|
130
|
+
static GetViewEntity(viewId: number, contextUser?: UserInfo): Promise<UserViewEntityExtended>;
|
|
131
|
+
/**
|
|
132
|
+
* Loads a new entity object for User Views for the specified viewName and returns it if successful.
|
|
133
|
+
* @param viewName name for the view to load
|
|
134
|
+
* @param contextUser optional, the user to use for context when loading the view
|
|
135
|
+
* @returns UserViewEntityBase (or a subclass of it)
|
|
136
|
+
* @throws Error if the view cannot be loaded
|
|
137
|
+
* @memberof ViewInfo
|
|
138
|
+
* @static
|
|
139
|
+
* @async
|
|
140
|
+
*/
|
|
141
|
+
static GetViewEntityByName(viewName: string, contextUser?: UserInfo): Promise<UserViewEntityExtended>;
|
|
142
|
+
}
|
|
143
|
+
export declare class ViewColumnInfo extends BaseInfo {
|
|
144
|
+
ID: number;
|
|
145
|
+
Name: string;
|
|
146
|
+
DisplayName: string;
|
|
147
|
+
hidden: boolean;
|
|
148
|
+
width?: number;
|
|
149
|
+
orderIndex?: number;
|
|
150
|
+
EntityField: EntityFieldInfo;
|
|
151
|
+
constructor(initData?: any);
|
|
152
|
+
}
|
|
153
|
+
export declare enum ViewFilterLogicInfo {
|
|
154
|
+
And = 1,
|
|
155
|
+
Or = 2
|
|
156
|
+
}
|
|
157
|
+
export declare class ViewFilterInfo extends BaseInfo {
|
|
158
|
+
logicOperator: ViewFilterLogicInfo;
|
|
159
|
+
field: string;
|
|
160
|
+
operator: string;
|
|
161
|
+
value: string;
|
|
162
|
+
filters: ViewFilterInfo[];
|
|
163
|
+
constructor(initData?: any);
|
|
164
|
+
}
|
|
165
|
+
export declare enum ViewSortDirectionInfo {
|
|
166
|
+
Asc = 1,
|
|
167
|
+
Desc = 2
|
|
168
|
+
}
|
|
169
|
+
export declare class ViewSortInfo extends BaseInfo {
|
|
170
|
+
field: string;
|
|
171
|
+
direction: ViewSortDirectionInfo;
|
|
172
|
+
constructor(initData?: any);
|
|
173
|
+
}
|
|
174
|
+
export declare class ViewGridState {
|
|
175
|
+
sortSettings?: any;
|
|
176
|
+
columnSettings?: any;
|
|
177
|
+
filter?: any;
|
|
178
|
+
}
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ViewGridState = exports.ViewSortInfo = exports.ViewSortDirectionInfo = exports.ViewFilterInfo = exports.ViewFilterLogicInfo = exports.ViewColumnInfo = exports.ViewInfo = exports.UserViewEntityExtended = void 0;
|
|
10
|
+
const global_1 = require("@memberjunction/global");
|
|
11
|
+
const core_1 = require("@memberjunction/core");
|
|
12
|
+
const entity_subclasses_1 = require("../generated/entity_subclasses");
|
|
13
|
+
let UserViewEntityExtended = class UserViewEntityExtended extends entity_subclasses_1.UserViewEntity {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this._ViewEntityInfo = null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* This is a read-only property that returns the filters for this view. This information
|
|
20
|
+
* is persisted in a JSON format in the FilterState column of the UserViewEntity table. To access
|
|
21
|
+
* the filters easily, use this property.
|
|
22
|
+
* @readonly
|
|
23
|
+
* @type {ViewFilterInfo[]}
|
|
24
|
+
* @memberof UserViewEntitySubclass
|
|
25
|
+
*/
|
|
26
|
+
get Filter() {
|
|
27
|
+
if (this.FilterState) {
|
|
28
|
+
return [new ViewFilterInfo(JSON.parse(this.FilterState))];
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* This is a read-only property that returns the columns for this view. This information
|
|
35
|
+
* is persisted in a JSON format in the GridState column of the UserViewEntity table. To access
|
|
36
|
+
* the columns easily, use this property.
|
|
37
|
+
*/
|
|
38
|
+
get Columns() {
|
|
39
|
+
// now, we need to do some post-processing once we've loaded the raw data so that our
|
|
40
|
+
// columns and filters are set up correctly
|
|
41
|
+
if (this.GridState) {
|
|
42
|
+
const gridState = JSON.parse(this.GridState);
|
|
43
|
+
if (gridState && gridState.columnSettings) {
|
|
44
|
+
const columns = gridState.columnSettings.map(c => {
|
|
45
|
+
// find the entity field and put it in place inside the View Metadata for easy access
|
|
46
|
+
if (c) {
|
|
47
|
+
// check to make sure the current item is non-null to ensure metadata isn't messed up
|
|
48
|
+
const field = this._ViewEntityInfo.Fields.find(f => f.Name.trim().toLowerCase() == c.Name.trim().toLowerCase());
|
|
49
|
+
return new ViewColumnInfo({ ...c, EntityField: field });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log('null column setting found in view grid state for columns - ViewID: ' + this.ID);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return columns;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// if we get here, we don't have column info, return an empty array
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The entity info for the entity that this view is based on
|
|
63
|
+
* @readonly
|
|
64
|
+
* @type {EntityInfo}
|
|
65
|
+
* @memberof UserViewEntitySubclass
|
|
66
|
+
*/
|
|
67
|
+
get ViewEntityInfo() {
|
|
68
|
+
return this._ViewEntityInfo;
|
|
69
|
+
}
|
|
70
|
+
get ViewSortInfo() {
|
|
71
|
+
if (this.SortState) {
|
|
72
|
+
const sortTemp = JSON.parse(this.SortState);
|
|
73
|
+
if (sortTemp && sortTemp.length > 0)
|
|
74
|
+
return sortTemp.map(s => new ViewSortInfo(s));
|
|
75
|
+
}
|
|
76
|
+
// if we get here return a blank array
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
get OrderByClause() {
|
|
80
|
+
if (this.ViewSortInfo && this.ViewSortInfo.length > 0) {
|
|
81
|
+
return this.ViewSortInfo.map(s => s.field + (s.direction === ViewSortDirectionInfo.Asc ? '' : ' DESC')).join(', ');
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
LoadFromData(data) {
|
|
87
|
+
// in this case we need to make sure we ge the _ViewEntityInfo property set up correctly
|
|
88
|
+
if (data && data.EntityID) {
|
|
89
|
+
const md = new core_1.Metadata();
|
|
90
|
+
const match = md.Entities.find(e => e.ID == data.EntityID);
|
|
91
|
+
if (match) {
|
|
92
|
+
this._ViewEntityInfo = match;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw new Error('Unable to find entity info for entity ID: ' + data.EntityID);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return super.LoadFromData(data);
|
|
99
|
+
}
|
|
100
|
+
async Load(ID, EntityRelationshipsToLoad) {
|
|
101
|
+
// first load up the view info, use the superclass to do this
|
|
102
|
+
const result = await super.Load(ID, EntityRelationshipsToLoad);
|
|
103
|
+
if (result) {
|
|
104
|
+
const md = new core_1.Metadata();
|
|
105
|
+
// first, cache a copy of the entity info for the entity that is used in this view
|
|
106
|
+
const match = md.Entities.find(e => e.ID == this.EntityID);
|
|
107
|
+
if (match)
|
|
108
|
+
this._ViewEntityInfo = match;
|
|
109
|
+
else
|
|
110
|
+
throw new Error('Unable to find entity info for entity ID: ' + this.EntityID);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
async Save(options) {
|
|
115
|
+
// we want to preprocess the Save() call because we need to regenerate the WhereClause in some situations
|
|
116
|
+
if (options?.IgnoreDirtyState ||
|
|
117
|
+
this.Fields.find(c => c.Name.toLowerCase() == 'filterstate')?.Dirty ||
|
|
118
|
+
this.Fields.find(c => c.Name.toLowerCase() == 'smartfilterenabled')?.Dirty ||
|
|
119
|
+
this.Fields.find(c => c.Name.toLowerCase() == 'smartfilterprompt')?.Dirty) {
|
|
120
|
+
// either we're ignoring dirty state or the filter state is dirty, so we need to update the where clause
|
|
121
|
+
await this.UpdateWhereClause();
|
|
122
|
+
}
|
|
123
|
+
// now just call our superclass to do the actual save()
|
|
124
|
+
return super.Save(options);
|
|
125
|
+
}
|
|
126
|
+
async SetDefaultsFromEntity(e) {
|
|
127
|
+
this.EntityID = e.ID;
|
|
128
|
+
const newGridState = new ViewGridState();
|
|
129
|
+
newGridState.columnSettings = [];
|
|
130
|
+
e.Fields.filter(f => f.DefaultInView).forEach(f => {
|
|
131
|
+
newGridState.columnSettings.push({
|
|
132
|
+
ID: f.ID,
|
|
133
|
+
DisplayName: f.DisplayName,
|
|
134
|
+
Name: f.Name,
|
|
135
|
+
hidden: false,
|
|
136
|
+
width: f.DefaultColumnWidth,
|
|
137
|
+
orderIndex: newGridState.columnSettings.length
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
this.GridState = JSON.stringify(newGridState); // default columns for a view are the ones with DefaultInView turned on
|
|
141
|
+
}
|
|
142
|
+
NewRecord() {
|
|
143
|
+
const result = super.NewRecord();
|
|
144
|
+
if (result) {
|
|
145
|
+
if (this.ContextCurrentUser) {
|
|
146
|
+
this.UserID = this.ContextCurrentUser.ID;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const md = new core_1.Metadata();
|
|
150
|
+
if (md.CurrentUser)
|
|
151
|
+
this.UserID = md.CurrentUser.ID;
|
|
152
|
+
else
|
|
153
|
+
throw new Error('Unable to determine current user for new view record');
|
|
154
|
+
}
|
|
155
|
+
this.Name = '';
|
|
156
|
+
this.IsShared = false;
|
|
157
|
+
this.IsDefault = false;
|
|
158
|
+
this.WhereClause = '';
|
|
159
|
+
this.Description = '';
|
|
160
|
+
this.FilterState = JSON.stringify({ "logic": "and", "filters": [] }); // blank default for filter
|
|
161
|
+
this.GridState = JSON.stringify({}); // blank object initially
|
|
162
|
+
this.CustomFilterState = false;
|
|
163
|
+
this.CustomWhereClause = false;
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* This method is used to update the view's where clause based on the following logic.
|
|
169
|
+
* 1) If the view has a regular Filter State (which is typically set by an end-user in a UI), the FilterState will be processed and a WHERE clause will be generated
|
|
170
|
+
* 2) If SmartFilterEnabled === 1 and the view has a SmartFilterPrompt, the SmartFilterPrompt will be processed by AI and the SmartFilterWhereClause will be generated. SmartFilterWhereClause will only be generated whenever the SmartFilterPrompt changes.
|
|
171
|
+
* 3) If CustomWhereClause === 1, this function will NOT modify the WhereClause because the sysadmin has set CustomWhereClause === 1 which means we don't want any changes to this particular view's WhereClause
|
|
172
|
+
* IMPORTANT NOTE: This method does not save the record. You still call .Save() to save the record as desired. If you want to get the new WhereClause based on the FilterState but not
|
|
173
|
+
* update the FilterState column, call the GenerateWhereClause() method.
|
|
174
|
+
* KEY ASSUMPTION: The server code must set a property in the MJGlobal.Properties array with a key of OPENAI_API_KEY to use the AI functionality. If this property is not set, the AI functionality will not work.
|
|
175
|
+
*/
|
|
176
|
+
async UpdateWhereClause() {
|
|
177
|
+
if (this.CustomWhereClause && (this.CustomWhereClause === true || this.CustomWhereClause === 1))
|
|
178
|
+
// if the CustomWhereClause is set to true or 1, we don't want to update the WhereClause
|
|
179
|
+
return;
|
|
180
|
+
// if we get here, we need to update the WhereClause, first check to see if we have a Smart Filter or not
|
|
181
|
+
if (this.SmartFilterEnabled && (this.SmartFilterEnabled === true || this.SmartFilterEnabled === 1) &&
|
|
182
|
+
this.SmartFilterPrompt && this.SmartFilterPrompt.length > 0) {
|
|
183
|
+
// we have a smart filter prompt (e.g. a prompt for the AI to create the where clause)
|
|
184
|
+
// if the SmartFilterPrompt has changed, then we need to update the SmartFilterWhereClause using AI
|
|
185
|
+
// otherwise, we don't need to do anything other than just use the SmartFilterWhereClause as it is
|
|
186
|
+
if (this.Fields.find(c => c.Name.toLowerCase() == 'smartfilterprompt')?.Dirty) {
|
|
187
|
+
// the prompt has changed (or is newly populated, either way it is dirty) so use the AI to figure this out
|
|
188
|
+
this.SmartFilterWhereClause = await this.GenerateSmartFilterWhereClause(this.SmartFilterPrompt, this.ViewEntityInfo);
|
|
189
|
+
}
|
|
190
|
+
else
|
|
191
|
+
this.WhereClause = this.SmartFilterWhereClause;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this.WhereClause = this.GenerateWhereClause(this.FilterState, this.ViewEntityInfo);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* This method will use AI to return a valid WHERE clause based on the provided prompt. This is automatically called at the right time if the view has SmartFilterEnabled turned on and the SmartFilterPrompt is set. If you want
|
|
199
|
+
* to call this directly to get back a WHERE clause for other purposes you can call this method directly and provide both a prompt and the entity that the view is based on.
|
|
200
|
+
* @param prompt
|
|
201
|
+
*/
|
|
202
|
+
async GenerateSmartFilterWhereClause(prompt, entityInfo) {
|
|
203
|
+
try {
|
|
204
|
+
const apiKey = global_1.MJGlobal.Instance.Properties['OPENAI_API_KEY'];
|
|
205
|
+
if (!apiKey)
|
|
206
|
+
throw new Error('Unable to find OPENAI_API_KEY property in MJGlobal.Properties array. This property is required to use the AI functionality.');
|
|
207
|
+
// we have our OpenAI API Key, let's prompt
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
(0, core_1.LogError)(e);
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
Set(FieldName, Value) {
|
|
216
|
+
// call the superclass first and set the value internally there
|
|
217
|
+
super.Set(FieldName, Value);
|
|
218
|
+
if (FieldName.toLowerCase() == 'entityid') {
|
|
219
|
+
// we're updating the entityID, need to upate the _ViewEntityInfo property so it is always in sync
|
|
220
|
+
const md = new core_1.Metadata();
|
|
221
|
+
const match = md.Entities.find(e => e.ID == Value);
|
|
222
|
+
if (match)
|
|
223
|
+
this._ViewEntityInfo = match;
|
|
224
|
+
else
|
|
225
|
+
throw new Error('Unable to find entity info for entity ID: ' + Value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Create a where clause for SQL from the structured filter state JSON information
|
|
230
|
+
* @param FilterState A string containing a valid Filter State JSON string - this uses the format that the Kendo Filter component uses which is generic and can be used anywhere
|
|
231
|
+
* with/without kendo
|
|
232
|
+
* @param EntityInfo The entity info for the entity that the UserView is based on
|
|
233
|
+
* @returns a string that represents a valid SQL WHERE clause
|
|
234
|
+
* @memberof UserViewEntitySubclass
|
|
235
|
+
* @example Example Filter State JSON below
|
|
236
|
+
FilterState = `{
|
|
237
|
+
"logic": "or",
|
|
238
|
+
"filters": [{
|
|
239
|
+
"field": "Name",
|
|
240
|
+
"operator": "startswith",
|
|
241
|
+
"value": "A"
|
|
242
|
+
}, {
|
|
243
|
+
"logic": "or",
|
|
244
|
+
"filters": [{
|
|
245
|
+
"field": "TotalRevenue",
|
|
246
|
+
"operator": "gt",
|
|
247
|
+
"value": 10000000
|
|
248
|
+
}, {
|
|
249
|
+
"field": "NumberEmployees",
|
|
250
|
+
"operator": "gte",
|
|
251
|
+
"value": 25
|
|
252
|
+
}, {
|
|
253
|
+
"field": "InformationTechnologyExpense",
|
|
254
|
+
"operator": "gte",
|
|
255
|
+
"value": 500000
|
|
256
|
+
}, {
|
|
257
|
+
"logic": "and",
|
|
258
|
+
"filters": [{
|
|
259
|
+
"field": "City",
|
|
260
|
+
"operator": "eq",
|
|
261
|
+
"value": "Chicago"
|
|
262
|
+
}, {
|
|
263
|
+
"field": "ActivityCount",
|
|
264
|
+
"operator": "gte",
|
|
265
|
+
"value": 5
|
|
266
|
+
}]
|
|
267
|
+
}]
|
|
268
|
+
}, {
|
|
269
|
+
"field": "LatestActivityDate",
|
|
270
|
+
"operator": "gte",
|
|
271
|
+
"value": "2023-01-01T06:00:00.000Z"
|
|
272
|
+
}]
|
|
273
|
+
}`;
|
|
274
|
+
*/
|
|
275
|
+
GenerateWhereClause(FilterState, entityInfo) {
|
|
276
|
+
return this.processFilterGroup(JSON.parse(FilterState), entityInfo);
|
|
277
|
+
}
|
|
278
|
+
wrapQuotes(value, needQuotes) {
|
|
279
|
+
return needQuotes ? `'${value}'` : value;
|
|
280
|
+
}
|
|
281
|
+
convertFilterToSQL(field, operator, value, entity) {
|
|
282
|
+
let op = '';
|
|
283
|
+
let bNeedsQuotes = false;
|
|
284
|
+
const f = entity.Fields.find((f) => f.Name.trim().toLowerCase() === field.trim().toLowerCase());
|
|
285
|
+
if (!f)
|
|
286
|
+
throw new Error('Unable to find field ' + field + ' in entity ' + entity.Name);
|
|
287
|
+
switch (f.Type.toLowerCase().trim()) {
|
|
288
|
+
case 'nvarchar':
|
|
289
|
+
case 'char':
|
|
290
|
+
case 'varchar':
|
|
291
|
+
case 'text':
|
|
292
|
+
case 'ntext':
|
|
293
|
+
case 'date':
|
|
294
|
+
case 'datetime':
|
|
295
|
+
case 'datetimeoffset':
|
|
296
|
+
case 'time':
|
|
297
|
+
case 'guid':
|
|
298
|
+
case 'uniqueidentifier':
|
|
299
|
+
bNeedsQuotes = true;
|
|
300
|
+
break;
|
|
301
|
+
// all other cases do not need quotes
|
|
302
|
+
}
|
|
303
|
+
switch (operator) {
|
|
304
|
+
case 'eq':
|
|
305
|
+
op = '= ' + this.wrapQuotes(value, bNeedsQuotes);
|
|
306
|
+
break;
|
|
307
|
+
case 'neq':
|
|
308
|
+
op = '<> ' + this.wrapQuotes(value, bNeedsQuotes);
|
|
309
|
+
break;
|
|
310
|
+
case 'gt':
|
|
311
|
+
op = '> ' + this.wrapQuotes(value, bNeedsQuotes);
|
|
312
|
+
break;
|
|
313
|
+
case 'gte':
|
|
314
|
+
op = '>= ' + this.wrapQuotes(value, bNeedsQuotes);
|
|
315
|
+
break;
|
|
316
|
+
case 'lt':
|
|
317
|
+
op = '< ' + this.wrapQuotes(value, bNeedsQuotes);
|
|
318
|
+
break;
|
|
319
|
+
case 'lte':
|
|
320
|
+
op = '<= ' + this.wrapQuotes(value, bNeedsQuotes);
|
|
321
|
+
break;
|
|
322
|
+
case 'startswith':
|
|
323
|
+
op = `LIKE '${value}%'`;
|
|
324
|
+
break;
|
|
325
|
+
case 'endswith':
|
|
326
|
+
op = `LIKE '%${value}'`;
|
|
327
|
+
break;
|
|
328
|
+
case 'contains':
|
|
329
|
+
op = `LIKE '%${value}%'`;
|
|
330
|
+
break;
|
|
331
|
+
case 'doesnotcontain':
|
|
332
|
+
op = `NOT LIKE '%${value}%'`;
|
|
333
|
+
break;
|
|
334
|
+
case 'isnull':
|
|
335
|
+
case 'isempty':
|
|
336
|
+
op = 'IS NULL';
|
|
337
|
+
break;
|
|
338
|
+
case 'isnotnull':
|
|
339
|
+
case 'isnotempty':
|
|
340
|
+
op = 'IS NOT NULL';
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
return `[${field}] ${op}`;
|
|
344
|
+
}
|
|
345
|
+
processFilterGroup(filterGroup, entity) {
|
|
346
|
+
// each filter has two properties, logic and filters
|
|
347
|
+
// logic is either 'and' or 'or' and is what we use to determine the SQL logic operator
|
|
348
|
+
// filters is an array of filters, each filter has a field, operator, and value,
|
|
349
|
+
let whereClause = '';
|
|
350
|
+
let bFirst = true;
|
|
351
|
+
const logic = filterGroup.logic.toUpperCase();
|
|
352
|
+
for (const filter of filterGroup.filters) {
|
|
353
|
+
if (!bFirst)
|
|
354
|
+
whereClause += ` ${logic} `;
|
|
355
|
+
else
|
|
356
|
+
bFirst = false;
|
|
357
|
+
// if an individual filter has a "logic" property, it's a group and we need to process it with parenthesis and recurisely
|
|
358
|
+
if (filter.logic && filter.logic.length > 0) {
|
|
359
|
+
// this is a group, we process it with parenthesis
|
|
360
|
+
whereClause += `(${this.processFilterGroup(filter, entity)})`;
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// this is an individual filter, easy to process
|
|
364
|
+
whereClause += `(${this.convertFilterToSQL(filter.field, filter.operator, filter.value, entity)})`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return whereClause;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
exports.UserViewEntityExtended = UserViewEntityExtended;
|
|
371
|
+
exports.UserViewEntityExtended = UserViewEntityExtended = __decorate([
|
|
372
|
+
(0, global_1.RegisterClass)(core_1.BaseEntity, 'User Views', 2) // 2 priority so this gets used ahead of the generated sub-class
|
|
373
|
+
], UserViewEntityExtended);
|
|
374
|
+
class ViewInfo {
|
|
375
|
+
/**
|
|
376
|
+
* Returns a list of views for the specified user. If no user is specified, the current user is used.
|
|
377
|
+
* @param contextUser optional, the user to use for context when loading the view
|
|
378
|
+
* @param entityId optional, the entity ID to filter the views by, if not provided, there is no filter on EntityID and all views for the user are returned
|
|
379
|
+
* @returns an array of UserViewEntityBase objects
|
|
380
|
+
* @memberof ViewInfo
|
|
381
|
+
* @static
|
|
382
|
+
* @async
|
|
383
|
+
*/
|
|
384
|
+
static async GetViewsForUser(entityId, contextUser) {
|
|
385
|
+
const rv = new core_1.RunView();
|
|
386
|
+
const md = new core_1.Metadata();
|
|
387
|
+
const result = await rv.RunView({
|
|
388
|
+
EntityName: 'User Views',
|
|
389
|
+
ExtraFilter: `UserID = ${contextUser ? contextUser.ID : md.CurrentUser.ID}
|
|
390
|
+
${entityId ? ` AND EntityID = ${entityId}` : ''}`
|
|
391
|
+
});
|
|
392
|
+
const rd = result?.Results;
|
|
393
|
+
if (result && result.Success && rd)
|
|
394
|
+
return rd;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Returns a view ID for a given viewName
|
|
398
|
+
* @param viewName Name of the view to lookup the ID for
|
|
399
|
+
* @returns the ID of the User View record that matches the provided name, if found
|
|
400
|
+
*/
|
|
401
|
+
static async GetViewID(viewName) {
|
|
402
|
+
const rv = new core_1.RunView();
|
|
403
|
+
const result = await rv.RunView({ EntityName: 'User Views', ExtraFilter: `Name = '${viewName}'` });
|
|
404
|
+
const rd = result?.Results;
|
|
405
|
+
if (result && result.Success && rd && rd.length > 0) {
|
|
406
|
+
return rd[0].ID;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
throw new Error('Unable to find view with name: ' + viewName);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Loads a new entity object for User Views for the specified viewId and returns it if successful.
|
|
414
|
+
* @param viewId record ID for the view to load
|
|
415
|
+
* @param contextUser optional, the user to use for context when loading the view
|
|
416
|
+
* @returns UserViewEntityBase (or a subclass of it)
|
|
417
|
+
* @throws Error if the view cannot be loaded
|
|
418
|
+
* @memberof ViewInfo
|
|
419
|
+
* @static
|
|
420
|
+
* @async
|
|
421
|
+
*/
|
|
422
|
+
static async GetViewEntity(viewId, contextUser) {
|
|
423
|
+
const md = new core_1.Metadata();
|
|
424
|
+
const view = await md.GetEntityObject('User Views', contextUser);
|
|
425
|
+
if (await view.Load(viewId))
|
|
426
|
+
return view;
|
|
427
|
+
else
|
|
428
|
+
throw new Error('Unable to load view with ID: ' + viewId);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Loads a new entity object for User Views for the specified viewName and returns it if successful.
|
|
432
|
+
* @param viewName name for the view to load
|
|
433
|
+
* @param contextUser optional, the user to use for context when loading the view
|
|
434
|
+
* @returns UserViewEntityBase (or a subclass of it)
|
|
435
|
+
* @throws Error if the view cannot be loaded
|
|
436
|
+
* @memberof ViewInfo
|
|
437
|
+
* @static
|
|
438
|
+
* @async
|
|
439
|
+
*/
|
|
440
|
+
static async GetViewEntityByName(viewName, contextUser) {
|
|
441
|
+
const viewId = await ViewInfo.GetViewID(viewName);
|
|
442
|
+
if (viewId)
|
|
443
|
+
return await ViewInfo.GetViewEntity(viewId, contextUser);
|
|
444
|
+
else
|
|
445
|
+
throw new Error('Unable to find view with name: ' + viewName);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
exports.ViewInfo = ViewInfo;
|
|
449
|
+
class ViewColumnInfo extends core_1.BaseInfo {
|
|
450
|
+
constructor(initData = null) {
|
|
451
|
+
super();
|
|
452
|
+
this.ID = null;
|
|
453
|
+
this.Name = null;
|
|
454
|
+
this.DisplayName = null;
|
|
455
|
+
this.hidden = null;
|
|
456
|
+
this.width = null;
|
|
457
|
+
this.orderIndex = null;
|
|
458
|
+
this.EntityField = null;
|
|
459
|
+
this.copyInitData(initData);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
exports.ViewColumnInfo = ViewColumnInfo;
|
|
463
|
+
var ViewFilterLogicInfo;
|
|
464
|
+
(function (ViewFilterLogicInfo) {
|
|
465
|
+
ViewFilterLogicInfo[ViewFilterLogicInfo["And"] = 1] = "And";
|
|
466
|
+
ViewFilterLogicInfo[ViewFilterLogicInfo["Or"] = 2] = "Or";
|
|
467
|
+
})(ViewFilterLogicInfo || (exports.ViewFilterLogicInfo = ViewFilterLogicInfo = {}));
|
|
468
|
+
class ViewFilterInfo extends core_1.BaseInfo {
|
|
469
|
+
constructor(initData = null) {
|
|
470
|
+
super();
|
|
471
|
+
this.logicOperator = null;
|
|
472
|
+
this.field = null;
|
|
473
|
+
this.operator = null;
|
|
474
|
+
this.value = null;
|
|
475
|
+
this.filters = [];
|
|
476
|
+
this.copyInitData(initData);
|
|
477
|
+
if (initData && initData.logic) {
|
|
478
|
+
this.logicOperator = initData.logic.trim().toLowerCase() == 'and' ? ViewFilterLogicInfo.And : ViewFilterLogicInfo.Or;
|
|
479
|
+
}
|
|
480
|
+
if (initData && initData.filters) {
|
|
481
|
+
this.filters = initData.filters.map(f => new ViewFilterInfo(f));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
exports.ViewFilterInfo = ViewFilterInfo;
|
|
486
|
+
var ViewSortDirectionInfo;
|
|
487
|
+
(function (ViewSortDirectionInfo) {
|
|
488
|
+
ViewSortDirectionInfo[ViewSortDirectionInfo["Asc"] = 1] = "Asc";
|
|
489
|
+
ViewSortDirectionInfo[ViewSortDirectionInfo["Desc"] = 2] = "Desc";
|
|
490
|
+
})(ViewSortDirectionInfo || (exports.ViewSortDirectionInfo = ViewSortDirectionInfo = {}));
|
|
491
|
+
class ViewSortInfo extends core_1.BaseInfo {
|
|
492
|
+
constructor(initData = null) {
|
|
493
|
+
super();
|
|
494
|
+
this.field = null;
|
|
495
|
+
this.direction = null;
|
|
496
|
+
this.copyInitData(initData);
|
|
497
|
+
if (initData && initData.dir && typeof initData.dir == 'string') {
|
|
498
|
+
this.direction = initData.dir.trim().toLowerCase() == 'asc' ? ViewSortDirectionInfo.Asc : ViewSortDirectionInfo.Desc;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
exports.ViewSortInfo = ViewSortInfo;
|
|
503
|
+
class ViewGridState {
|
|
504
|
+
}
|
|
505
|
+
exports.ViewGridState = ViewGridState;
|
|
506
|
+
//# sourceMappingURL=UserViewEntity.js.map
|