strapi-plugin-hubspot 0.2.1 → 0.3.0
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/README.md +24 -3
- package/dist/_chunks/{HubspotPropertyInput-C7Wcw8ee.mjs → HubspotPropertyInput-C3KbDHYP.mjs} +1 -1
- package/dist/_chunks/{HubspotPropertyInput-BVFRtt49.js → HubspotPropertyInput-C9PpYYgj.js} +1 -1
- package/dist/_chunks/{Settings-B5w7LAsH.mjs → Settings-DGJ_mmlw.mjs} +1 -1
- package/dist/_chunks/{Settings-TGB0MOHr.js → Settings-Lj49S5fO.js} +1 -1
- package/dist/_chunks/index-Bq1tS5h6.js +71 -0
- package/dist/_chunks/index-lx7um1Yx.mjs +72 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +42 -14
- package/dist/server/index.mjs +42 -14
- package/dist/server/src/index.d.ts +6 -0
- package/dist/server/src/properties.d.ts +34 -3
- package/package.json +1 -1
- package/dist/_chunks/index-BpZtcsPM.mjs +0 -42
- package/dist/_chunks/index-DgsAHPui.js +0 -41
package/README.md
CHANGED
|
@@ -44,8 +44,10 @@ Société · Nombre d'employés (numberofemployees)
|
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
**The list narrows to the object you picked.** Point the field at the sibling
|
|
47
|
-
that holds the object
|
|
48
|
-
|
|
47
|
+
that holds the object — from the Content-Type Builder, under *HubSpot → Object
|
|
48
|
+
field* — and choosing *Contact* leaves only contact properties. The prefix
|
|
49
|
+
disappears, since it is no longer telling you anything. In `schema.json` that
|
|
50
|
+
setting reads:
|
|
49
51
|
|
|
50
52
|
```json
|
|
51
53
|
{
|
|
@@ -69,7 +71,25 @@ entry before it is written, at any depth — steps, repeatable components, dynam
|
|
|
69
71
|
zones. An invalid mapping is refused with a message that says which property and
|
|
70
72
|
why:
|
|
71
73
|
|
|
72
|
-
>
|
|
74
|
+
> Invalid HubSpot mapping — "name" exists on company, not on contact
|
|
75
|
+
|
|
76
|
+
It catches four things:
|
|
77
|
+
|
|
78
|
+
| Code | Case |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `unknown` | The property doesn't exist in the portal |
|
|
81
|
+
| `wrong-object` | It exists, but on another object |
|
|
82
|
+
| `whitespace` | It exists, but the stored value has surrounding spaces |
|
|
83
|
+
| `bad-option` | The field offers a choice the enumeration doesn't accept |
|
|
84
|
+
|
|
85
|
+
That last one closes a real hole: HubSpot refuses a value outside an
|
|
86
|
+
enumeration exactly as hard as it refuses an unknown property, so a select whose
|
|
87
|
+
choices drifted from the CRM fails at send time with nothing to show for it.
|
|
88
|
+
Point `optionsField` at the repeatable holding the choices (default `options`,
|
|
89
|
+
each `{ value?, label? }`) and they are checked too.
|
|
90
|
+
|
|
91
|
+
Each problem is also carried as a structured code in the error's `details`, so a
|
|
92
|
+
host app can localize the message instead of parsing the sentence.
|
|
73
93
|
|
|
74
94
|
### A settings screen
|
|
75
95
|
|
|
@@ -111,6 +131,7 @@ export default ({ env }) => ({
|
|
|
111
131
|
uid: "api::form.form",
|
|
112
132
|
objectField: "hsObject", // holds "contact" | "company"
|
|
113
133
|
propertyField: "hsProperty", // holds the property name
|
|
134
|
+
optionsField: "options", // optional — the field's choices
|
|
114
135
|
},
|
|
115
136
|
],
|
|
116
137
|
},
|
package/dist/_chunks/{HubspotPropertyInput-C7Wcw8ee.mjs → HubspotPropertyInput-C3KbDHYP.mjs}
RENAMED
|
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Field, Flex, Loader, Combobox, ComboboxOption, Link } from "@strapi/design-system";
|
|
4
4
|
import { useFetchClient, useField } from "@strapi/strapi/admin";
|
|
5
|
-
import { P as PLUGIN_ID } from "./index-
|
|
5
|
+
import { P as PLUGIN_ID } from "./index-lx7um1Yx.mjs";
|
|
6
6
|
const LABELS = {
|
|
7
7
|
contact: "Contact",
|
|
8
8
|
company: "Société",
|
|
@@ -4,7 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const React = require("react");
|
|
5
5
|
const designSystem = require("@strapi/design-system");
|
|
6
6
|
const admin = require("@strapi/strapi/admin");
|
|
7
|
-
const index = require("./index-
|
|
7
|
+
const index = require("./index-Bq1tS5h6.js");
|
|
8
8
|
function _interopNamespace(e) {
|
|
9
9
|
if (e && e.__esModule) return e;
|
|
10
10
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -2,7 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Box, Loader, Flex, Typography, Badge, Field, Button } from "@strapi/design-system";
|
|
4
4
|
import { useFetchClient } from "@strapi/strapi/admin";
|
|
5
|
-
import { P as PLUGIN_ID } from "./index-
|
|
5
|
+
import { P as PLUGIN_ID } from "./index-lx7um1Yx.mjs";
|
|
6
6
|
const SOURCE_LABEL = {
|
|
7
7
|
settings: "saisie ici",
|
|
8
8
|
config: "config/plugins.ts",
|
|
@@ -4,7 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const React = require("react");
|
|
5
5
|
const designSystem = require("@strapi/design-system");
|
|
6
6
|
const admin = require("@strapi/strapi/admin");
|
|
7
|
-
const index = require("./index-
|
|
7
|
+
const index = require("./index-Bq1tS5h6.js");
|
|
8
8
|
function _interopNamespace(e) {
|
|
9
9
|
if (e && e.__esModule) return e;
|
|
10
10
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const PLUGIN_ID = "hubspot";
|
|
3
|
+
const index = {
|
|
4
|
+
register(app) {
|
|
5
|
+
app.customFields.register({
|
|
6
|
+
name: "property",
|
|
7
|
+
pluginId: PLUGIN_ID,
|
|
8
|
+
type: "string",
|
|
9
|
+
intlLabel: {
|
|
10
|
+
id: `${PLUGIN_ID}.property.label`,
|
|
11
|
+
defaultMessage: "Propriété HubSpot"
|
|
12
|
+
},
|
|
13
|
+
intlDescription: {
|
|
14
|
+
id: `${PLUGIN_ID}.property.description`,
|
|
15
|
+
defaultMessage: "Choisie dans les propriétés réelles du portail"
|
|
16
|
+
},
|
|
17
|
+
components: {
|
|
18
|
+
Input: async () => Promise.resolve().then(() => require("./HubspotPropertyInput-C9PpYYgj.js"))
|
|
19
|
+
},
|
|
20
|
+
// Exposed in the Content-Type Builder. Without this, `objectField` could
|
|
21
|
+
// only be set by hand-editing schema.json — which is not how anyone
|
|
22
|
+
// installing from npm adds a field, so the filtering would be unreachable.
|
|
23
|
+
options: {
|
|
24
|
+
base: [
|
|
25
|
+
{
|
|
26
|
+
sectionTitle: {
|
|
27
|
+
id: `${PLUGIN_ID}.options.section`,
|
|
28
|
+
defaultMessage: "HubSpot"
|
|
29
|
+
},
|
|
30
|
+
items: [
|
|
31
|
+
{
|
|
32
|
+
name: "options.objectField",
|
|
33
|
+
type: "text",
|
|
34
|
+
intlLabel: {
|
|
35
|
+
id: `${PLUGIN_ID}.options.objectField.label`,
|
|
36
|
+
defaultMessage: "Object field"
|
|
37
|
+
},
|
|
38
|
+
description: {
|
|
39
|
+
id: `${PLUGIN_ID}.options.objectField.description`,
|
|
40
|
+
defaultMessage: "Name of the sibling field holding the HubSpot object (e.g. hsObject). Leave empty to list every object's properties."
|
|
41
|
+
},
|
|
42
|
+
placeholder: {
|
|
43
|
+
id: `${PLUGIN_ID}.options.objectField.placeholder`,
|
|
44
|
+
defaultMessage: "hsObject"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
app.createSettingSection(
|
|
53
|
+
{
|
|
54
|
+
id: PLUGIN_ID,
|
|
55
|
+
intlLabel: { id: `${PLUGIN_ID}.settings.section`, defaultMessage: "HubSpot" }
|
|
56
|
+
},
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
intlLabel: { id: `${PLUGIN_ID}.settings.link`, defaultMessage: "Configuration" },
|
|
60
|
+
id: `${PLUGIN_ID}-settings`,
|
|
61
|
+
to: `/settings/${PLUGIN_ID}`,
|
|
62
|
+
permissions: [],
|
|
63
|
+
Component: async () => (await Promise.resolve().then(() => require("./Settings-Lj49S5fO.js"))).default
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
);
|
|
67
|
+
app.registerPlugin({ id: PLUGIN_ID, name: PLUGIN_ID });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.PLUGIN_ID = PLUGIN_ID;
|
|
71
|
+
exports.index = index;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const PLUGIN_ID = "hubspot";
|
|
2
|
+
const index = {
|
|
3
|
+
register(app) {
|
|
4
|
+
app.customFields.register({
|
|
5
|
+
name: "property",
|
|
6
|
+
pluginId: PLUGIN_ID,
|
|
7
|
+
type: "string",
|
|
8
|
+
intlLabel: {
|
|
9
|
+
id: `${PLUGIN_ID}.property.label`,
|
|
10
|
+
defaultMessage: "Propriété HubSpot"
|
|
11
|
+
},
|
|
12
|
+
intlDescription: {
|
|
13
|
+
id: `${PLUGIN_ID}.property.description`,
|
|
14
|
+
defaultMessage: "Choisie dans les propriétés réelles du portail"
|
|
15
|
+
},
|
|
16
|
+
components: {
|
|
17
|
+
Input: async () => import("./HubspotPropertyInput-C3KbDHYP.mjs")
|
|
18
|
+
},
|
|
19
|
+
// Exposed in the Content-Type Builder. Without this, `objectField` could
|
|
20
|
+
// only be set by hand-editing schema.json — which is not how anyone
|
|
21
|
+
// installing from npm adds a field, so the filtering would be unreachable.
|
|
22
|
+
options: {
|
|
23
|
+
base: [
|
|
24
|
+
{
|
|
25
|
+
sectionTitle: {
|
|
26
|
+
id: `${PLUGIN_ID}.options.section`,
|
|
27
|
+
defaultMessage: "HubSpot"
|
|
28
|
+
},
|
|
29
|
+
items: [
|
|
30
|
+
{
|
|
31
|
+
name: "options.objectField",
|
|
32
|
+
type: "text",
|
|
33
|
+
intlLabel: {
|
|
34
|
+
id: `${PLUGIN_ID}.options.objectField.label`,
|
|
35
|
+
defaultMessage: "Object field"
|
|
36
|
+
},
|
|
37
|
+
description: {
|
|
38
|
+
id: `${PLUGIN_ID}.options.objectField.description`,
|
|
39
|
+
defaultMessage: "Name of the sibling field holding the HubSpot object (e.g. hsObject). Leave empty to list every object's properties."
|
|
40
|
+
},
|
|
41
|
+
placeholder: {
|
|
42
|
+
id: `${PLUGIN_ID}.options.objectField.placeholder`,
|
|
43
|
+
defaultMessage: "hsObject"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
app.createSettingSection(
|
|
52
|
+
{
|
|
53
|
+
id: PLUGIN_ID,
|
|
54
|
+
intlLabel: { id: `${PLUGIN_ID}.settings.section`, defaultMessage: "HubSpot" }
|
|
55
|
+
},
|
|
56
|
+
[
|
|
57
|
+
{
|
|
58
|
+
intlLabel: { id: `${PLUGIN_ID}.settings.link`, defaultMessage: "Configuration" },
|
|
59
|
+
id: `${PLUGIN_ID}-settings`,
|
|
60
|
+
to: `/settings/${PLUGIN_ID}`,
|
|
61
|
+
permissions: [],
|
|
62
|
+
Component: async () => (await import("./Settings-DGJ_mmlw.mjs")).default
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
);
|
|
66
|
+
app.registerPlugin({ id: PLUGIN_ID, name: PLUGIN_ID });
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export {
|
|
70
|
+
PLUGIN_ID as P,
|
|
71
|
+
index as i
|
|
72
|
+
};
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -69,14 +69,14 @@ async function loadSchema(strapi, apiKey, objects, { force = false } = {}) {
|
|
|
69
69
|
for (const entry of settled) {
|
|
70
70
|
if ("error" in entry && entry.error) {
|
|
71
71
|
unavailable.push({ object: entry.object.name, reason: entry.error });
|
|
72
|
-
strapi.log.warn(`[hubspot] ${entry.object.name}
|
|
72
|
+
strapi.log.warn(`[hubspot] ${entry.object.name} unreadable — ${entry.error}`);
|
|
73
73
|
continue;
|
|
74
74
|
}
|
|
75
75
|
available.push(entry.object.name);
|
|
76
76
|
properties.push(...entry.properties ?? []);
|
|
77
77
|
}
|
|
78
78
|
if (!available.length) {
|
|
79
|
-
throw new Error(unavailable[0]?.reason ?? "
|
|
79
|
+
throw new Error(unavailable[0]?.reason ?? "No readable object");
|
|
80
80
|
}
|
|
81
81
|
properties.sort(
|
|
82
82
|
(a, b) => a.object.localeCompare(b.object) || a.label.localeCompare(b.label)
|
|
@@ -100,16 +100,35 @@ async function loadSchema(strapi, apiKey, objects, { force = false } = {}) {
|
|
|
100
100
|
function clearCache() {
|
|
101
101
|
cache = null;
|
|
102
102
|
}
|
|
103
|
-
function
|
|
104
|
-
const
|
|
103
|
+
function checkMapping(properties, mapping) {
|
|
104
|
+
const { object, property } = mapping;
|
|
105
|
+
const trimmed = property.trim();
|
|
105
106
|
if (!trimmed) return null;
|
|
106
107
|
const match = properties.find((p) => p.object === object && p.name === trimmed);
|
|
107
|
-
if (match)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
if (!match) {
|
|
109
|
+
const elsewhere = properties.find((p) => p.name === trimmed);
|
|
110
|
+
return elsewhere ? { code: "wrong-object", property: trimmed, object, actualObject: elsewhere.object } : { code: "unknown", property: trimmed, object };
|
|
111
|
+
}
|
|
112
|
+
if (trimmed !== property) return { code: "whitespace", property, object };
|
|
113
|
+
if (match.type === "enumeration" && match.options.length && mapping.values?.length) {
|
|
114
|
+
const stray = mapping.values.filter((v) => v && !match.options.includes(v));
|
|
115
|
+
if (stray.length) {
|
|
116
|
+
return { code: "bad-option", property: trimmed, object, values: stray };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function describeProblem(problem) {
|
|
122
|
+
switch (problem.code) {
|
|
123
|
+
case "unknown":
|
|
124
|
+
return `"${problem.property}" does not exist on ${problem.object} in this HubSpot portal`;
|
|
125
|
+
case "wrong-object":
|
|
126
|
+
return `"${problem.property}" exists on ${problem.actualObject}, not on ${problem.object}`;
|
|
127
|
+
case "whitespace":
|
|
128
|
+
return `"${problem.property}" has surrounding whitespace`;
|
|
129
|
+
case "bad-option":
|
|
130
|
+
return `"${problem.property}" does not accept ${problem.values.map((v) => `"${v}"`).join(", ")}`;
|
|
111
131
|
}
|
|
112
|
-
return `« ${trimmed} » n'existe pas dans ce portail HubSpot`;
|
|
113
132
|
}
|
|
114
133
|
function resolveObjects(configured) {
|
|
115
134
|
if (!Array.isArray(configured) || !configured.length) {
|
|
@@ -185,7 +204,7 @@ const controllers = {
|
|
|
185
204
|
ctx.body = { configured: true, ...schema };
|
|
186
205
|
} catch (err) {
|
|
187
206
|
strapi.log.error(`[hubspot] ${err.message}`);
|
|
188
|
-
ctx.throw(502, "
|
|
207
|
+
ctx.throw(502, "Cannot reach HubSpot — check the API key.");
|
|
189
208
|
}
|
|
190
209
|
}
|
|
191
210
|
}),
|
|
@@ -231,9 +250,16 @@ function collectMappings(node, target, found = []) {
|
|
|
231
250
|
const property = obj[target.propertyField];
|
|
232
251
|
if (typeof property === "string" && property.trim()) {
|
|
233
252
|
const object = obj[target.objectField];
|
|
253
|
+
const rawOptions = obj[target.optionsField || "options"];
|
|
254
|
+
const values = Array.isArray(rawOptions) ? rawOptions.map((o) => {
|
|
255
|
+
const opt = o ?? {};
|
|
256
|
+
const v = typeof opt.value === "string" && opt.value.trim() ? opt.value : opt.label;
|
|
257
|
+
return typeof v === "string" ? v.trim() : "";
|
|
258
|
+
}).filter(Boolean) : void 0;
|
|
234
259
|
found.push({
|
|
235
260
|
object: typeof object === "string" && object ? object : "contact",
|
|
236
|
-
property
|
|
261
|
+
property,
|
|
262
|
+
values
|
|
237
263
|
});
|
|
238
264
|
}
|
|
239
265
|
for (const value of Object.values(obj)) collectMappings(value, target, found);
|
|
@@ -269,13 +295,15 @@ const index = {
|
|
|
269
295
|
resolveObjects(strapi.plugin("hubspot").config("objects", []))
|
|
270
296
|
);
|
|
271
297
|
} catch {
|
|
272
|
-
strapi.log.warn("[hubspot]
|
|
298
|
+
strapi.log.warn("[hubspot] schema unavailable — validation skipped");
|
|
273
299
|
return next();
|
|
274
300
|
}
|
|
275
|
-
const problems = mappings.map((m) =>
|
|
301
|
+
const problems = mappings.map((m) => checkMapping(schema.properties, m)).filter((p) => Boolean(p));
|
|
276
302
|
if (problems.length) {
|
|
303
|
+
const sentences = [...new Set(problems.map(describeProblem))];
|
|
277
304
|
throw new utils.errors.ValidationError(
|
|
278
|
-
`
|
|
305
|
+
`Invalid HubSpot mapping — ${sentences.join("; ")}`,
|
|
306
|
+
{ problems }
|
|
279
307
|
);
|
|
280
308
|
}
|
|
281
309
|
return next();
|
package/dist/server/index.mjs
CHANGED
|
@@ -68,14 +68,14 @@ async function loadSchema(strapi, apiKey, objects, { force = false } = {}) {
|
|
|
68
68
|
for (const entry of settled) {
|
|
69
69
|
if ("error" in entry && entry.error) {
|
|
70
70
|
unavailable.push({ object: entry.object.name, reason: entry.error });
|
|
71
|
-
strapi.log.warn(`[hubspot] ${entry.object.name}
|
|
71
|
+
strapi.log.warn(`[hubspot] ${entry.object.name} unreadable — ${entry.error}`);
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
74
|
available.push(entry.object.name);
|
|
75
75
|
properties.push(...entry.properties ?? []);
|
|
76
76
|
}
|
|
77
77
|
if (!available.length) {
|
|
78
|
-
throw new Error(unavailable[0]?.reason ?? "
|
|
78
|
+
throw new Error(unavailable[0]?.reason ?? "No readable object");
|
|
79
79
|
}
|
|
80
80
|
properties.sort(
|
|
81
81
|
(a, b) => a.object.localeCompare(b.object) || a.label.localeCompare(b.label)
|
|
@@ -99,16 +99,35 @@ async function loadSchema(strapi, apiKey, objects, { force = false } = {}) {
|
|
|
99
99
|
function clearCache() {
|
|
100
100
|
cache = null;
|
|
101
101
|
}
|
|
102
|
-
function
|
|
103
|
-
const
|
|
102
|
+
function checkMapping(properties, mapping) {
|
|
103
|
+
const { object, property } = mapping;
|
|
104
|
+
const trimmed = property.trim();
|
|
104
105
|
if (!trimmed) return null;
|
|
105
106
|
const match = properties.find((p) => p.object === object && p.name === trimmed);
|
|
106
|
-
if (match)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
if (!match) {
|
|
108
|
+
const elsewhere = properties.find((p) => p.name === trimmed);
|
|
109
|
+
return elsewhere ? { code: "wrong-object", property: trimmed, object, actualObject: elsewhere.object } : { code: "unknown", property: trimmed, object };
|
|
110
|
+
}
|
|
111
|
+
if (trimmed !== property) return { code: "whitespace", property, object };
|
|
112
|
+
if (match.type === "enumeration" && match.options.length && mapping.values?.length) {
|
|
113
|
+
const stray = mapping.values.filter((v) => v && !match.options.includes(v));
|
|
114
|
+
if (stray.length) {
|
|
115
|
+
return { code: "bad-option", property: trimmed, object, values: stray };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function describeProblem(problem) {
|
|
121
|
+
switch (problem.code) {
|
|
122
|
+
case "unknown":
|
|
123
|
+
return `"${problem.property}" does not exist on ${problem.object} in this HubSpot portal`;
|
|
124
|
+
case "wrong-object":
|
|
125
|
+
return `"${problem.property}" exists on ${problem.actualObject}, not on ${problem.object}`;
|
|
126
|
+
case "whitespace":
|
|
127
|
+
return `"${problem.property}" has surrounding whitespace`;
|
|
128
|
+
case "bad-option":
|
|
129
|
+
return `"${problem.property}" does not accept ${problem.values.map((v) => `"${v}"`).join(", ")}`;
|
|
110
130
|
}
|
|
111
|
-
return `« ${trimmed} » n'existe pas dans ce portail HubSpot`;
|
|
112
131
|
}
|
|
113
132
|
function resolveObjects(configured) {
|
|
114
133
|
if (!Array.isArray(configured) || !configured.length) {
|
|
@@ -184,7 +203,7 @@ const controllers = {
|
|
|
184
203
|
ctx.body = { configured: true, ...schema };
|
|
185
204
|
} catch (err) {
|
|
186
205
|
strapi.log.error(`[hubspot] ${err.message}`);
|
|
187
|
-
ctx.throw(502, "
|
|
206
|
+
ctx.throw(502, "Cannot reach HubSpot — check the API key.");
|
|
188
207
|
}
|
|
189
208
|
}
|
|
190
209
|
}),
|
|
@@ -230,9 +249,16 @@ function collectMappings(node, target, found = []) {
|
|
|
230
249
|
const property = obj[target.propertyField];
|
|
231
250
|
if (typeof property === "string" && property.trim()) {
|
|
232
251
|
const object = obj[target.objectField];
|
|
252
|
+
const rawOptions = obj[target.optionsField || "options"];
|
|
253
|
+
const values = Array.isArray(rawOptions) ? rawOptions.map((o) => {
|
|
254
|
+
const opt = o ?? {};
|
|
255
|
+
const v = typeof opt.value === "string" && opt.value.trim() ? opt.value : opt.label;
|
|
256
|
+
return typeof v === "string" ? v.trim() : "";
|
|
257
|
+
}).filter(Boolean) : void 0;
|
|
233
258
|
found.push({
|
|
234
259
|
object: typeof object === "string" && object ? object : "contact",
|
|
235
|
-
property
|
|
260
|
+
property,
|
|
261
|
+
values
|
|
236
262
|
});
|
|
237
263
|
}
|
|
238
264
|
for (const value of Object.values(obj)) collectMappings(value, target, found);
|
|
@@ -268,13 +294,15 @@ const index = {
|
|
|
268
294
|
resolveObjects(strapi.plugin("hubspot").config("objects", []))
|
|
269
295
|
);
|
|
270
296
|
} catch {
|
|
271
|
-
strapi.log.warn("[hubspot]
|
|
297
|
+
strapi.log.warn("[hubspot] schema unavailable — validation skipped");
|
|
272
298
|
return next();
|
|
273
299
|
}
|
|
274
|
-
const problems = mappings.map((m) =>
|
|
300
|
+
const problems = mappings.map((m) => checkMapping(schema.properties, m)).filter((p) => Boolean(p));
|
|
275
301
|
if (problems.length) {
|
|
302
|
+
const sentences = [...new Set(problems.map(describeProblem))];
|
|
276
303
|
throw new errors.ValidationError(
|
|
277
|
-
`
|
|
304
|
+
`Invalid HubSpot mapping — ${sentences.join("; ")}`,
|
|
305
|
+
{ problems }
|
|
278
306
|
);
|
|
279
307
|
}
|
|
280
308
|
return next();
|
|
@@ -18,6 +18,12 @@ interface ValidateTarget {
|
|
|
18
18
|
uid: string;
|
|
19
19
|
objectField: string;
|
|
20
20
|
propertyField: string;
|
|
21
|
+
/**
|
|
22
|
+
* Repeatable holding the values a field can submit (default `options`), each
|
|
23
|
+
* `{ value?, label? }`. Checked against enumeration properties: a select whose
|
|
24
|
+
* choices drifted from HubSpot fails exactly like an unknown property.
|
|
25
|
+
*/
|
|
26
|
+
optionsField?: string;
|
|
21
27
|
}
|
|
22
28
|
declare const _default: {
|
|
23
29
|
config: {
|
|
@@ -50,10 +50,41 @@ export declare function loadSchema(strapi: Core.Strapi, apiKey: string, objects:
|
|
|
50
50
|
}): Promise<Schema>;
|
|
51
51
|
/** Drop the cache — used after the API key or the object list changes. */
|
|
52
52
|
export declare function clearCache(): void;
|
|
53
|
+
/** A mapping the portal would reject, and why. */
|
|
54
|
+
export type Problem = {
|
|
55
|
+
code: "unknown";
|
|
56
|
+
property: string;
|
|
57
|
+
object: string;
|
|
58
|
+
} | {
|
|
59
|
+
code: "wrong-object";
|
|
60
|
+
property: string;
|
|
61
|
+
object: string;
|
|
62
|
+
actualObject: string;
|
|
63
|
+
} | {
|
|
64
|
+
code: "whitespace";
|
|
65
|
+
property: string;
|
|
66
|
+
object: string;
|
|
67
|
+
} | {
|
|
68
|
+
code: "bad-option";
|
|
69
|
+
property: string;
|
|
70
|
+
object: string;
|
|
71
|
+
values: string[];
|
|
72
|
+
};
|
|
73
|
+
export interface Mapping {
|
|
74
|
+
object: string;
|
|
75
|
+
property: string;
|
|
76
|
+
/** Values the field can submit — checked against enumeration properties. */
|
|
77
|
+
values?: string[];
|
|
78
|
+
}
|
|
53
79
|
/**
|
|
54
|
-
* Why a
|
|
55
|
-
*
|
|
80
|
+
* Why a mapping can't be written, or `null` when it's fine.
|
|
81
|
+
*
|
|
82
|
+
* Returns a structured code rather than a sentence: the message is rendered by
|
|
83
|
+
* the caller, so the wording stays in one place and stays translatable. The
|
|
84
|
+
* codes are also carried in the ValidationError details, for anyone localizing.
|
|
56
85
|
*/
|
|
57
|
-
export declare function
|
|
86
|
+
export declare function checkMapping(properties: HsProperty[], mapping: Mapping): Problem | null;
|
|
87
|
+
/** English rendering of a problem — the package's language. */
|
|
88
|
+
export declare function describeProblem(problem: Problem): string;
|
|
58
89
|
/** Resolve the configured object list, accepting names or full definitions. */
|
|
59
90
|
export declare function resolveObjects(configured: unknown): HsObjectDef[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-hubspot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "HubSpot integration for Strapi — pick CRM properties from a searchable list filtered by object, and catch bad mappings before they reach the API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
const PLUGIN_ID = "hubspot";
|
|
2
|
-
const index = {
|
|
3
|
-
register(app) {
|
|
4
|
-
app.customFields.register({
|
|
5
|
-
name: "property",
|
|
6
|
-
pluginId: PLUGIN_ID,
|
|
7
|
-
type: "string",
|
|
8
|
-
intlLabel: {
|
|
9
|
-
id: `${PLUGIN_ID}.property.label`,
|
|
10
|
-
defaultMessage: "Propriété HubSpot"
|
|
11
|
-
},
|
|
12
|
-
intlDescription: {
|
|
13
|
-
id: `${PLUGIN_ID}.property.description`,
|
|
14
|
-
defaultMessage: "Choisie dans les propriétés réelles du portail"
|
|
15
|
-
},
|
|
16
|
-
components: {
|
|
17
|
-
Input: async () => import("./HubspotPropertyInput-C7Wcw8ee.mjs")
|
|
18
|
-
},
|
|
19
|
-
options: {}
|
|
20
|
-
});
|
|
21
|
-
app.createSettingSection(
|
|
22
|
-
{
|
|
23
|
-
id: PLUGIN_ID,
|
|
24
|
-
intlLabel: { id: `${PLUGIN_ID}.settings.section`, defaultMessage: "HubSpot" }
|
|
25
|
-
},
|
|
26
|
-
[
|
|
27
|
-
{
|
|
28
|
-
intlLabel: { id: `${PLUGIN_ID}.settings.link`, defaultMessage: "Configuration" },
|
|
29
|
-
id: `${PLUGIN_ID}-settings`,
|
|
30
|
-
to: `/settings/${PLUGIN_ID}`,
|
|
31
|
-
permissions: [],
|
|
32
|
-
Component: async () => (await import("./Settings-B5w7LAsH.mjs")).default
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
);
|
|
36
|
-
app.registerPlugin({ id: PLUGIN_ID, name: PLUGIN_ID });
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
export {
|
|
40
|
-
PLUGIN_ID as P,
|
|
41
|
-
index as i
|
|
42
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const PLUGIN_ID = "hubspot";
|
|
3
|
-
const index = {
|
|
4
|
-
register(app) {
|
|
5
|
-
app.customFields.register({
|
|
6
|
-
name: "property",
|
|
7
|
-
pluginId: PLUGIN_ID,
|
|
8
|
-
type: "string",
|
|
9
|
-
intlLabel: {
|
|
10
|
-
id: `${PLUGIN_ID}.property.label`,
|
|
11
|
-
defaultMessage: "Propriété HubSpot"
|
|
12
|
-
},
|
|
13
|
-
intlDescription: {
|
|
14
|
-
id: `${PLUGIN_ID}.property.description`,
|
|
15
|
-
defaultMessage: "Choisie dans les propriétés réelles du portail"
|
|
16
|
-
},
|
|
17
|
-
components: {
|
|
18
|
-
Input: async () => Promise.resolve().then(() => require("./HubspotPropertyInput-BVFRtt49.js"))
|
|
19
|
-
},
|
|
20
|
-
options: {}
|
|
21
|
-
});
|
|
22
|
-
app.createSettingSection(
|
|
23
|
-
{
|
|
24
|
-
id: PLUGIN_ID,
|
|
25
|
-
intlLabel: { id: `${PLUGIN_ID}.settings.section`, defaultMessage: "HubSpot" }
|
|
26
|
-
},
|
|
27
|
-
[
|
|
28
|
-
{
|
|
29
|
-
intlLabel: { id: `${PLUGIN_ID}.settings.link`, defaultMessage: "Configuration" },
|
|
30
|
-
id: `${PLUGIN_ID}-settings`,
|
|
31
|
-
to: `/settings/${PLUGIN_ID}`,
|
|
32
|
-
permissions: [],
|
|
33
|
-
Component: async () => (await Promise.resolve().then(() => require("./Settings-TGB0MOHr.js"))).default
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
);
|
|
37
|
-
app.registerPlugin({ id: PLUGIN_ID, name: PLUGIN_ID });
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
exports.PLUGIN_ID = PLUGIN_ID;
|
|
41
|
-
exports.index = index;
|