@oneuptime/common 7.0.3567 → 7.0.3589

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.
Files changed (53) hide show
  1. package/Models/DatabaseModels/MonitorTest.ts +1 -0
  2. package/Models/DatabaseModels/StatusPageAnnouncement.ts +6 -1
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1737997557974-MigrationName.ts +17 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  5. package/Server/Services/AlertService.ts +106 -44
  6. package/Server/Services/IncidentService.ts +108 -44
  7. package/Server/Services/Index.ts +3 -0
  8. package/Server/Services/ScheduledMaintenanceService.ts +200 -19
  9. package/Types/Domain.ts +1 -1
  10. package/Types/Events/Recurring.ts +4 -0
  11. package/UI/Components/Forms/BasicForm.tsx +56 -2
  12. package/UI/Components/Forms/BasicModelForm.tsx +3 -0
  13. package/UI/Components/Forms/FormSummary.tsx +118 -0
  14. package/UI/Components/Forms/ModelForm.tsx +3 -1
  15. package/UI/Components/Forms/Types/Field.ts +2 -0
  16. package/UI/Components/Forms/Types/FormStep.ts +1 -0
  17. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +84 -0
  18. package/UI/Components/ModelTable/BaseModelTable.tsx +3 -1
  19. package/UI/Components/ModelTable/ModelTable.tsx +1 -0
  20. package/build/dist/Models/DatabaseModels/MonitorTest.js +1 -0
  21. package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
  22. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +6 -1
  23. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  24. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1737997557974-MigrationName.js +12 -0
  25. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1737997557974-MigrationName.js.map +1 -0
  26. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  27. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  28. package/build/dist/Server/Services/AlertService.js +85 -40
  29. package/build/dist/Server/Services/AlertService.js.map +1 -1
  30. package/build/dist/Server/Services/IncidentService.js +85 -40
  31. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  32. package/build/dist/Server/Services/Index.js +2 -0
  33. package/build/dist/Server/Services/Index.js.map +1 -1
  34. package/build/dist/Server/Services/ScheduledMaintenanceService.js +159 -16
  35. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  36. package/build/dist/Types/Domain.js +1 -1
  37. package/build/dist/Types/Domain.js.map +1 -1
  38. package/build/dist/Types/Events/Recurring.js +3 -0
  39. package/build/dist/Types/Events/Recurring.js.map +1 -1
  40. package/build/dist/UI/Components/Forms/BasicForm.js +51 -17
  41. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  42. package/build/dist/UI/Components/Forms/BasicModelForm.js +1 -1
  43. package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
  44. package/build/dist/UI/Components/Forms/FormSummary.js +64 -0
  45. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -0
  46. package/build/dist/UI/Components/Forms/ModelForm.js +1 -1
  47. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  48. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +81 -0
  49. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -0
  50. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  51. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -0
  52. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  53. package/package.json +2 -2
@@ -50,6 +50,8 @@ import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
50
50
  import ScheduledMaintenanceFeedService from "./ScheduledMaintenanceFeedService";
51
51
  import { ScheduledMaintenanceFeedEventType } from "../../Models/DatabaseModels/ScheduledMaintenanceFeed";
52
52
  import { Gray500, Red500 } from "../../Types/BrandColors";
53
+ import Label from "../../Models/DatabaseModels/Label";
54
+ import LabelService from "./LabelService";
53
55
 
