Wiki source code of Sécuriser un modèle de données
Last modified by Aurelie Bertrand on 2025/07/18 15:04
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | {{ddtoc/}} | ||
2 | |||
3 | ---- | ||
4 | |||
5 | (% class="wikigeneratedid" id="HPrE9ambule" %) | ||
6 | This guide describes the methods for securing the information contained in data cubes. This is generally referred to as Row Level Security. We also refer to this as 'personalisation' of cubes and flows. | ||
7 | |||
8 | (% class="wikigeneratedid" %) | ||
9 | It presents an overview of classic personalisation methods with the use of user variables, and then introduces a more powerful and flexible approach:** Live Security**, dynamic customisation based on scripts. | ||
10 | |||
11 | (% class="box" %) | ||
12 | ((( | ||
13 | 💡 See [[Live Security: examples of use>>doc:.Live Security \: exemples.WebHome]] for detailed usage examples. | ||
14 | ))) | ||
15 | |||
16 | = Security via personalisation = | ||
17 | |||
18 | Row-level security can be achieved by personalising cubes or flows using user variables **(${user.<variablename>}**). | ||
19 | |||
20 | == Personalisation at data model (cube) level == | ||
21 | |||
22 | Line-level security is resolved at data cube generation via the use of user variables **(${user.<namevariable>}**) in the definition of the data source and data model. For example in SQL: SELECT * FROM UneTable WHERE country='${user.country}' AND service in '${user.services}'. | ||
23 | |||
24 | In this example, each user has a //country// variable equal to their country, and a //services// variable which is a comma-separated list of department names (Marketing,Sales,Logistics) to which the user has access rights. | ||
25 | |||
26 | Thus, for each user, DigDash generates a different cube (one for each combination of variable values) containing only the data corresponding to each user's "personalisation profile". If several users have the same values for these variables, they are said to have the same profile, so they share the same cube. | ||
27 | |||
28 | This mechanism is very simple to set up, and generally makes it possible to segment the volume of data, and therefore the memory load on the server during consultation. Users rarely all connect to the server at the same time, so the server will not need to store all the cubes in memory at all times. | ||
29 | |||
30 | Another advantage is that, depending on the type of data source, the security rule can be very advanced. Our simple example of filtering on two columns, //countries// and //services//, can take into account other criteria, come from other tables (that we don't want to include in the cube), etc. | ||
31 | |||
32 | On the other hand, this mechanism multiplies the number of cubes to be generated and therefore the number of queries to the data source. | ||
33 | |||
34 | Finally, the security rule can generate data redundancy between different cubes, representing the common part of the data that several users can see, even if they don't share exactly the same profile. This common part is duplicated in several cubes. | ||
35 | |||
36 | * **Advantages **: Strong security, easy to implement, great flexibility (the complexity of security depends on the capabilities of the data source). | ||
37 | * **Disadvantages **: More queries to generate cubes, risk of data redundancy in several cubes. | ||
38 | |||
39 | [[image:Picture1.png||alt="DM personalisation" data-xwiki-image-style-alignment="center" height="319" width="363"]] | ||
40 | |||
41 | == Personalisation at flow level (chart/table) == | ||
42 | |||
43 | Another more dynamic approach is to use these user variables in the value of filters on each chart using the cube. To take the previous example, the cube takes its data from an SQL source: SELECT * FROM ATable. It is unique (not personalised) for all users, and therefore large. In all the flows using this cube, you can add a filter on the dimension Country ( rule //"equals to"// ${user.country}) and on the dimension Department ( rule //"equals to" //rule ${user.services}). Each time a flow is displayed, the filter will be applied to the data cube so that only the rows to which the user has access are retained. Of course, browsing these dimensions must be forbidden to the user so that they are restricted to this perimeter. | ||
44 | |||
45 | The advantage is that there is only one cube to generate, and therefore only one request to the data source. | ||
46 | |||
47 | On the other hand, as the filtering is controlled by the flows themselves when the dashboards are consulted, there is a risk of allowing users to access data that does not concern them. This can happen either because of a design flaw in the dashboard pages (e.g. the prohibition on navigation on a dimension has been omitted), or through manipulation (by an expert user) of the display requests sent to the server. | ||
48 | |||
49 | Finally, another disadvantage is the reduced flexibility of the security rule. In this approach, it depends directly on what has been stored in the cube and relies solely on dimension filtering. | ||
50 | |||
51 | * **Advantage **: A single request for the generation of a single cube. | ||
52 | * **Disadvantages **: tedious to implement (pre-configure filtering on all flows), low security, limited security rules. | ||
53 | |||
54 | [[image:Picture2c.png||alt="Picture2b.png" data-xwiki-image-style-alignment="center" height="337" width="341"]] | ||
55 | |||
56 | == Comparison of the 2 types of personalisation == | ||
57 | |||
58 | Here is a table comparing these two personalisation approaches: | ||
59 | |||
60 | | |**Cube personalisation**|**{{glossaryReference glossaryId="Glossary" entryId="Flux"}}Flow{{/glossaryReference}} personalisation** | ||
61 | |**Security**|High|Low | ||
62 | |**Generation of cubes**|((( | ||
63 | X cubes generated (1 per "personalisation profile") | ||
64 | |||
65 | ~=> X queries | ||
66 | )))|((( | ||
67 | 1 cube generated | ||
68 | |||
69 | ~=> 1 single query | ||
70 | ))) | ||
71 | |**Implementation**|Simple|Tedious | ||
72 | |**Flexibility**|Depends on the data source (e.g. SQL)|Filtering on cube data only | ||
73 | |||
74 | = Security via Live Security{{id name="live_security"/}} = | ||
75 | |||
76 | == Concept == | ||
77 | |||
78 | The Live Security approach aims to retain only the advantages of the two personalisation mechanisms described above: strong security, a low number of queries required to generate the cube, and implementation that is as simple as possible while retaining a sufficiently high level of flexibility. | ||
79 | |||
80 | It allows security rules to be added at the level of the data model itself, without multiplying the number of cubes generated and without having to manage security at row level in the flows. | ||
81 | |||
82 | Security is expressed through simple filtering rules set up using a wizard to cover the most common requirements for restricting browsing to members of one or more dimensions, depending on a user's variables. | ||
83 | |||
84 | For advanced needs, you can use a Javascript script that will be executed each time the cube is queried on the incoming selection. In this case, the selection can be transformed. This can range from simple filtering similar to the "{{glossaryReference glossaryId="Glossary" entryId="Flux"}}Flow{{/glossaryReference}} personalisation" approach to more complex transformation based on the user's profile. For example, changing an exploration level, deleting an axis, one or more measures, etc. | ||
85 | |||
86 | Some examples of advanced selection transformations (advanced examples of Live Security are detailed in our knowledge base): | ||
87 | |||
88 | * Filtering one dimension //or //another according to the value of a user variable. | ||
89 | * Creating "OR" filter rules between dimensions. | ||
90 | * Query another cube to extract a dynamic security perimeter, depending on the current selection... | ||
91 | |||
92 | On the security side, this transformation, whether simple or advanced, is applied before the selection is processed on the server side and only depends on the user's profile. There is no way for a user, even an expert, to alter this process in the same way as for flow personalisation. | ||
93 | |||
94 | * **Advantages **: A single request for the generation of a single cube, strong security, great flexibility (potentially total transformation of the flow selection). | ||
95 | * **Disadvantages **: Potentially complicated implementation (Javascript) for complex requirements. But there is an interface that helps users to create simple Live Security filters, without Javascript. | ||
96 | |||
97 | [[image:Picture3.png||alt="Live security" data-xwiki-image-style-alignment="center" height="427" width="759"]] | ||
98 | |||
99 | {{comment}} | ||
100 | Capture en basse résolution | ||
101 | {{/comment}} | ||
102 | |||
103 | == Comparison of security methods == | ||
104 | |||
105 | Here is a table comparing this new approach with the two previous personalisation approaches: | ||
106 | |||
107 | | |**Cube personnalisation**|**{{glossaryReference glossaryId="Glossary" entryId="Flux"}}Flow{{/glossaryReference}} personalisation**|**"Live Security"** | ||
108 | |**Security**|Strong|Low|High | ||
109 | |**Generation of cubes**|((( | ||
110 | X cubes generated (1 per "personalisation profile") | ||
111 | |||
112 | ~=> X queries | ||
113 | )))|((( | ||
114 | 1 cube generated | ||
115 | |||
116 | ~=> 1 single request | ||
117 | )))|((( | ||
118 | 1 cube generated | ||
119 | |||
120 | ~=> 1 single query | ||
121 | ))) | ||
122 | |**How to set it up**|Simple|Tedious (per {{glossaryReference glossaryId="Glossary" entryId="Flux"}}Flow{{/glossaryReference}})|Assistant / Script | ||
123 | |**Flexibility**|Depending on the data source (e.g. SQL)|Filtering on cube data only|Selection transformation | ||
124 | |||
125 | == Implementation == | ||
126 | |||
127 | We will now look at how to implement the Live Security mechanism using a standard dimension filtering example. | ||
128 | |||
129 | The example is based on a data source containing at least two columns //Area //(level country) and //department// and value columns (measures). Each user is assigned to a country and one or more department and will therefore only see the figures relating to them. Some users may have the right to see all countries and/or all services. | ||
130 | |||
131 | === Requirements === | ||
132 | |||
133 | __Users__ | ||
134 | |||
135 | Each user has two user variables in LDAP: //country// and //department//, which define the user's security perimeter. Each of the two variables can be empty, which means that the user can view all countries and/or departments. The variable //department// can be a list of service names separated by commas (no spaces after the commas). | ||
136 | |||
137 | For example, we have the following 2 users: | ||
138 | |||
139 | * User U1 : | ||
140 | ** country =//'fr'// | ||
141 | ** department = 'Marketing,Sales' | ||
142 | * User U2 : | ||
143 | ** country = 'de' | ||
144 | ** department = (empty string) | ||
145 | |||
146 | __Data model__ | ||
147 | |||
148 | The data model is based on an SQL source on which the following query is executed: | ||
149 | |||
150 | {{code language="SQL"}} | ||
151 | SELECT country, department, val1, val2 FROM ATable | ||
152 | {{/code}} | ||
153 | |||
154 | Note that there is no WHERE clause in this query, as the aim is to generate a single cube containing all the data for all the users. | ||
155 | |||
156 | The data model therefore contains two dimensions and two measures. | ||
157 | |||
158 | (% class="box warningmessage" %) | ||
159 | ((( | ||
160 | ⚠ **Important** | ||
161 | //The user variables **${user.country}**, **${user.department}** must not be used in this context, either in the data source (WHERE clause), in a calculated measurement formula or anywhere in the data model. Otherwise this would force the system to use cube-based personalisation, which we don't want in this approach.// | ||
162 | ))) | ||
163 | |||
164 | === Creation of the Live Security function === | ||
165 | |||
166 | Live Security is activated in the **Advanced configuration **screen of the data model, on the **Advanced** tab. Here we describe the two ways of developing a Live Security function, either via our assistant dedicated to simple functions, or via the creation of a Javascript function. | ||
167 | |||
168 | [[image:Advanced_tab_EN.png||alt="Advanced tab"]] | ||
169 | |||
170 | (% class="wikigeneratedid" id="HCrE9ationavecl27assistant" %) | ||
171 | **Creating a function using the wizard** | ||
172 | |||
173 | 1. To create a simple Live Security function without Javascript, you can click on the **Create (assistant)...** button next to the **Transformation selection function** drop-down list. | ||
174 | 1. Enter a name for the function. | ||
175 | 1. Choose the dimension **Country** for the first filter rule. This rule has no hierarchy or level, so leave these two fields empty. | ||
176 | 1. Enter **${user.country}** in the **Selected members** field. | ||
177 | 1. Add a new rule by clicking on the **+** icon | ||
178 | 1. Select the dimension **Department **on the second filter rule. This rule has no hierarchy or level, so leave these two fields empty. | ||
179 | 1. Enter **${user.department}** in the selected members field. | ||
180 | 1. As we have defined that the user variable //department //can have multiple values separated by commas, a comma (**,**) must be entered in the value separator field. | ||
181 | 1. Click **OK **to create the new Live Security function. | ||
182 | |||
183 | [[image:Function_assistant_EN.png||alt="Function assistant"]] | ||
184 | |||
185 | (% class="wikigeneratedid" id="HCrE9ationd27unscript" %) | ||
186 | **Creating a function based on a script** | ||
187 | |||
188 | 1. To create an advanced Live Security function based on a JavaScript script, click the **Edit...** button next to the **Transformation selection function** drop-down list. | ||
189 | 1. Choose **Shared function **in the Function **type**. | ||
190 | 1. Add a new pre-defined function by clicking on the **+** button in the Function Manager toolbar. | ||
191 | 1. Enter a name for the function. | ||
192 | |||
193 | You can now enter a script to transform the selections made on the cube corresponding to this data model. This is described in the following paragraph. | ||
194 | |||
195 | The script to be written is the body of a JavaScript function which takes a "selection" object as its parameter. This object represents the description of the result we want to obtain from the cube ("flattening" operation). This complex object defines the desired axes, dimensions, filters, measurements and other parameters specific to each type of flow (chart, table, etc.). What interests us in this example are the dimension filter parameters. The aim is to transform this selection to include two 'forced' filters, one on the user's country and one on their departments, if the user has defined (non-empty) variables for country and/or departments. | ||
196 | |||
197 | Let's start by retrieving the value of the user's variable //country//using the function **getUserAttribute('<variableName>')** : | ||
198 | |||
199 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
200 | var country = getUserAttribute('country'); | ||
201 | {{/code}} | ||
202 | |||
203 | Then, if the country variable is defined and not empty, we apply a filter to the selection object: | ||
204 | |||
205 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
206 | ... | ||
207 | |||
208 | if (country != null && country != '') | ||
209 | |||
210 | { | ||
211 | |||
212 | var tabCountry = [country]; //creation of a table containing the country | ||
213 | |||
214 | var dim = selection.dm.getDimensionById('Country'); | ||
215 | |||
216 | var filt = new FilterSelection(dim, -1, -1, [], tabCountry); | ||
217 | |||
218 | selection.setFilter(filt); | ||
219 | |||
220 | } | ||
221 | {{/code}} | ||
222 | |||
223 | Explanation: The filter is constructed using the instruction **new FilterSelection(Dimension, hierarchyIndex, levelIndex, [ ], arrayMembers)**: | ||
224 | |||
225 | * **Dimension**: The dimension object can be retrieved directly from the data model accessible via **selection.dm**, using the **getDimensionById(dimId)** function. | ||
226 | * **hierarchyIndex** and **levelIndex**: Then, in this example, we filter the root members of the dimension directly, there is no hierarchy or level to specify (...-1, -1...). | ||
227 | * **[ ] **(empty array): Used internally, do not modify. | ||
228 | * **arrayMembers**: Finally, a filter on a dimension can have several members selected, so an array of members must be passed to it (here **tabCountry**). For the country, as the user only has one, the array contains only one element. | ||
229 | |||
230 | Then we apply this filter to the selection using **selection.setFilter(...)**. | ||
231 | |||
232 | Finally, we do the same thing for the variable //department//__,__ __which represents a list of departments__, which we treat specifically using the Javascript **split**() function. This splits a character string into an array of character strings according to a separator character (the comma in our case): | ||
233 | |||
234 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
235 | ... | ||
236 | |||
237 | var department = getUserAttribute('department'); | ||
238 | |||
239 | if (department != null && department != '') | ||
240 | |||
241 | { | ||
242 | |||
243 | var tabDepartment = department.split(','); | ||
244 | |||
245 | var dim = selection.dm.getDimensionById('Department'); | ||
246 | |||
247 | var filt = new FilterSelection(dim, -1, -1, [], tabDepartment); | ||
248 | |||
249 | selection.setFilter(filt); | ||
250 | |||
251 | } | ||
252 | {{/code}} | ||
253 | |||
254 | === Use === | ||
255 | |||
256 | All that remains is to create a flow based on this data model and place it on a dashboard page. | ||
257 | |||
258 | If you don't hide the dimensions //country// and //department// in the dashboard (or in the flow) it will still be possible to filter on one of these dimensions. However, the filter will be overwritten by the Live Security script if the user has a non-empty value for his variable //country// or //department//. In the end, filtering on these dimensions in the dashboard is only useful for users who have the right to see everything, i.e. those who have an empty value in their variable //country// and/or //department//. | ||
259 | |||
260 | == API reference == | ||
261 | |||
262 | There are a few recommended methods for manipulating a selection, presented below. | ||
263 | |||
264 | * Retrieving user information | ||
265 | |||
266 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
267 | (String) getUserAttribute (attr) | ||
268 | {{/code}} | ||
269 | |||
270 | Description : Returns the LDAP **attr** attribute of the user. See the [[User attributes >>doc:.User_attributes.WebHome]]page for a list of default user attributes. | ||
271 | It is also possible to retrieve the value of user parameters. See the paragraph [[Adding user parameters>>doc:Digdash.deployment.configuration.administration.WebHome||anchor="Parametres_utilisateurs"]] paragraph for more information. | ||
272 | |||
273 | Example: | ||
274 | |||
275 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
276 | var userName = getUserAttribute('displayName'); | ||
277 | {{/code}} | ||
278 | |||
279 | * **Retrieving user session information** | ||
280 | |||
281 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
282 | (String) getSessionAttribute (attr) | ||
283 | {{/code}} | ||
284 | |||
285 | Description: Returns the user's Session **attr** attribute for the current session. | ||
286 | |||
287 | (% class="box infomessage" %) | ||
288 | ((( | ||
289 | The use of session variables is covered on the page [[**Session Variables**>>doc:Digdash.user_guide.tutorials.session_variables_tutorial.WebHome]]. | ||
290 | ))) | ||
291 | |||
292 | Example: | ||
293 | |||
294 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
295 | var scenario = getSessionAttribute('Scenario'); | ||
296 | {{/code}} | ||
297 | |||
298 | * **Retrieving information from the current selection or data model** | ||
299 | |||
300 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
301 | (Number) getDDVar(variable) | ||
302 | {{/code}} | ||
303 | |||
304 | Description: Returns the value of the corresponding **model variable** (DDVar). These variables are defined in the data model and correspond to a widget in the dashboard page. | ||
305 | |||
306 | (% class="box warningmessage" %) | ||
307 | ((( | ||
308 | //Warning: do not confuse the DDVar with the user variable (LDAP attribute).// | ||
309 | ))) | ||
310 | |||
311 | Example: | ||
312 | |||
313 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
314 | var rateEuroDollar = getDDVar('rateEuroDollar'); | ||
315 | |||
316 | (Dimension) selection.dm.getDimensionById(dimId) | ||
317 | {{/code}} | ||
318 | |||
319 | Description: Returns a Javascript object corresponding to the **dimId** identifier dimension. Returns **null** if this dimension is not in the current model. This object is needed for other functions, for example to build a filter. | ||
320 | |||
321 | Example: | ||
322 | |||
323 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
324 | var dimCountry = selection.dm.getDimensionById('Country'); | ||
325 | {{/code}} | ||
326 | |||
327 | * **Selection transformations** | ||
328 | |||
329 | __FilterSelection function__ | ||
330 | |||
331 | {{code cssClass="notranslate" language="javascript"}} | ||
332 | (Filter) new FilterSelection(dim, hierarchy, level, [ ], ValuesTab) | ||
333 | // dim : dimension to be filtered | ||
334 | // hierarchy : hierarchy index (or -1 if no hierarchy) | ||
335 | // level : hierarchy level (or -1 for root) | ||
336 | // [] : internal array — always empty, do not touch! | ||
337 | // ValuesTab : List of values/members to be filtered | ||
338 | {{/code}} | ||
339 | |||
340 | Description: Returns a JavaScript object corresponding to the desired filter on the dimension in parameter, in a given hierarchy and level (**-1** if no hierarchy/level) and with the specified members. This object is needed for other functions, for example to apply the filter to the selection. | ||
341 | |||
342 | (% class="box infomessage" %) | ||
343 | ((( | ||
344 | 💡 **Hierarchies** | ||
345 | |||
346 | In this function, you need to use indexes for hierarchies and levels. This corresponds to the position of the hierarchy in the dimension's hierarchies, as well as its level. | ||
347 | For hierarchies, the positions are as follows: | ||
348 | 0 | ||
349 | 1 | ||
350 | 2 | ||
351 | ... | ||
352 | For levels, the positions are as follows: | ||
353 | -1 (root level) | ||
354 | 0 | ||
355 | 1 | ||
356 | ... | ||
357 | For example, if in my time dimension I have three hierarchies: "Date", "Month Year", "Week Year" then the index of "Date" is 0, "Month Year" is 1 and "Week Year" is 2. | ||
358 | For the levels of Date: root -1, "day" 0, "month" 1 and "year" 2. | ||
359 | ))) | ||
360 | |||
361 | Example : | ||
362 | |||
363 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
364 | var filter = new FilterSelection(dim, -1, -1, [], new Array('fr','it','de')); | ||
365 | |||
366 | void selection.setFilter(filter) | ||
367 | {{/code}} | ||
368 | |||
369 | Description: Applies a filter to the current selection. Overwrites the existing filter on this dimension if there was one. | ||
370 | |||
371 | Example: Description | ||
372 | |||
373 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
374 | selection.setFilter(filter); | ||
375 | |||
376 | void setDDVar(variable, value) | ||
377 | {{/code}} | ||
378 | |||
379 | Description: Modifies the value of a model variable (DDVar) for the current selection. The value of the variable is not modified persistently, only for this selection. | ||
380 | |||
381 | (% class="box warningmessage" %) | ||
382 | ((( | ||
383 | ⚠ //Warning: do not confuse the DDVar with the user variable (LDAP attribute).// | ||
384 | ))) | ||
385 | |||
386 | Example: | ||
387 | |||
388 | {{code cssClass="notranslate" language="JAVASCRIPT"}} | ||
389 | setDDVar('rateEuroDollar', 1.06); | ||
390 | {{/code}} | ||
391 | |||
392 | __FilterSelectionMatch function__ | ||
393 | |||
394 | {{code language="js"}} | ||
395 | new FilterSelectionMatch (dim, hierarchy, level, values, operators, matchMode) | ||
396 | // dim : dimension to be filtered | ||
397 | // hierarchy : hierarchy index (or -1 if no hierarchy) | ||
398 | // level : hierarchy level (or -1 for root) | ||
399 | // values : table of values to be filtered | ||
400 | // operators : table of operators applied to each value | ||
401 | // matchMode : 0 = all rules must be true (AND), 1 = at least one of the rules must be true (OR) | ||
402 | |||
403 | {{/code}} | ||
404 | |||
405 | Description: Allows you to filter a dimension using one or more conditions, specifying which operator to use for each value (equal, begins with, contains, etc.). | ||
406 | //values// and //operators// are parallel arrays: each value has its own operator. | ||
407 | It is also possible to choose whether the conditions should be combined using AND or OR. | ||
408 | |||
409 | The following operators are available: | ||
410 | |||
411 | |=(% style="width: 330px;" %)JavaScript code|=(% style="width: 260px;" %)Value|=(% style="width: 902px;" %)Meaning | ||
412 | |(% style="width:330px" %)OP_ISNOTNULL|(% style="width:260px" %)0|(% style="width:902px" %)Is non-null | ||
413 | |(% style="width:330px" %)OP_ISNULL|(% style="width:260px" %)1|(% style="width:902px" %)Is null | ||
414 | |(% style="width:330px" %)OP_EQUAL|(% style="width:260px" %)2|(% style="width:902px" %)Equals | ||
415 | |(% style="width:330px" %)OP_CONTAIN|(% style="width:260px" %)3|(% style="width:902px" %)Contains | ||
416 | |(% style="width:330px" %)OP_NOTCONTAIN|(% style="width:260px" %)4|(% style="width:902px" %)Does not contain | ||
417 | |(% style="width:330px" %)OP_NOTEQUAL|(% style="width:260px" %)5|(% style="width:902px" %)Different | ||
418 | |(% style="width:330px" %)OP_MATCHREGEXP|(% style="width:260px" %)6|(% style="width:902px" %)Matches the regular expression | ||
419 | |(% style="width:330px" %)OP_CONTAINWORD|(% style="width:260px" %)7|(% style="width:902px" %)Contains the word | ||
420 | |(% style="width:330px" %)OP_NOTCONTAINWORD|(% style="width:260px" %)8|(% style="width:902px" %)Does not contain the word | ||
421 | |(% style="width:330px" %)OP_SUP|(% style="width:260px" %)9|(% style="width:902px" %)Greater than | ||
422 | |(% style="width:330px" %)OP_INF|(% style="width:260px" %)10|(% style="width:902px" %)Lower | ||
423 | |(% style="width:330px" %)OP_SUPEQUAL|(% style="width:260px" %)11|(% style="width:902px" %)Greater than or equal to | ||
424 | |(% style="width:330px" %)OP_INFEQUAL|(% style="width:260px" %)12|(% style="width:902px" %)Less than or equal to | ||
425 | |(% style="width:330px" %)OP_STARTSWITH|(% style="width:260px" %)13|(% style="width:902px" %)Starts with | ||
426 | |(% style="width:330px" %)OP_ENDSWITH|(% style="width:260px" %)14|(% style="width:902px" %)Ends with | ||
427 | |(% style="width:330px" %)OP_ISIN|(% style="width:260px" %)15|(% style="width:902px" %)Is in | ||
428 | |(% style="width:330px" %)OP_ISNOTIN|(% style="width:260px" %)16|(% style="width:902px" %)Not in | ||
429 | |||
430 | __Example with 1 single filter :__ | ||
431 | |||
432 | {{code}} | ||
433 | new FilterSelectionMatch(dim, hierarchy, level, ['admin'], [13], 0); | ||
434 | {{/code}} | ||
435 | |||
436 | In this case : | ||
437 | |||
438 | * Value: ['admin'] | ||
439 | * Operator: [13] → OP_STARTSWITH → login must begin with admin | ||
440 | * matchMode: 0 → only one rule, so AND or OR changes nothing. | ||
441 | |||
442 | __Example with 2 filters:__ | ||
443 | |||
444 | {{code}} | ||
445 | new FilterSelectionMatch(dim, hierarchy, level, ['admin', 'John'], [13, 2], 1); | ||
446 | {{/code}} | ||
447 | |||
448 | In this case: | ||
449 | |||
450 | * We combine 2 rules: | ||
451 | ** login starts with admin (13 = STARTSWITH) | ||
452 | ** login equal to John (2 = EQUAL) | ||
453 | * matchMode: 1 → at least one of the rules must be true (OR logic). | ||
454 | |||
455 | = To find out more... = | ||
456 | |||
457 | {{ddchildren}}{{/ddchildren}} |