@oneuptime/common 8.0.5177 → 8.0.5181

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.
@@ -38,6 +38,10 @@ import SlackOnCallDutyActions from "../Utils/Workspace/Slack/Actions/OnCallDutyP
38
38
  import WorkspaceProjectAuthToken, {
39
39
  SlackMiscData,
40
40
  } from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
41
+ import UserMiddleware from "../Middleware/UserAuthorization";
42
+ import CommonAPI from "./CommonAPI";
43
+ import SlackUtil from "../Utils/Workspace/Slack/Slack";
44
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
41
45
 
42
46
  export default class SlackAPI {
43
47
  public getRouter(): ExpressRouter {
@@ -656,6 +660,71 @@ export default class SlackAPI {
656
660
  },
657
661
  );
658
662
 
663
+ // Fetch and cache all Slack channels for current tenant's project
664
+ router.get(
665
+ "/slack/get-all-channels",
666
+ UserMiddleware.getUserMiddleware,
667
+ async (req: ExpressRequest, res: ExpressResponse) => {
668
+ const props: DatabaseCommonInteractionProps =
669
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
670
+
671
+ if (!props.tenantId) {
672
+ return Response.sendErrorResponse(
673
+ req,
674
+ res,
675
+ new BadRequestException("ProjectId (tenant) not found in request"),
676
+ );
677
+ }
678
+
679
+ // Get Slack project auth
680
+ const projectAuth: WorkspaceProjectAuthToken | null =
681
+ await WorkspaceProjectAuthTokenService.getProjectAuth({
682
+ projectId: props.tenantId,
683
+ workspaceType: WorkspaceType.Slack,
684
+ });
685
+
686
+ if (!projectAuth || !projectAuth.authToken) {
687
+ return Response.sendErrorResponse(
688
+ req,
689
+ res,
690
+ new BadRequestException(
691
+ "Slack is not connected for this project. Please connect Slack first.",
692
+ ),
693
+ );
694
+ }
695
+
696
+ // Fetch all channels (also updates cache under miscData.channelCache)
697
+
698
+ let updatedProjectAuth: WorkspaceProjectAuthToken | null = projectAuth;
699
+
700
+ if (!(projectAuth.miscData as SlackMiscData)?.channelCache) {
701
+ await SlackUtil.getAllWorkspaceChannels({
702
+ authToken: projectAuth.authToken,
703
+ projectId: props.tenantId,
704
+ });
705
+
706
+ // Re-fetch to return the latest cached object
707
+ updatedProjectAuth =
708
+ await WorkspaceProjectAuthTokenService.getProjectAuth({
709
+ projectId: props.tenantId,
710
+ workspaceType: WorkspaceType.Slack,
711
+ });
712
+ }
713
+
714
+ const channelCache: {
715
+ [channelName: string]: {
716
+ id: string;
717
+ name: string;
718
+ lastUpdated: string;
719
+ };
720
+ } =
721
+ ((updatedProjectAuth?.miscData as SlackMiscData | undefined) || {})
722
+ ?.channelCache || {};
723
+
724
+ return Response.sendJsonObjectResponse(req, res, channelCache as any);
725
+ },
726
+ );
727
+
659
728
  // options load endpoint.
660
729
 
661
730
  router.post(
@@ -4,10 +4,18 @@ import React, {
4
4
  MouseEventHandler,
5
5
  ReactElement,
6
6
  } from "react";
7
+ import Icon from "../Icon/Icon";
8
+ import IconProp from "../../../Types/Icon/IconProp";
7
9
 
8
10
  export interface ComponentProps {
9
11
  textToBeCopied: string;
10
12
  className?: string | undefined;
13
+ size?: "xs" | "sm" | "md"; // visual size
14
+ variant?: "ghost" | "soft" | "solid"; // visual style
15
+ iconOnly?: boolean; // render only icon without label
16
+ label?: string; // default: "Copy"
17
+ copiedLabel?: string; // default: "Copied!"
18
+ title?: string; // tooltip
11
19
  }
12
20
 
13
21
  const CopyTextButton: FunctionComponent<ComponentProps> = (
@@ -15,8 +23,13 @@ const CopyTextButton: FunctionComponent<ComponentProps> = (
15
23
  ): ReactElement => {
16
24
  const [copied, setCopied] = React.useState(false);
17
25
 
18
- const handleCopy: MouseEventHandler<HTMLDivElement> = async (
19
- event: React.MouseEvent<HTMLDivElement, MouseEvent>,
26
+ const size: "xs" | "sm" | "md" = props.size || "xs";
27
+ const variant: "ghost" | "soft" | "solid" = props.variant || "ghost";
28
+ const label: string = props.label || "Copy";
29
+ const copiedLabel: string = props.copiedLabel || "Copied!";
30
+
31
+ const handleCopy: MouseEventHandler<HTMLButtonElement> = async (
32
+ event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
20
33
  ) => {
21
34
  event.preventDefault();
22
35
  event.stopPropagation();
@@ -27,10 +40,58 @@ const CopyTextButton: FunctionComponent<ComponentProps> = (
27
40
  }, 1000);
28
41
  };
29
42
 
43
+ const sizeClasses: Record<typeof size, string> = {
44
+ xs: "text-[10px] px-1.5 py-0.5 rounded",
45
+ sm: "text-xs px-2 py-1 rounded-md",
46
+ md: "text-sm px-2.5 py-1.5 rounded-md",
47
+ } as const;
48
+
49
+ const iconSizes: Record<typeof size, string> = {
50
+ xs: "w-3.5 h-3.5",
51
+ sm: "w-4 h-4",
52
+ md: "w-4.5 h-4.5",
53
+ } as const;
54
+
55
+ const variantClasses: Record<typeof variant, string> = {
56
+ ghost:
57
+ "bg-transparent border border-slate-600 text-slate-300 hover:bg-slate-700/40",
58
+ soft: "bg-slate-700 text-white border border-slate-600 hover:bg-slate-600",
59
+ solid:
60
+ "bg-indigo-600 text-white border border-indigo-600 hover:bg-indigo-500",
61
+ } as const;
62
+
63
+ const copiedClasses: string =
64
+ "bg-emerald-600/20 border border-emerald-500 text-emerald-300";
65
+
30
66
  return (
31
- <div className={`cursor-pointer ${props.className}`} onClick={handleCopy}>
32
- <div>{copied ? "Copied!" : "Copy"}</div>
33
- </div>
67
+ <button
68
+ type="button"
69
+ className={`inline-flex items-center justify-center gap-1 transition-colors duration-150 cursor-pointer select-none ${
70
+ copied ? copiedClasses : variantClasses[variant]
71
+ } ${sizeClasses[size]} ${props.className || ""}`}
72
+ onClick={handleCopy}
73
+ onKeyDown={(e: React.KeyboardEvent<HTMLButtonElement>) => {
74
+ if (e.key === "Enter" || e.key === " ") {
75
+ handleCopy(e as unknown as React.MouseEvent<HTMLButtonElement>);
76
+ }
77
+ }}
78
+ title={props.title || label}
79
+ aria-label={props.title || label}
80
+ >
81
+ {/* Icon */}
82
+ <span aria-hidden="true" className="flex items-center justify-center">
83
+ {copied ? (
84
+ <Icon
85
+ icon={IconProp.Check}
86
+ className={`${iconSizes[size]} text-emerald-400`}
87
+ />
88
+ ) : (
89
+ <Icon icon={IconProp.Copy} className={`${iconSizes[size]}`} />
90
+ )}
91
+ </span>
92
+ {/* Label (optional) */}
93
+ {!props.iconOnly && <span>{copied ? copiedLabel : label}</span>}
94
+ </button>
34
95
  );
35
96
  };
36
97