54
56
  export class Service extends DatabaseService<Model> {
55
57
  public constructor() {
@@ -694,40 +696,219 @@ ${createdItem.description || "No description provided."}
694
696
 
695
697
  if (updatedItemIds.length > 0) {
696
698
  for (const scheduledMaintenanceId of updatedItemIds) {
699
+ let shouldAddScheduledMaintenanceFeed: boolean = false;
700
+ let feedInfoInMarkdown: string =
701
+ "**Scheduled Maintenance was updated.**";
702
+
703
+ const createdByUserId: ObjectID | undefined | null =
704
+ onUpdate.updateBy.props.userId;
705
+
697
706
  if (onUpdate.updateBy.data.title) {
698
707
  // add scheduledMaintenance feed.
699
- const createdByUserId: ObjectID | undefined | null =
700
- onUpdate.updateBy.props.userId;
701
708
 
702
- await ScheduledMaintenanceFeedService.createScheduledMaintenanceFeed({
703
- scheduledMaintenanceId: scheduledMaintenanceId,
704
- projectId: onUpdate.updateBy.props.tenantId as ObjectID,
705
- scheduledMaintenanceFeedEventType:
706
- ScheduledMaintenanceFeedEventType.ScheduledMaintenanceUpdated,
707
- displayColor: Gray500,
708
- feedInfoInMarkdown: `**Scheduled Maintenance title was updated.** Here's the new title.
709
-
709
+ feedInfoInMarkdown += `\n\n**Title**:
710
710
  ${onUpdate.updateBy.data.title || "No title provided."}
711
- `,
712
- userId: createdByUserId || undefined,
713
- });
711
+ `;
712
+ shouldAddScheduledMaintenanceFeed = true;
713
+ }
714
+
715
+ if (onUpdate.updateBy.data.startsAt) {
716
+ // add scheduledMaintenance feed.
717
+
718
+ feedInfoInMarkdown += `\n\n**Starts At**:
719
+ ${OneUptimeDate.getDateAsLocalFormattedString(onUpdate.updateBy.data.startsAt as Date) || "No title provided."}
720
+ `;
721
+ shouldAddScheduledMaintenanceFeed = true;
722
+ }
723
+
724
+ if (onUpdate.updateBy.data.endsAt) {
725
+ // add scheduledMaintenance feed.
726
+
727
+ feedInfoInMarkdown += `\n\n**Ends At**:
728
+ ${OneUptimeDate.getDateAsLocalFormattedString(onUpdate.updateBy.data.endsAt as Date) || "No title provided."}
729
+ `;
730
+ shouldAddScheduledMaintenanceFeed = true;
714
731
  }
715
732
 
716
733
  if (onUpdate.updateBy.data.description) {
717
734
  // add scheduledMaintenance feed.
718
- const createdByUserId: ObjectID | undefined | null =
719
- onUpdate.updateBy.props.userId;
720
735
 
736
+ feedInfoInMarkdown += `\n\n**Scheduled Maintenance Description**:
737
+ ${onUpdate.updateBy.data.description || "No description provided."}
738
+ `;
739
+ shouldAddScheduledMaintenanceFeed = true;
740
+ }
741
+
742
+ if (
743
+ onUpdate.updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent &&
744
+ Array.isArray(
745
+ onUpdate.updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent,
746
+ ) &&
747
+ onUpdate.updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent
748
+ .length > 0
749
+ ) {
750
+ feedInfoInMarkdown += `\n\n**Notify Subscribers Before Event Starts**:
751
+ ${(
752
+ onUpdate.updateBy.data
753
+ .sendSubscriberNotificationsOnBeforeTheEvent as Array<Recurring>
754
+ )
755
+ .map((recurring: Recurring) => {
756
+ return `- ${(recurring as Recurring).toString()}`;
757
+ })
758
+ .join("\n")}
759
+ `;
760
+ shouldAddScheduledMaintenanceFeed = true;
761
+ }
762
+
763
+ if (
764
+ onUpdate.updateBy.data.monitors &&
765
+ onUpdate.updateBy.data.monitors.length > 0 &&
766
+ Array.isArray(onUpdate.updateBy.data.monitors)
767
+ ) {
768
+ const monitorIds: Array<ObjectID> = (
769
+ onUpdate.updateBy.data.monitors as any
770
+ )
771
+ .map((monitor: Label) => {
772
+ if (monitor._id) {
773
+ return new ObjectID(monitor._id?.toString());
774
+ }
775
+
776
+ return null;
777
+ })
778
+ .filter((monitorId: ObjectID | null) => {
779
+ return monitorId !== null;
780
+ });
781
+
782
+ const monitors: Array<Label> = await MonitorService.findBy({
783
+ query: {
784
+ _id: QueryHelper.any(monitorIds),
785
+ },
786
+ select: {
787
+ name: true,
788
+ },
789
+ limit: LIMIT_PER_PROJECT,
790
+ skip: 0,
791
+ props: {
792
+ isRoot: true,
793
+ },
794
+ });
795
+
796
+ if (monitors.length > 0) {
797
+ feedInfoInMarkdown += `\n\n**Resources Affected**:
798
+
799
+ ${monitors
800
+ .map((monitor: Monitor) => {
801
+ return `- ${monitor.name}`;
802
+ })
803
+ .join("\n")}
804
+ `;
805
+
806
+ shouldAddScheduledMaintenanceFeed = true;
807
+ }
808
+ }
809
+
810
+ if (
811
+ onUpdate.updateBy.data.statusPages &&
812
+ onUpdate.updateBy.data.statusPages.length > 0 &&
813
+ Array.isArray(onUpdate.updateBy.data.statusPages)
814
+ ) {
815
+ const statusPageIds: Array<ObjectID> = (
816
+ onUpdate.updateBy.data.statusPages as any
817
+ )
818
+ .map((statusPage: Label) => {
819
+ if (statusPage._id) {
820
+ return new ObjectID(statusPage._id?.toString());
821
+ }
822
+
823
+ return null;
824
+ })
825
+ .filter((statusPageId: ObjectID | null) => {
826
+ return statusPageId !== null;
827
+ });
828
+
829
+ const statusPages: Array<Label> = await StatusPageService.findBy({
830
+ query: {
831
+ _id: QueryHelper.any(statusPageIds),
832
+ },
833
+ select: {
834
+ name: true,
835
+ },
836
+ limit: LIMIT_PER_PROJECT,
837
+ skip: 0,
838
+ props: {
839
+ isRoot: true,
840
+ },
841
+ });
842
+
843
+ if (statusPages.length > 0) {
844
+ feedInfoInMarkdown += `\n\n**Show on these status pages:**:
845
+
846
+ ${statusPages
847
+ .map((statusPage: StatusPage) => {
848
+ return `- ${statusPage.name}`;
849
+ })
850
+ .join("\n")}
851
+ `;
852
+
853
+ shouldAddScheduledMaintenanceFeed = true;
854
+ }
855
+ }
856
+
857
+ if (
858
+ onUpdate.updateBy.data.labels &&
859
+ onUpdate.updateBy.data.labels.length > 0 &&
860
+ Array.isArray(onUpdate.updateBy.data.labels)
861
+ ) {
862
+ const labelIds: Array<ObjectID> = (
863
+ onUpdate.updateBy.data.labels as any
864
+ )
865
+ .map((label: Label) => {
866
+ if (label._id) {
867
+ return new ObjectID(label._id?.toString());
868
+ }
869
+
870
+ return null;
871
+ })
872
+ .filter((labelId: ObjectID | null) => {
873
+ return labelId !== null;
874
+ });
875
+
876
+ const labels: Array<Label> = await LabelService.findBy({
877
+ query: {
878
+ _id: QueryHelper.any(labelIds),
879
+ },
880
+ select: {
881
+ name: true,
882
+ },
883
+ limit: LIMIT_PER_PROJECT,
884
+ skip: 0,
885
+ props: {
886
+ isRoot: true,
887
+ },
888
+ });
889
+
890
+ if (labels.length > 0) {
891
+ feedInfoInMarkdown += `\n\n**Labels**:
892
+
893
+ ${labels
894
+ .map((label: Label) => {
895
+ return `- ${label.name}`;
896
+ })
897
+ .join("\n")}
898
+ `;
899
+
900
+ shouldAddScheduledMaintenanceFeed = true;
901
+ }
902
+ }
903
+
904
+ if (shouldAddScheduledMaintenanceFeed) {
721
905
  await ScheduledMaintenanceFeedService.createScheduledMaintenanceFeed({
722
906
  scheduledMaintenanceId: scheduledMaintenanceId,
723
907
  projectId: onUpdate.updateBy.props.tenantId as ObjectID,
724
908
  scheduledMaintenanceFeedEventType:
725
909
  ScheduledMaintenanceFeedEventType.ScheduledMaintenanceUpdated,
726
910
  displayColor: Gray500,
727
- feedInfoInMarkdown: `**Scheduled Maintenance description was updated.** Here's the new description.
728
-
729
- ${onUpdate.updateBy.data.description || "No description provided."}
730
- `,
911
+ feedInfoInMarkdown: feedInfoInMarkdown,
731
912
  userId: createdByUserId || undefined,
732
913
  });
733
914
  }
package/Types/Domain.ts CHANGED
@@ -26,7 +26,7 @@ export default class Domain extends DatabaseProperty {
26
26
  "|",
27
27
  );
28
28
  const secondTLDs: Array<string> =
29
- "ac|academy|accountant|accountants|actor|adult|aero|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh|lol|africa".split(
29
+ "ac|academy|accountant|accountants|actor|adult|aero|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh|lol|africa|top".split(
30
30
  "|",
31
31
  );
32
32
 
@@ -186,6 +186,10 @@ export default class Recurring extends DatabaseProperty {
186
186
  return arrayToReturn;
187
187
  }
188
188
 
189
+ public override toString(): string {
190
+ return `${this.intervalCount} ${this.intervalType}`;
191
+ }
192
+
189
193
  protected static override toDatabase(
190
194
  value: Recurring | Array<Recurring> | FindOperator<Recurring>,
191
195
  ): JSONObject | Array<JSONObject> | null {
@@ -3,9 +3,11 @@ import UiAnalytics from "../../Utils/Analytics";
3
3
  import Alert, { AlertType } from "../Alerts/Alert";
4
4
  import Button, { ButtonStyleType } from "../Button/Button";
5
5
  import ButtonTypes from "../Button/ButtonTypes";
6
+
6
7
  import { DropdownOption, DropdownValue } from "../Dropdown/Dropdown";
7
8
  import ErrorMessage from "../ErrorMessage/ErrorMessage";
8
9
  import FormField from "./Fields/FormField";
10
+ import FormSummary from "./FormSummary";
9
11
  import Steps from "./Steps/Steps";
10
12
  import Field from "./Types/Field";
11
13
  import Fields from "./Types/Fields";
@@ -48,6 +50,11 @@ export const DefaultValidateFunction: DefaultValidateFunctionType = (
48
50
  return {};
49
51
  };
50
52
 
53
+ export interface FormSummaryConfig {
54
+ enabled: boolean;
55
+ defaultStepName?: string | undefined;
56
+ }
57
+
51
58
  export interface BaseComponentProps<T> {
52
59
  submitButtonStyleType?: ButtonStyleType | undefined;
53
60
  initialValues?: FormValues<T> | undefined;
@@ -73,6 +80,7 @@ export interface BaseComponentProps<T> {
73
80
  onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void);
74
81
  onFormValidationErrorChanged?: ((hasError: boolean) => void) | undefined;
75
82
  showSubmitButtonOnlyIfSomethingChanged?: boolean | undefined;
83
+ summary?: FormSummaryConfig | undefined;
76
84
  }
77
85
 
78
86
  export interface ComponentProps<T extends GenericObject>
@@ -104,12 +112,33 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
104
112
  setIsLoading(props.isLoading);
105
113
  }, [props.isLoading]);
106
114
 
115
+ const getFormSteps: () => Array<FormStep<T>> | undefined = () => {
116
+ if (props.summary && props.summary.enabled) {
117
+ // add to last step
118
+ return [
119
+ ...(props.steps || [
120
+ {
121
+ id: props.summary.defaultStepName || "basic",
122
+ title: props.summary.defaultStepName || "Basic",
123
+ isSummaryStep: false,
124
+ },
125
+ ]),
126
+ {
127
+ id: "summary",
128
+ title: "Summary",
129
+ isSummaryStep: true,
130
+ },
131
+ ];
132
+ }
133
+ return props.steps;
134
+ };
135
+
107
136
  const [submitButtonText, setSubmitButtonText] = useState<string>(
108
137
  props.submitButtonText || "Submit",
109
138
  );
110
139
 
111
140
  const [formSteps, setFormSteps] = useState<Array<FormStep<T>> | undefined>(
112
- props.steps,
141
+ getFormSteps(),
113
142
  );
114
143
 
115
144
  const isInitialValuesSet: MutableRefObject<boolean> = useRef(false);
@@ -175,7 +204,7 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
175
204
 
176
205
  useEffect(() => {
177
206
  setFormSteps(
178
- props.steps?.filter((step: FormStep<T>) => {
207
+ getFormSteps()?.filter((step: FormStep<T>) => {
179
208
  if (!step.showIf) {
180
209
  return true;
181
210
  }
@@ -240,11 +269,26 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
240
269
 
241
270
  for (const item of fields) {
242
271
  // if this field is not the current step.
272
+
273
+ let shouldSkip: boolean = false;
243
274
  if (
244
275
  currentFormStepId &&
245
276
  item.stepId &&
246
277
  item.stepId !== currentFormStepId
247
278
  ) {
279
+ shouldSkip = true;
280
+ }
281
+
282
+ if (
283
+ props.summary?.enabled &&
284
+ (!props.steps || props.steps.length === 0)
285
+ ) {
286
+ // if summary is enabled and no steps are provided, then all fields belong to the same step and should not be skipped.
287
+ shouldSkip = false;
288
+ item.stepId = props.summary.defaultStepName || "basic";
289
+ }
290
+
291
+ if (shouldSkip) {
248
292
  continue;
249
293
  }
250
294
 
@@ -606,6 +650,16 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
606
650
  </div>
607
651
  );
608
652
  })}
653
+
654
+ {/* If Summary, show Model detail */}
655
+
656
+ {currentFormStepId === "summary" && (
657
+ <FormSummary
658
+ formValues={refCurrentValue.current}
659
+ formFields={formFields}
660
+ formSteps={formSteps || undefined}
661
+ />
662
+ )}
609
663
  </div>
610
664
  </div>
611
665
 
@@ -3,6 +3,7 @@ import BasicForm, {
3
3
  DefaultValidateFunction,
4
4
  FormErrors,
5
5
  FormProps,
6
+ FormSummaryConfig,
6
7
  } from "./BasicForm";
7
8
  import Fields from "./Types/Fields";
8
9
  import { FormStep } from "./Types/FormStep";
@@ -46,6 +47,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
46
47
  hideSubmitButton?: undefined | boolean;
47
48
  formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>;
48
49
  initialValues?: FormValues<TBaseModel> | undefined;
50
+ summary?: FormSummaryConfig | undefined;
49
51
  }
50
52
 
51
53
  const BasicModelForm: <TBaseModel extends BaseModel>(
@@ -121,6 +123,7 @@ const BasicModelForm: <TBaseModel extends BaseModel>(
121
123
  onIsLastFormStep={props.onIsLastFormStep}
122
124
  hideSubmitButton={props.hideSubmitButton}
123
125
  ref={props.formRef}
126
+ summary={props.summary}
124
127
  ></BasicForm>
125
128
  );
126
129
  };
@@ -0,0 +1,118 @@
1
+ import React, { ReactElement } from "react";
2
+ import Detail from "../Detail/Detail";
3
+ import FormValues from "./Types/FormValues";
4
+ import GenericObject from "../../../Types/GenericObject";
5
+ import Fields from "./Types/Fields";
6
+ import FormFieldSchemaTypeUtil from "./Utils/FormFieldSchemaTypeUtil";
7
+ import FormFieldSchemaType from "./Types/FormFieldSchemaType";
8
+ import DetailField from "../Detail/Field";
9
+ import Field from "./Types/Field";
10
+ import FieldType from "../Types/FieldType";
11
+ import { FormStep } from "./Types/FormStep";
12
+ import HorizontalRule from "../HorizontalRule/HorizontalRule";
13
+
14
+ export interface ComponentProps<T> {
15
+ formValues: FormValues<T>;
16
+ formFields: Fields<T>;
17
+ formSteps: FormStep<T>[] | undefined;
18
+ }
19
+
20
+ const FormSummary: <T extends GenericObject>(
21
+ props: ComponentProps<T>,
22
+ ) => ReactElement = <T extends GenericObject>(
23
+ props: ComponentProps<T>,
24
+ ): ReactElement => {
25
+ const { formValues, formFields } = props;
26
+
27
+ const getDetailForFormFields: <T extends GenericObject>(
28
+ formValues: FormValues<T>,
29
+ formFields: Fields<T>,
30
+ ) => ReactElement = <T extends GenericObject>(
31
+ formValues: FormValues<T>,
32
+ formFields: Fields<T>,
33
+ ): ReactElement => {
34
+ return (
35
+ <div>
36
+ <Detail
37
+ item={formValues as T}
38
+ fields={
39
+ formFields.map((field: Field<T>) => {
40
+ const detailField: DetailField<T> = {
41
+ title: field.title || "",
42
+ fieldType: field.getSummaryElement
43
+ ? FieldType.Element
44
+ : FormFieldSchemaTypeUtil.toFieldType(
45
+ field.fieldType || FormFieldSchemaType.Text,
46
+ ),
47
+ description: field.description || "",
48
+ getElement: field.getSummaryElement as any,
49
+ sideLink: field.sideLink,
50
+ key: (Object.keys(field.field || {})[0]?.toString() ||
51
+ "") as keyof T,
52
+ };
53
+ return detailField;
54
+ }) as DetailField<GenericObject>[]
55
+ }
56
+ />
57
+ <HorizontalRule />
58
+ </div>
59
+ );
60
+ };
61
+
62
+ const getFormStepTitle: (formStep: FormStep<T>) => ReactElement = (
63
+ formStep: FormStep<T>,
64
+ ): ReactElement => {
65
+ return (
66
+ <h2 className="text-md font-medium text-gray-900 mb-3">
67
+ {formStep.title}
68
+ </h2>
69
+ );
70
+ };
71
+
72
+ const getDetailForFormStep: (formStep: FormStep<T>) => ReactElement = (
73
+ formStep: FormStep<T>,
74
+ ): ReactElement => {
75
+ const formFields: Fields<T> = props.formFields
76
+ .filter((field: Field<T>) => {
77
+ return formStep.id === field.stepId;
78
+ })
79
+ .filter((formField: Field<T>) => {
80
+ if (!formField.showIf) {
81
+ return true;
82
+ }
83
+ return formField.showIf(formValues);
84
+ });
85
+
86
+ if (formFields.length === 0) {
87
+ return <></>;
88
+ }
89
+
90
+ return (
91
+ <div>
92
+ {getFormStepTitle(formStep)}
93
+ {getDetailForFormFields(formValues, formFields)}
94
+ </div>
95
+ );
96
+ };
97
+
98
+ if (props.formSteps && props.formSteps.length > 0) {
99
+ return (
100
+ <div>
101
+ {props.formSteps
102
+ .filter((step: FormStep<T>) => {
103
+ if (!step.showIf) {
104
+ return true;
105
+ }
106
+ return step.showIf(props.formValues);
107
+ })
108
+ .map((formStep: FormStep<T>) => {
109
+ return getDetailForFormStep(formStep);
110
+ })}
111
+ </div>
112
+ );
113
+ }
114
+
115
+ return getDetailForFormFields(formValues, formFields);
116
+ };
117
+
118
+ export default FormSummary;
@@ -15,7 +15,7 @@ import {
15
15
  } from "../CategoryCheckbox/CategoryCheckboxTypes";
16
16
  import Loader, { LoaderType } from "../Loader/Loader";
17
17
  import Pill, { PillSize } from "../Pill/Pill";
18
- import { FormErrors, FormProps } from "./BasicForm";
18
+ import { FormErrors, FormProps, FormSummaryConfig } from "./BasicForm";
19
19
  import BasicModelForm from "./BasicModelForm";
20
20
  import Field from "./Types/Field";
21
21
  import Fields from "./Types/Fields";
@@ -101,6 +101,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
101
101
  saveRequestOptions?: RequestOptions | undefined;
102
102
  doNotFetchExistingModel?: boolean | undefined;
103
103
  modelAPI?: typeof ModelAPI | undefined;
104
+ summary?: FormSummaryConfig | undefined;
104
105
  }
105
106
 
106
107
  const ModelForm: <TBaseModel extends BaseModel>(
@@ -783,6 +784,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
783
784
  | FormValues<TBaseModel>
784
785
  | undefined
785
786
  }
787
+ summary={props.summary}
786
788
  ></BasicModelForm>
787
789
  </div>
788
790
  );
@@ -102,4 +102,6 @@ export default interface Field<TEntity> {
102
102
  jsonKeysForDictionary?: Array<string> | undefined;
103
103
 
104
104
  hideOptionalLabel?: boolean | undefined;
105
+
106
+ getSummaryElement?: (item: FormValues<TEntity>) => ReactElement | undefined;
105
107
  }
@@ -10,4 +10,5 @@ export interface FormStep<TEntity> {
10
10
  id: string;
11
11
  title: string;
12
12
  showIf?: ((item: FormValues<TEntity>) => boolean) | undefined;
13
+ isSummaryStep?: boolean | undefined;
13
14
  }
@@ -0,0 +1,84 @@
1
+ import FieldType from "../../Types/FieldType";
2
+ import FormFieldSchemaType from "../Types/FormFieldSchemaType";
3
+
4
+ export default class FormFieldSchemaTypeUtil {
5
+ public static toFieldType(
6
+ formFieldSchemaType: FormFieldSchemaType,
7
+ ): FieldType {
8
+ switch (formFieldSchemaType) {
9
+ case FormFieldSchemaType.ObjectID:
10
+ return FieldType.ObjectID;
11
+ case FormFieldSchemaType.Name:
12
+ return FieldType.Name;
13
+ case FormFieldSchemaType.Hostname:
14
+ return FieldType.Hostname;
15
+ case FormFieldSchemaType.ImageFile:
16
+ return FieldType.ImageFile;
17
+ case FormFieldSchemaType.URL:
18
+ return FieldType.URL;
19
+ case FormFieldSchemaType.Route:
20
+ return FieldType.Route;
21
+ case FormFieldSchemaType.Number:
22
+ return FieldType.Number;
23
+ case FormFieldSchemaType.Password:
24
+ return FieldType.Password;
25
+ case FormFieldSchemaType.Text:
26
+ return FieldType.Text;
27
+ case FormFieldSchemaType.Time:
28
+ return FieldType.DateTime;
29
+ case FormFieldSchemaType.Email:
30
+ return FieldType.Email;
31
+ case FormFieldSchemaType.PositiveNumber:
32
+ return FieldType.Number;
33
+ case FormFieldSchemaType.Date:
34
+ return FieldType.Date;
35
+ case FormFieldSchemaType.Phone:
36
+ return FieldType.Phone;
37
+ case FormFieldSchemaType.DateTime:
38
+ return FieldType.DateTime;
39
+ case FormFieldSchemaType.Domain:
40
+ return FieldType.Text;
41
+ case FormFieldSchemaType.LongText:
42
+ return FieldType.LongText;
43
+ case FormFieldSchemaType.Color:
44
+ return FieldType.Color;
45
+ case FormFieldSchemaType.Dropdown:
46
+ return FieldType.Dropdown;
47
+ case FormFieldSchemaType.Radio:
48
+ return FieldType.Text;
49
+ case FormFieldSchemaType.File:
50
+ return FieldType.File;
51
+ case FormFieldSchemaType.MultiSelectDropdown:
52
+ return FieldType.MultiSelectDropdown;
53
+ case FormFieldSchemaType.Toggle:
54
+ return FieldType.Boolean;
55
+ case FormFieldSchemaType.Port:
56
+ return FieldType.Port;
57
+ case FormFieldSchemaType.EncryptedText:
58
+ return FieldType.HiddenText;
59
+ case FormFieldSchemaType.Markdown:
60
+ return FieldType.Markdown;
61
+ case FormFieldSchemaType.JavaScript:
62
+ return FieldType.JavaScript;
63
+ case FormFieldSchemaType.CSS:
64
+ return FieldType.CSS;
65
+ case FormFieldSchemaType.HTML:
66
+ return FieldType.HTML;
67
+ case FormFieldSchemaType.RadioButton:
68
+ return FieldType.Element;
69
+ case FormFieldSchemaType.JSON:
70
+ return FieldType.JSON;
71
+ case FormFieldSchemaType.Query:
72
+ return FieldType.Element;
73
+ case FormFieldSchemaType.CustomComponent:
74
+ return FieldType.Element;
75
+ case FormFieldSchemaType.Checkbox:
76
+ return FieldType.Boolean;
77
+ case FormFieldSchemaType.CategoryCheckbox:
78
+ return FieldType.Boolean;
79
+
80
+ default:
81
+ return FieldType.Text;
82
+ }
83
+ }
84
+ }
@@ -30,7 +30,7 @@ import Field from "../Detail/Field";
30
30
  import ErrorMessage from "../ErrorMessage/ErrorMessage";
31
31
  import ClassicFilterType from "../Filters/Types/Filter";
32
32
  import FilterData from "../Filters/Types/FilterData";
33
- import { FormProps } from "../Forms/BasicForm";
33
+ import { FormProps, FormSummaryConfig } from "../Forms/BasicForm";
34
34
  import { ModelField } from "../Forms/ModelForm";
35
35
  import { FormStep } from "../Forms/Types/FormStep";
36
36
  import FormValues from "../Forms/Types/FormValues";
@@ -215,6 +215,8 @@ export interface BaseTableProps<
215
215
  initialFilterData?: FilterData<TBaseModel> | undefined;
216
216
 
217
217
  saveFilterProps?: SaveFilterProps | undefined;
218
+
219
+ formSummary?: FormSummaryConfig | undefined;
218
220
  }
219
221
 
220
222
  export interface ComponentProps<