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

stacklok / codegate-ui / 13032675856

29 Jan 2025 01:44PM UTC coverage: 71.513% (+4.4%) from 67.071%
13032675856

Pull #222

github

web-flow
Merge fc09eabbc into a3e86e582
Pull Request #222: feat: initial work on alerts summary cards

349 of 547 branches covered (63.8%)

Branch coverage included in aggregate %.

20 of 20 new or added lines in 10 files covered. (100.0%)

14 existing lines in 2 files now uncovered.

743 of 980 relevant lines covered (75.82%)

90.96 hits per line

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

65.69
/src/lib/utils.ts
1
import { AlertConversation, Conversation } from "@/api/generated/types.gen";
2
import { isAlertSecret } from "@/features/alerts/lib/is-alert-secret";
3
import { isAlertMalicious } from "@/features/alerts/lib/is-alert-malicious";
4
import { MaliciousPkgType, TriggerType } from "@/types";
5
import { format, isToday, isYesterday } from "date-fns";
6

7
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
6✔
8
const SEVEN_DAYS_MS = 7 * ONE_DAY_MS;
6✔
9
const TEEN_DAYS_MS = 14 * ONE_DAY_MS;
6✔
10
const THTY_DAYS_MS = 30 * ONE_DAY_MS;
6✔
11
const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g;
6✔
12
const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g;
6✔
13

14
function parsingByKeys(text: string | undefined, timestamp: string) {
15
  const fallback = `Prompt ${format(new Date(timestamp ?? ""), "y/MM/dd - hh:mm:ss a")}`;
299!
16
  try {
299✔
17
    if (!text) return fallback;
299!
18
    const filePath = text.match(FILEPATH_REGEX);
299✔
19
    const compareCode = text.match(COMPARE_CODE_REGEX);
299✔
20
    // there some edge cases in copilot where the prompts are not correctly parsed. In this case is better to show the filepath
21
    if (compareCode || filePath) {
299✔
22
      if (filePath)
3✔
23
        return `Prompt on file${filePath[0]?.trim().toLocaleLowerCase()}`;
2✔
24

25
      if (compareCode)
1!
26
        return `Prompt from snippet ${compareCode[0]?.trim().toLocaleLowerCase()}`;
1✔
27
    }
28

29
    return text.trim();
296✔
30
  } catch {
31
    return fallback;
×
32
  }
33
}
34

35
export function parsingPromptText(message: string, timestamp: string) {
36
  try {
299✔
37
    // checking malformed markdown code blocks
38
    const regex = /^(.*)```[\s\S]*?```(.*)$/s;
299✔
39
    const match = message.match(regex);
299✔
40

41
    if (match !== null && match !== undefined) {
299✔
42
      const beforeMarkdown = match[1]?.trim();
229✔
43
      const afterMarkdown = match[2]?.trim();
229✔
44
      const title = beforeMarkdown || afterMarkdown;
229✔
45
      return parsingByKeys(title, timestamp);
229✔
46
    }
47

48
    return parsingByKeys(message, timestamp);
70✔
49
  } catch {
50
    return message.trim();
×
51
  }
52
}
53

54
function getGroup(differenceInMs: number, promptDate: Date): string {
55
  if (isToday(promptDate)) {
14!
56
    return "Today";
×
57
  }
58
  if (isYesterday(promptDate)) {
14!
59
    return "Yesterday";
×
60
  }
61
  if (differenceInMs <= SEVEN_DAYS_MS) {
14!
62
    return "Previous 7 days";
×
63
  }
64
  if (differenceInMs <= TEEN_DAYS_MS) {
14!
65
    return "Previous 14 days";
×
66
  }
67
  if (differenceInMs <= THTY_DAYS_MS) {
14!
68
    return "Previous 30 days";
14✔
69
  }
70
  return "Beyond 30 days";
×
71
}
72

