• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacklok / codegate-ui / 12948847633

24 Jan 2025 11:35AM UTC coverage: 69.253% (-0.03%) from 69.281%
12948847633

Pull #191

github

web-flow
Merge e7050e1ab into d84461041
Pull Request #191: feat: redirect to conversation from alerts table

315 of 552 branches covered (57.07%)

Branch coverage included in aggregate %.

1 of 2 new or added lines in 1 file covered. (50.0%)

2 existing lines in 1 file now uncovered.

649 of 840 relevant lines covered (77.26%)

58.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.62
/src/components/AlertsTable.tsx
1
import { format } from "date-fns";
2
import {
3
  Cell,
4
  Column,
5
  Input,
6
  Row,
7
  SearchField,
8
  Table,
9
  TableBody,
10
  FieldGroup,
11
  TableHeader,
12
  SearchFieldClearButton,
13
  Badge,
14
  Button,
15
} from "@stacklok/ui-kit";
16
import { Switch } from "@stacklok/ui-kit";
17
import { AlertConversation } from "@/api/generated";
18
import { Tooltip, TooltipTrigger } from "@stacklok/ui-kit";
19
import { getMaliciousPackage } from "@/lib/utils";
20
import { Search } from "lucide-react";
21
import { Markdown } from "./Markdown";
22
import { useAlertSearch } from "@/hooks/useAlertSearch";
23
import { useCallback } from "react";
24
import { useNavigate, useSearchParams } from "react-router-dom";
25
import { useFilteredAlerts } from "@/hooks/useAlertsData";
26
import { useClientSidePagination } from "@/hooks/useClientSidePagination";
27

28
const wrapObjectOutput = (input: AlertConversation["trigger_string"]) => {
2✔
29
  const data = getMaliciousPackage(input);
280✔
30
  if (data === null) return "N/A";
280✔
31
  if (typeof data === "string") {
261✔
32
    return (
33
      <div className="bg-gray-25 rounded-lg overflow-auto p-4">
34
        <Markdown>{data}</Markdown>
35
      </div>
36
    );
37
  }
38
  if (!data.type || !data.name) return "N/A";
35!
39

40
  return (
41
    <div className="max-h-40 w-fit overflow-y-auto whitespace-pre-wrap p-2">
42
      <label className="font-medium">Package:</label>
43
      &nbsp;
44
      <a
45
        href={`https://www.insight.stacklok.com/report/${data.type}/${data.name}`}
46
        target="_blank"
47
        rel="noopener noreferrer"
48
        className="text-brand-500 hover:underline"
49
      >
50
        {data.type}/{data.name}
51
      </a>
52
      {data.status && (
35✔
53
        <>
54
          <br />
55
          <label className="font-medium">Status:</label> {data.status}
56
        </>
57
      )}
58
      {data.description && (
35✔
59
        <>
60
          <br />
61
          <label className="font-medium">Description:</label> {data.description}
62
        </>
63
      )}
64
    </div>
65
  );
66
};
67

