@camunda/camunda-composite-components 0.19.1 → 0.20.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/camunda-composite-components",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -12,6 +12,7 @@ export interface Endpoints {
12
12
  export declare const NOTIFICATIONS: Endpoint;
13
13
  export declare const ACCOUNTS: Endpoint;
14
14
  export declare const CONSOLE: Endpoint;
15
+ export declare const STATUS: Endpoint;
15
16
  export type Stage = "dev" | "int" | "prod";
16
17
  export declare function getEndpoint(stage: Stage, endpoint: Endpoint): string;
17
18
  export declare function getEndpointByOptions(options: {
@@ -16,6 +16,12 @@ export const CONSOLE = {
16
16
  int: "https://console.cloud.ultrawombat.com",
17
17
  prod: "https://console.cloud.camunda.io",
18
18
  };
19
+ export const STATUS = {
20
+ id: "status",
21
+ dev: "https://camundaultrawombat.statuspage.io/",
22
+ int: "https://camundaultrawombat.statuspage.io/",
23
+ prod: "https://status.camunda.io",
24
+ };
19
25
  export function getEndpoint(stage, endpoint) {
20
26
  switch (stage) {
21
27
  case "dev":
@@ -0,0 +1,7 @@
1
+ export declare class StatusService {
2
+ static camundaStatus(decodedAudience: string): Promise<{
3
+ error: boolean;
4
+ description: string;
5
+ endpoint: string;
6
+ }>;
7
+ }
@@ -0,0 +1,41 @@
1
+ import { request } from "./api";
2
+ import { getEndpoint, STATUS } from "./endpoints.const";
3
+ export class StatusService {
4
+ static async camundaStatus(decodedAudience) {
5
+ try {
6
+ let stage = "dev";
7
+ switch (decodedAudience) {
8
+ case "cloud.dev.ultrawombat.com":
9
+ stage = "dev";
10
+ break;
11
+ case "cloud.ultrawombat.com":
12
+ stage = "int";
13
+ break;
14
+ default:
15
+ stage = "prod";
16
+ break;
17
+ }
18
+ const endpoint = getEndpoint(stage, STATUS);
19
+ const resp = await request({
20
+ url: `${endpoint}/api/v2/status.json`,
21
+ method: "get",
22
+ type: "json",
23
+ responseType: "json",
24
+ });
25
+ let result = resp.result;
26
+ if (!resp.success ||
27
+ !result?.status.indicator ||
28
+ !result?.status.description) {
29
+ return { error: false, description: "", endpoint };
30
+ }
31
+ return {
32
+ error: result?.status.indicator === "major",
33
+ description: result?.status.description ?? "",
34
+ endpoint,
35
+ };
36
+ }
37
+ catch (_error) {
38
+ return { error: false, description: "", endpoint: STATUS.dev };
39
+ }
40
+ }
41
+ }
@@ -1,39 +1,15 @@
1
- import React, { useEffect, useState } from "react";
1
+ import React from "react";
2
2
  import { C3AppTeaser } from "./c3-app-teaser";
3
3
  import { canCreateCluster, canUpgradePlan, isTrialExpired, } from "../../utils/camunda";
4
4
  import { useC3Profile } from "../c3-user-configuration/c3-profile-provider/c3-profile-provider";
5
5
  import { Loading } from "@carbon/react";
6
- import { getStartingPrice } from "../../api/organizations";
7
6
  import { useC3UserConfiguration } from "../c3-user-configuration/c3-user-configuration-provider";
8
7
  export const C3AppTeaserPage = ({ appName, redirectToClusters, redirectToCreateCluster, redirectToCheckout, }) => {
9
- const { activeOrganizationId, decodedAudience, domain, analyticsTrack, userToken, } = useC3UserConfiguration();
8
+ const { activeOrganizationId, domain, analyticsTrack } = useC3UserConfiguration();
10
9
  const { activeOrg, clusters } = useC3Profile();
11
- const [pricing, setPricing] = useState(null);
12
- const [hasTrialExpired, setHasTrialExpired] = useState(false);
13
- const [hasSleepingCluster, setHasSleepingCluster] = useState(false);
14
- useEffect(() => {
15
- if (!userToken || !decodedAudience)
16
- return;
17
- (async () => {
18
- const { result } = await getStartingPrice({
19
- token: userToken,
20
- audience: decodedAudience,
21
- });
22
- if (result) {
23
- setPricing(result);
24
- }
25
- })();
26
- }, [userToken, decodedAudience]);
27
- useEffect(() => {
28
- if (activeOrg)
29
- setHasTrialExpired(isTrialExpired(activeOrg));
30
- }, [activeOrg]);
31
- useEffect(() => {
32
- if (clusters && appName) {
33
- const sleepingCluster = clusters.find((c) => c.status[appName] === "Suspended");
34
- setHasSleepingCluster(!!sleepingCluster);
35
- }
36
- }, [clusters, appName]);
10
+ const hasTrialExpired = (activeOrg && isTrialExpired(activeOrg)) ?? false;
11
+ const sleepingCluster = clusters && clusters.find((c) => c.status[appName] === "Suspended");
12
+ const hasSleepingCluster = (clusters && appName && !!sleepingCluster) ?? false;
37
13
  const onClickCta = () => {
38
14
  if (hasTrialExpired) {
39
15
  analyticsTrack?.("checkout:open", { appTeaser: appName });
@@ -63,5 +39,5 @@ export const C3AppTeaserPage = ({ appName, redirectToClusters, redirectToCreateC
63
39
  }
64
40
  }
65
41
  };
66
- return activeOrg && appName ? (React.createElement(C3AppTeaser, { appName: appName, canCreateCluster: canCreateCluster(activeOrg), canUpgradePlan: canUpgradePlan(activeOrg), hasTrialExpired: hasTrialExpired, hasSleepingCluster: hasSleepingCluster, onClickCta: onClickCta, pricing: pricing })) : (React.createElement(Loading, { withOverlay: true }));
42
+ return activeOrg && appName ? (React.createElement(C3AppTeaser, { appName: appName, canCreateCluster: canCreateCluster(activeOrg), canUpgradePlan: canUpgradePlan(activeOrg), hasTrialExpired: hasTrialExpired, hasSleepingCluster: hasSleepingCluster, onClickCta: onClickCta, pricing: null })) : (React.createElement(Loading, { withOverlay: true }));
67
43
  };
@@ -1,24 +1,24 @@
1
1
  import React from "react";
2
- import { Add, ArrowUp, Settings } from "@carbon/react/icons";
2
+ import { Add, Settings } from "@carbon/react/icons";
3
3
  import { AppTeaserCards } from "./app-teaser-cards";
4
4
  import { getReadableAppName } from "../../utils/camunda";
5
5
  import { DefaultStyleWrapper } from "../styles";
6
6
  import { appTeaserCardsConfig } from "./app-teaser-cards-config";
7
7
  export const teaserApps = ["tasklist", "operate", "optimize"];
8
- export const C3AppTeaser = ({ appName: app, hasTrialExpired, hasSleepingCluster, canUpgradePlan, canCreateCluster, onClickCta, pricing, }) => {
8
+ export const C3AppTeaser = ({ appName: app, hasTrialExpired, hasSleepingCluster, canUpgradePlan, canCreateCluster, onClickCta, }) => {
9
9
  const appName = app ? getReadableAppName(app) : "";
10
10
  const variants = {
11
11
  upgrade: {
12
12
  title: `Upgrade your plan${appName ? ` to use ${appName}` : ""}`,
13
13
  subtitle: `Creating a cluster and accessing the ${appName} app and features
14
14
  are not available with the Modeling plan`,
15
- ctaText: "Upgrade plan",
16
- ctaIcon: ArrowUp,
15
+ ctaText: "Contact us",
16
+ ctaIcon: null,
17
17
  disabledMessage: "Contact your admin to upgrade",
18
18
  },
19
19
  manageClusters: {
20
20
  title: `Resume your cluster${appName ? ` to use ${appName}` : ""}`,
21
- subtitle: `The ${appName} app requires an active
21
+ subtitle: `The ${appName} app requires an active
22
22
  cluster to run.`,
23
23
  ctaText: "Manage clusters",
24
24
  ctaIcon: Settings,
@@ -38,21 +38,8 @@ export const C3AppTeaser = ({ appName: app, hasTrialExpired, hasSleepingCluster,
38
38
  : hasSleepingCluster
39
39
  ? variants.manageClusters
40
40
  : variants.createClusters;
41
- const currency = pricing?.currency === "€" ? "EUR" : pricing?.currency;
42
- const locale = navigator.language || "en";
43
- const price = pricing?.amount &&
44
- currency &&
45
- locale &&
46
- Intl.NumberFormat(locale, {
47
- style: "currency",
48
- currency,
49
- minimumFractionDigits: 0,
50
- maximumFractionDigits: pricing.amount % 1 === 0 ? 0 : 2,
51
- }).format(pricing.amount);
52
41
  return (React.createElement(DefaultStyleWrapper, null,
53
- React.createElement(AppTeaserCards, { title: variant.title, subtitle: React.createElement(React.Fragment, null, variant.subtitle), subtext: hasTrialExpired && price
54
- ? `Plans starting at ${price}${pricing.interval ? `/${pricing.interval}` : ""}`
55
- : "", cta: {
42
+ React.createElement(AppTeaserCards, { title: variant.title, subtitle: React.createElement(React.Fragment, null, variant.subtitle), subtext: "", cta: {
56
43
  action: onClickCta,
57
44
  text: variant.ctaText,
58
45
  renderIcon: variant.ctaIcon,
@@ -325,7 +325,8 @@ export const C3DataTable = ({ data, headers, options, toolbar: singleToolbar, to
325
325
  }
326
326
  else if (toolbarElement.type === "search") {
327
327
  const searchElement = toolbarElement;
328
- const defaultValue = currentFilters.current.get(searchElement.id);
328
+ const filterId = searchElement.id ?? searchElement.queryName;
329
+ const defaultValue = currentFilters.current.get(filterId);
329
330
  toolbarComponents.push(React.createElement(TableToolbarSearch, { key: searchElement.id, onFocus: (event, handleExpand) => {
330
331
  handleExpand(event, true);
331
332
  if (searchElement.queryName) {
@@ -337,16 +338,15 @@ export const C3DataTable = ({ data, headers, options, toolbar: singleToolbar, to
337
338
  }
338
339
  updateFilterURLState(searchElement.queryName ?? searchElement.id, event.target.value);
339
340
  }, onChange: (event) => {
340
- currentFilters.current.set(searchElement.id, {
341
- searchTerm: event.target?.value ??
342
- getQueryKey(searchElement.queryName ?? ""),
341
+ currentFilters.current.set(filterId, {
342
+ searchTerm: event.target?.value ?? defaultValue?.searchTerm,
343
343
  });
344
344
  updateFilteredData();
345
345
  } }));
346
346
  }
347
347
  else if (toolbarElement.type === "button") {
348
348
  const buttonElement = toolbarElement;
349
- toolbarComponents.push(React.createElement(Button, { key: buttonElement.id, onClick: () => {
349
+ toolbarComponents.push(React.createElement(Button, { id: buttonElement.id, key: buttonElement.id, onClick: () => {
350
350
  if (buttonElement.onClick) {
351
351
  buttonElement.onClick();
352
352
  }
@@ -1,4 +1,4 @@
1
- import { Header, HeaderGlobalBar, HeaderMenuItem, HeaderName, HeaderNavigation, SkipToContent, Tag, Toggletip, ToggletipButton, ToggletipContent, Stack as CarbonStack, Link, } from "@carbon/react";
1
+ import { Header, HeaderGlobalBar, HeaderMenuItem, HeaderName, HeaderNavigation, SkipToContent, Tag, Toggletip, ToggletipButton, ToggletipContent, Stack as CarbonStack, Link, Callout, DefinitionTooltip, } from "@carbon/react";
2
2
  import React, { useState } from "react";
3
3
  import C3InfoSidebar from "./c3-navigation-sidebar/c3-info-sidebar";
4
4
  import { C3NotificationSidebar } from "./c3-navigation-sidebar/c3-notification-sidebar";
@@ -19,6 +19,7 @@ import { HelpCenterHint } from "../c3-help-center/help-center-hint";
19
19
  import { useC3UserConfiguration } from "../c3-user-configuration/c3-user-configuration-provider";
20
20
  import { OrgName } from "./c3-org-name";
21
21
  import { Time, Warning } from "@carbon/react/icons";
22
+ import { StatusService } from "../../api/status";
22
23
  /**
23
24
  * UI SHELL
24
25
  * Docs: https://react.carbondesignsystem.com/?path=/story/components-ui-shell--fixed-side-nav
@@ -52,7 +53,7 @@ export const DAY = HOUR * 24;
52
53
  const LICENSE_EXPIRY = DAY * 30;
53
54
  export const C3Navigation = ({ app, appBar, forwardRef, navbar, orgSideBar, infoSideBar, infoButton, helpCenter, actionButtons, userSideBar, notificationSideBar, clusterUuid, options, }) => {
54
55
  const { openHelpCenter } = useC3HelpCenter();
55
- const { currentClusterUuid } = useC3UserConfiguration();
56
+ const { currentClusterUuid, decodedAudience } = useC3UserConfiguration();
56
57
  const { activeOrg } = useC3Profile();
57
58
  const isLargeScreen = useMediaQuery(`(min-width: ${BREAKPOINT_LG_WIDTH}`);
58
59
  const appBarElementsLength = appBar.elements?.length ?? 0;
@@ -70,6 +71,22 @@ export const C3Navigation = ({ app, appBar, forwardRef, navbar, orgSideBar, info
70
71
  expiresAt = navbar.licenseTag?.expiresAt;
71
72
  }
72
73
  }
74
+ const [camundaStatus, setCamundaStatus] = useState({ error: false, description: "", endpoint: "" });
75
+ const fetchStatus = async (audience) => {
76
+ let status = await StatusService.camundaStatus(audience);
77
+ setCamundaStatus(status);
78
+ };
79
+ React.useEffect(() => {
80
+ if (decodedAudience) {
81
+ fetchStatus(decodedAudience);
82
+ }
83
+ const interval = setInterval(() => {
84
+ if (decodedAudience) {
85
+ fetchStatus(decodedAudience);
86
+ }
87
+ }, 5 * MINUTE);
88
+ return () => clearInterval(interval);
89
+ }, [decodedAudience]);
73
90
  const tags = [
74
91
  ...(navbar.tags || []),
75
92
  ...(navbar.licenseTag?.show
@@ -211,5 +228,56 @@ export const C3Navigation = ({ app, appBar, forwardRef, navbar, orgSideBar, info
211
228
  ...userSideBar,
212
229
  type: "user",
213
230
  } })))),
231
+ (() => {
232
+ if (camundaStatus.error &&
233
+ camundaStatus.endpoint &&
234
+ options?.c8StatusBanner) {
235
+ return (React.createElement(React.Fragment, null,
236
+ React.createElement("div", { className: "cds--header", style: {
237
+ position: "fixed",
238
+ marginTop: "48px",
239
+ zIndex: "7000",
240
+ } },
241
+ React.createElement(Callout, { title: "", kind: "error", lowContrast: true, style: { maxWidth: "100%" } },
242
+ React.createElement("div", { style: {
243
+ display: "flex",
244
+ justifyContent: "space-between",
245
+ alignItems: "center",
246
+ width: "100%",
247
+ position: "absolute",
248
+ paddingRight: "62px",
249
+ } },
250
+ React.createElement("div", { className: "cds--actionable-notification__title", dir: "auto", style: {
251
+ overflow: "hidden",
252
+ whiteSpace: "nowrap",
253
+ textWrap: "nowrap",
254
+ } }, "Service incident alert"),
255
+ React.createElement(DefinitionTooltip, { "aria-describedby": "", definition: "We're aware of a live incident potentially impacting our SaaS product. Please check our status page for details.", align: "bottom", openOnHover: true, style: {
256
+ marginTop: "-1px",
257
+ width: "calc(100vw - 400px)",
258
+ borderBlockEnd: "none",
259
+ overflow: "hidden",
260
+ textOverflow: "ellipsis",
261
+ textWrap: "nowrap",
262
+ whiteSpace: "nowrap",
263
+ fontSize: "14px",
264
+ } },
265
+ React.createElement("span", null, "We're aware of a live incident potentially impacting our SaaS product. Please check our status page for details.")),
266
+ React.createElement(Link, { onClick: () => {
267
+ window.open(camundaStatus.endpoint, "_blank");
268
+ }, inline: true, style: {
269
+ cursor: "pointer",
270
+ color: "$link-primary",
271
+ marginLeft: "auto",
272
+ marginRight: 0,
273
+ textDecoration: "none",
274
+ overflow: "hidden",
275
+ whiteSpace: "nowrap",
276
+ textWrap: "nowrap",
277
+ } }, "Camunda status page")))),
278
+ React.createElement("div", { style: { height: "48px" } })));
279
+ }
280
+ return React.createElement(React.Fragment, null);
281
+ })(),
214
282
  helpCenter && React.createElement(C3HelpCenter, { ...helpCenter })));
215
283
  };
@@ -79,6 +79,7 @@ export interface C3NavigationProps {
79
79
  clusterUuid?: string;
80
80
  options?: {
81
81
  conditionalTagRendering?: (stage: CamundaClusterStage) => boolean;
82
+ c8StatusBanner?: boolean;
82
83
  };
83
84
  toggleAppbar: (value: boolean) => void;
84
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/camunda-composite-components",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"