73
export function groupPromptsByRelativeDate(prompts: Conversation[]) {
74
  const promptsSorted = prompts.sort(
8✔
75
    (a, b) =>
76
      new Date(b.conversation_timestamp).getTime() -
6✔
77
      new Date(a.conversation_timestamp).getTime(),
78
  );
79

80
  const grouped = promptsSorted.reduce(
8✔
81
    (groups, prompt) => {
82
      const promptDate = new Date(prompt.conversation_timestamp);
14✔
83
      const now = new Date();
14✔
84
      const differenceInMs = now.getTime() - promptDate.getTime();
14✔
85
      const group = getGroup(differenceInMs, promptDate);
14✔
86

87
      if (!groups[group]) {
14✔
88
        groups[group] = [];
8✔
89
      }
90

91
      (groups[group] ?? []).push(prompt);
14!
92
      return groups;
14✔
93
    },
94
    {} as Record<string, Conversation[]>,
95
  );
96

97
  return grouped;
8✔
98
}
99

100
export function getAllIssues(alerts: AlertConversation[]) {
UNCOV
101
  const groupedTriggerCounts = alerts.reduce<Record<string, number>>(
×
102
    (acc, alert) => {
UNCOV
103
      const triggerType: TriggerType = alert.trigger_type;
×
UNCOV
104
      if (triggerType) {
×
UNCOV
105
        acc[triggerType] = (acc[triggerType] || 0) + 1;
×
106
      }
UNCOV
107
      return acc;
×
108
    },
109
    {},
110
  );
111

UNCOV
112
  const maxCount = Math.max(...Object.values(groupedTriggerCounts));
×
113

UNCOV
114
  const sortedTagCounts = Object.entries(groupedTriggerCounts).sort(
×
UNCOV
115
    ([, countA], [, countB]) => countB - countA,
×
116
  );
UNCOV
117
  return { maxCount, sortedTagCounts };
×
118
}
119

120
export function getMaliciousPackages() {
121
  const packageCounts = ([] as { packages: [] }[]).reduce<
×
122
    Record<string, number>
123
  >((acc, prompt) => {
124
    (prompt?.packages ?? []).forEach((pkg) => {
×
125
      acc[pkg] = (acc[pkg] || 0) + 1;
×
126
    });
127
    return acc;
×
128
  }, {});
129

130
  const chartData = Object.entries(packageCounts).map(([pkg, count]) => ({
×
131
    id: pkg,
132
    label: pkg,
133
    value: count,
134
  }));
135

136
  return chartData;
×
137
}
138

139
export function sanitizeQuestionPrompt({
140
  question,
141
  answer,
142
}: {
143
  question: string;
144
  answer: string;
145
}) {
146
  try {
300✔
147
    // it shouldn't be possible to receive the prompt answer without a question
148
    if (!answer) {
300!
149
      throw new Error("Missing AI answer");
×
150
    }
151

152
    // Check if 'answer' is truthy; if so, try to find and return the text after "Query:"
153
    const index = question.indexOf("Query:");
300✔
154
    if (index !== -1) {
300!
155
      // Return the substring starting right after the first occurrence of "Query:"
156
      // Adding the length of "Query:" to the index to start after it
157
      return question.substring(index + "Query:".length).trim();
×
158
    }
159
    return question;
300✔
160
  } catch (error) {
161
    // Log the error and return the original question as a fallback
162
    console.error("Error processing the question:", error);
×
163
    return question;
×
164
  }
165
}
166

167
export function getMaliciousPackage(
168
  value: AlertConversation["trigger_string"],
169
): string | (MaliciousPkgType & { [key: string]: string }) | null {
170
  if (typeof value === "string") {
1,202✔
171
    return value;
1,055✔
172
  }
173

174
  if (typeof value === "object" && value !== null) {
147✔
175
    return value as MaliciousPkgType;
65✔
176
  }
177

178
  return null;
82✔
179
}
180

181
export function getIssueDetectedType(
182
  alert: AlertConversation,
183
): "malicious_package" | "leaked_secret" | null {
184
  if (isAlertMalicious(alert)) return "malicious_package";
284✔
185
  if (isAlertSecret(alert)) return "leaked_secret";
248!
186

187
  return null;
×
188
}
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

© 2025 Coveralls, Inc