68
export function AlertsTable() {
69
  const {
70
    isMaliciousFilterActive,
71
    setIsMaliciousFilterActive,
72
    setSearch,
73
    search,
74
    page,
75
    nextPage,
76
    prevPage,
77
  } = useAlertSearch();
79✔
78
  const navigate = useNavigate();
79✔
79
  const [searchParams, setSearchParams] = useSearchParams();
79✔
80
  const { data: filteredAlerts = [] } = useFilteredAlerts();
79✔
81

82
  const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination(
79✔
83
    filteredAlerts,
84
    page,
85
    15,
86
  );
87

88
  const handleToggleFilter = useCallback(
79✔
89
    (isChecked: boolean) => {
90
      if (isChecked) {
2✔
91
        searchParams.set("maliciousPkg", "true");
1✔
92
        searchParams.delete("search");
1✔
93
        setSearch("");
1✔
94
      } else {
95
        searchParams.delete("maliciousPkg");
1✔
96
      }
97
      setSearchParams(searchParams);
2✔
98
      setIsMaliciousFilterActive(isChecked);
2✔
99
    },
100
    [setSearchParams, setSearch, searchParams, setIsMaliciousFilterActive],
101
  );
102

103
  const handleSearch = useCallback(
79✔
104
    (value: string) => {
105
      if (value) {
16!
106
        searchParams.set("search", value);
16✔
107
        searchParams.delete("maliciousPkg");
16✔
108
        setSearch(value);
16✔
109
        setIsMaliciousFilterActive(false);
16✔
110
      } else {
111
        searchParams.delete("search");
×
UNCOV
112
        setSearch("");
×
113
      }
114
      setSearchParams(searchParams);
16✔
115
    },
116
    [searchParams, setIsMaliciousFilterActive, setSearch, setSearchParams],
117
  );
118

119
  return (
120
    <>
121
      <div className="flex mb-2 mx-2 justify-between w-[calc(100vw-20rem)]">
122
        <div className="flex gap-2 items-center">
123
          <h2 className="font-bold text-lg">All Alerts</h2>
124
          <Badge size="sm" variant="inverted" data-testid="alerts-count">
125
            {filteredAlerts.length}
126
          </Badge>
127
        </div>
128

129
        <div className="flex items-center gap-8">
130
          <div className="flex items-center space-x-2">
131
            <TooltipTrigger>
132
              <Switch
133
                id="malicious-packages"
134
                isSelected={isMaliciousFilterActive}
135
                onChange={handleToggleFilter}
136
              >
137
                Malicious Packages
138
              </Switch>
139

140
              <Tooltip>
141
                <p>Filter by malicious packages</p>
142
              </Tooltip>
143
            </TooltipTrigger>
144
          </div>
145
          <SearchField
146
            type="text"
147
            aria-label="Search alerts"
148
            value={search}
149
            onChange={(value) => handleSearch(value.toLowerCase().trim())}
16✔
150
          >
151
            <FieldGroup>
152
              <Input
153
                type="search"
154
                placeholder="Search..."
155
                isBorderless
156
                icon={<Search />}
157
              />
158
              <SearchFieldClearButton />
159
            </FieldGroup>
160
          </SearchField>
161
        </div>
162
      </div>
163
      <div className="overflow-x-auto">
164
        <Table data-testid="alerts-table" aria-label="Alerts table">
165
          <TableHeader>
166
            <Row>
167
              <Column isRowHeader width={150}>
168
                Trigger Type
169
              </Column>
170
              <Column width={300}>Trigger Token</Column>
171
              <Column width={150}>File</Column>
172
              <Column width={250}>Code</Column>
173
              <Column width={100}>Timestamp</Column>
174
            </Row>
175
          </TableHeader>
176
          <TableBody>
177
            {dataView.map((alert) => (
178
              <Row
179
                key={alert.alert_id}
180
                className="h-20"
181
                onAction={() =>
NEW
UNCOV
182
                  navigate(`/prompt/${alert.conversation.chat_id}`)
×
183
                }
184
              >
185
                <Cell className="truncate">{alert.trigger_type}</Cell>
186
                <Cell className="overflow-auto whitespace-nowrap max-w-80">
187
                  {wrapObjectOutput(alert.trigger_string)}
188
                </Cell>
189
                <Cell className="truncate">
190
                  {alert.code_snippet?.filepath || "N/A"}
560✔
191
                </Cell>
192
                <Cell className="overflow-auto whitespace-nowrap max-w-80">
193
                  {alert.code_snippet?.code ? (
280!
194
                    <pre className="max-h-40 overflow-auto bg-gray-100 p-2 whitespace-pre-wrap">
195
                      <code>{alert.code_snippet.code}</code>
196
                    </pre>
197
                  ) : (
198
                    "N/A"
199
                  )}
200
                </Cell>
201
                <Cell className="truncate">
202
                  <div data-testid="date">
203
                    {format(new Date(alert.timestamp ?? ""), "y/MM/dd")}
280!
204
                  </div>
205
                  <div data-testid="time">
206
                    {format(new Date(alert.timestamp ?? ""), "hh:mm:ss a")}
280!
207
                  </div>
208
                </Cell>
209
              </Row>
210
            ))}
211
          </TableBody>
212
        </Table>
213
      </div>
214

215
      <div className="flex justify-center w-full p-4">
216
        <div className="flex gap-2">
217
          <Button isDisabled={!hasPreviousPage} onPress={prevPage}>
218
            Previous
219
          </Button>
220
          <Button isDisabled={!hasNextPage} onPress={nextPage}>
221
            Next
222
          </Button>
223
        </div>
224
      </div>
225
    </>
226
  );
227
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc