@datagouv/components-next 1.0.2-dev.221 → 1.0.2-dev.223

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.
@@ -1,4 +1,4 @@
1
- import { g as Ke } from "./main-HU94LBny.js";
1
+ import { g as Ke } from "./main-BzR7HPLe.js";
2
2
  import We from "vue";
3
3
  function Fe(I, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.221",
3
+ "version": "1.0.2-dev.223",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,9 +1,9 @@
1
- import type { Chart, ChartForm, ChartForApi, Filter, ColumnDefinition } from '../types/visualizations'
1
+ import type { Chart, ChartForm, ChartForApi, GenericFilter, ColumnDefinition } from '../types/visualizations'
2
2
  import type { ColumnType, TabularProfile } from '../components/TabularExplorer/types'
3
3
  import { resolveColumnType } from './tabular'
4
4
 
5
5
  export function toChartForm(chart: Chart) {
6
- const seriesFilter = chart.series[0]?.filters as Filter | null
6
+ const seriesFilter: GenericFilter | null = chart.series[0]?.filters ?? null
7
7
 
8
8
  return {
9
9
  title: chart.title,
@@ -257,15 +257,16 @@ const FALSY_VALUES = ['false', '0', 'non', 'no']
257
257
 
258
258
  // `encodeURIComponent` leaves `.`, `(` and `)` as-is, but they are the operator
259
259
  // separator and the delimiters of the API's `or(...)` grammar: a search value
260
- // containing one makes the parser reject the whole query with a 400. Wrapping
261
- // the value in double quotes (as the API README suggests) does not work here —
262
- // the API keeps them as part of the searched string and returns no result. It
263
- // does percent-decode the value after parsing, so encoding them is enough.
264
- function encodeConditionValue(value: string): string {
260
+ // containing one makes the parser reject the whole query with a 400.
261
+ // The API percent-decodes the query string once before running the grammar
262
+ // parser, then percent-decodes the parsed value: grammar-significant characters
263
+ // must therefore be sent double-encoded (`%252E`) so the first decode leaves
264
+ // them escaped (`%2E`) for the parser to unquote after parsing.
265
+ export function encodeConditionValue(value: string): string {
265
266
  return encodeURIComponent(value)
266
- .replace(/\./g, '%2E')
267
- .replace(/\(/g, '%28')
268
- .replace(/\)/g, '%29')
267
+ .replace(/\./g, '%252E')
268
+ .replace(/\(/g, '%2528')
269
+ .replace(/\)/g, '%2529')
269
270
  }
270
271
 
271
272
  // A decimal literal, not `Number()`: the API compares against a number column,
@@ -1,6 +1,7 @@
1
1
  import { ofetch } from 'ofetch'
2
2
  import { useComponentsConfig, type PluginConfig } from '../config'
3
- import type { GenericFilter } from '../types/visualizations'
3
+ import { encodeConditionValue } from './tabular'
4
+ import type { AndFilters, Filter, GenericFilter, OrFilters } from '../types/visualizations'
4
5
  import type { SortConfig, TabularProfileResponse } from '../components/TabularExplorer/types'
5
6
 
6
7
  export type { SortConfig }
@@ -56,41 +57,67 @@ export async function fetchTabularData(config: PluginConfig, options: FetchTabul
56
57
  return await ofetch<TabularDataResponse>(url)
57
58
  }
58
59
 
59
- function buildFilterQuery(filters: GenericFilter): string {
60
+ /**
61
+ * Serialize a filter tree into tabular-api query params.
62
+ * - `Filter` → `column__condition=value` (ANDed with the other params)
63
+ * - root `AndFilters` → one param per child; `OrFilters` children become `or=(...)` params
64
+ * - root `OrFilters` → a single `or=(...)` param, with `AndFilters` children nested as `and(...)`
65
+ * (nested-condition grammar: `.` instead of `=`, no value for isnull/isnotnull)
66
+ */
67
+ export function buildFilterQuery(filters: GenericFilter): string {
60
68
  const params: Array<string> = []
61
- if ('filters' in filters) {
62
- for (const filter of filters.filters) {
63
- if ('filters' in filter) {
64
- params.push(buildFilterQuery(filter))
69
+ if (filters._cls === 'Filter') {
70
+ const param = serializeParam(filters)
71
+ if (param) params.push(param)
72
+ }
73
+ else if (filters._cls === 'AndFilters') {
74
+ for (const child of filters.filters) {
75
+ if (child._cls === 'Filter') {
76
+ const param = serializeParam(child)
77
+ if (param) params.push(param)
65
78
  }
66
79
  else {
67
- if (filter.condition === 'is_null') {
68
- params.push(`${encodeURIComponent(filter.column)}__isnull`)
69
- }
70
- else if (filter.condition === 'is_not_null') {
71
- params.push(`${encodeURIComponent(filter.column)}__isnotnull`)
72
- }
73
- else if (filter.value !== null && filter.value !== undefined && filter.value !== '') {
74
- params.push(`${encodeURIComponent(filter.column)}__${encodeURIComponent(filter.condition)}=${encodeURIComponent(filter.value)}`)
75
- }
80
+ // child is an OrFilters group: serializeGroup returns `or(...)`, the param is `or=(...)`
81
+ const nested = serializeGroup(child)
82
+ if (nested) params.push(`or=${nested.slice(2)}`)
76
83
  }
77
84
  }
78
85
  }
79
86
  else {
80
- const filter = filters
81
- if (filter.condition === 'is_null') {
82
- params.push(`${encodeURIComponent(filter.column)}__isnull`)
83
- }
84
- else if (filter.condition === 'is_not_null') {
85
- params.push(`${encodeURIComponent(filter.column)}__isnotnull`)
86
- }
87
- else if (filter.value !== null && filter.value !== undefined && filter.value !== '') {
88
- params.push(`${encodeURIComponent(filter.column)}__${encodeURIComponent(filter.condition)}=${encodeURIComponent(filter.value)}`)
89
- }
87
+ const nested = serializeGroup(filters)
88
+ if (nested) params.push(`or=${nested.slice(2)}`)
90
89
  }
91
90
  return params.join('&')
92
91
  }
93
92
 
93
+ /** `column__condition=value` param for a top-level (ANDed) filter, null when empty */
94
+ function serializeParam(filter: Filter): string | null {
95
+ const column = encodeURIComponent(filter.column)
96
+ if (filter.condition === 'is_null') return `${column}__isnull`
97
+ if (filter.condition === 'is_not_null') return `${column}__isnotnull`
98
+ if (filter.value === null || filter.value === undefined || filter.value === '') return null
99
+ return `${column}__${encodeURIComponent(filter.condition)}=${encodeURIComponent(filter.value)}`
100
+ }
101
+
102
+ /** `column__condition.value` condition for the `or(...)` / `and(...)` grammar, null when empty */
103
+ function serializeCondition(filter: Filter): string | null {
104
+ const column = encodeURIComponent(filter.column)
105
+ if (filter.condition === 'is_null') return `${column}__isnull`
106
+ if (filter.condition === 'is_not_null') return `${column}__isnotnull`
107
+ if (filter.value === null || filter.value === undefined || filter.value === '') return null
108
+ return `${column}__${encodeURIComponent(filter.condition)}.${encodeConditionValue(filter.value)}`
109
+ }
110
+
111
+ /** `or(a,b,...)` / `and(a,b,...)` for the nested-condition grammar, null when empty */
112
+ function serializeGroup(group: AndFilters | OrFilters): string | null {
113
+ const operator = group._cls === 'OrFilters' ? 'or' : 'and'
114
+ const conditions = group.filters
115
+ .map(child => ('filters' in child ? serializeGroup(child) : serializeCondition(child)))
116
+ .filter((condition): condition is string => condition !== null)
117
+ if (conditions.length === 0) return null
118
+ return `${operator}(${conditions.join(',')})`
119
+ }
120
+
94
121
  /**
95
122
  * Call Tabular-api to get table content
96
123
  */
package/src/main.ts CHANGED
@@ -23,7 +23,7 @@ import type { Site } from './types/site'
23
23
  import type { Weight, WellType } from './types/ui'
24
24
  import type { User, UserReference } from './types/users'
25
25
  import type { Report, ReportSubject, ReportReason } from './types/reports'
26
- import type { Chart, ChartForm, ChartForApi, FilterCondition, Filter, AndFilters, GenericFilter, XAxisType, XAxisSortBy, SortDirection, XAxis, XAxisForm, UnitPosition, YAxis, DataSeriesType, DataSeries, DataSeriesForm, CombinedSort, ColumnDefinition, ColumnsDefinition } from './types/visualizations'
26
+ import type { Chart, ChartForm, ChartForApi, FilterCondition, Filter, AndFilters, OrFilters, GenericFilter, XAxisType, XAxisSortBy, SortDirection, XAxis, XAxisForm, UnitPosition, YAxis, DataSeriesType, DataSeries, DataSeriesForm, CombinedSort, ColumnDefinition, ColumnsDefinition } from './types/visualizations'
27
27
  import type { ColumnFilters, ColumnType, TabularRow } from './components/TabularExplorer/types'
28
28
  import type { GlobalSearchConfig, SearchType, SearchTypeConfig, SortOption, HiddenFilter, BuiltInFilterKey, DatasetSearchConfig, DatasetSearchFilters, DataserviceSearchConfig, DataserviceSearchFilters, ReuseSearchConfig, ReuseSearchFilters, OrganizationSearchConfig, OrganizationSearchFilters, TopicSearchConfig, TopicSearchFilters } from './types/search'
29
29
  import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
@@ -272,6 +272,7 @@ export type {
272
272
  FilterCondition,
273
273
  Filter,
274
274
  AndFilters,
275
+ OrFilters,
275
276
  GenericFilter,
276
277
  XAxisType,
277
278
  XAxisSortBy,
@@ -12,10 +12,15 @@ export type Filter = {
12
12
 
13
13
  export type AndFilters = {
14
14
  _cls: 'AndFilters'
15
+ filters: Array<Filter | OrFilters>
16
+ }
17
+
18
+ export type OrFilters = {
19
+ _cls: 'OrFilters'
15
20
  filters: Array<Filter | AndFilters>
16
21
  }
17
22
 
18
- export type GenericFilter = Filter | AndFilters
23
+ export type GenericFilter = Filter | AndFilters | OrFilters
19
24
 
20
25
  export type XAxisType = 'discrete' | 'continuous'
21
26