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

eliashaeussler / composer-update-check / 10714779546

05 Sep 2024 06:03AM UTC coverage: 20.396% (-0.02%) from 20.418%
10714779546

Pull #144

github

web-flow
Merge 7f4d802b5 into 612345643
Pull Request #144: [TASK] Improve column width in Teams report table

0 of 9 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

371 of 1819 relevant lines covered (20.4%)

1.09 hits per line

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

0.0
/src/Entity/Report/TeamsReport.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/composer-update-check".
7
 *
8
 * Copyright (C) 2020-2024 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\ComposerUpdateCheck\Entity\Report;
25

26
use EliasHaeussler\ComposerUpdateCheck\Entity;
27
use JsonSerializable;
28

29
use function sprintf;
30

31
/**
32
 * TeamsReport.
33
 *
34
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
35
 * @license GPL-3.0-or-later
36
 */
37
final class TeamsReport implements JsonSerializable
38
{
39
    private const SECURITY_ADVISORIES_CONTAINER_ID = 'securityAdvisories';
40

41
    /**
42
     * @param list<Dto\TeamsAttachment> $attachments
43
     */
44
    private function __construct(
×
45
        public readonly string $type,
46
        public readonly array $attachments,
47
    ) {}
×
48

49
    public static function create(Entity\Result\UpdateCheckResult $result): self
×
50
    {
51
        return new self(
×
52
            'message',
×
53
            self::createAttachments($result),
×
54
        );
×
55
    }
56

57
    /**
58
     * @return list<Dto\TeamsAttachment>
59
     */
60
    private static function createAttachments(Entity\Result\UpdateCheckResult $result): array
×
61
    {
62
        $attachment = Dto\TeamsAttachment::adaptiveCard(
×
63
            self::createCardBody($result),
×
64
            self::createFallbackText($result),
×
65
            self::createCardActions($result),
×
66
        );
×
67

68
        return [$attachment];
×
69
    }
70

71
    /**
72
     * @return list<Dto\TeamsContent>
73
     */
74
    private static function createCardBody(Entity\Result\UpdateCheckResult $result): array
×
75
    {
76
        $rootPackageName = $result->getRootPackage()?->getName();
×
77
        $numberOfOutdatedPackages = 0;
×
78
        $numberOfInsecurePackages = 0;
×
79
        $highestSeverityLevels = [];
×
80

81
        // Count outdated and insecure packages
82
        foreach ($result->getOutdatedPackages() as $outdatedPackage) {
×
83
            ++$numberOfOutdatedPackages;
×
84

85
            if ($outdatedPackage->isInsecure()) {
×
86
                ++$numberOfInsecurePackages;
×
87
                $highestSeverityLevels[] = $outdatedPackage->getHighestSeverityLevel();
×
88
            }
89
        }
90

91
        // Add title
92
        $contents = [
×
93
            Dto\TeamsContent::textBlock(
×
94
                text: sprintf(
×
95
                    '%d package%s%s %s outdated',
×
96
                    $numberOfOutdatedPackages,
×
97
                    1 !== $numberOfOutdatedPackages ? 's' : '',
×
98
                    $numberOfInsecurePackages > 0 ? sprintf(' (%d insecure)', $numberOfInsecurePackages) : '',
×
99
                    1 !== $numberOfOutdatedPackages ? 'are' : 'is',
×
100
                ),
×
101
                wrap: true,
×
102
                size: 'Large',
×
103
                weight: 'Bolder',
×
104
            ),
×
105
        ];
×
106

107
        // Add summary
108
        if (null !== $rootPackageName) {
×
109
            $contents[] = Dto\TeamsContent::textBlock(
×
110
                text: sprintf(
×
111
                    'Project **%s** has **%d outdated package%s**.',
×
112
                    $rootPackageName,
×
113
                    $numberOfOutdatedPackages,
×
114
                    1 !== $numberOfOutdatedPackages ? 's' : '',
×
115
                ),
×
116
                wrap: true,
×
117
            );
×
118

119
            if ([] !== $highestSeverityLevels) {
×
120
                $highestSeverityLevel = Entity\Security\SeverityLevel::getHighestSeverityLevel(...$highestSeverityLevels);
×
121
                $contents[] = Dto\TeamsContent::textBlock(
×
122
                    text: sprintf(
×
NEW
123
                        '%s marked as **insecure** with a highest severity of **%s %s**.',
×
124
                        $numberOfInsecurePackages === $numberOfOutdatedPackages
×
125
                            ? ($numberOfInsecurePackages > 1 ? 'They are' : 'It is')
×
126
                            : sprintf(
×
127
                                '%d of them %s',
×
128
                                $numberOfInsecurePackages,
×
129
                                $numberOfInsecurePackages > 1 ? 'are' : 'is',
×
130
                            ),
×
NEW
131
                        self::getEmojiForSeverityLevel($highestSeverityLevel),
×
132
                        $highestSeverityLevel->value,
×
133
                    ),
×
134
                    wrap: true,
×
135
                    spacing: 'None',
×
136
                );
×
137
            }
138
        }
139

140
        // Add table with outdated packages
141
        $contents[] = self::createTableWithOutdatedPackages($result);
×
142

143
        // Add container with security advisories
144
        if ($numberOfInsecurePackages > 0) {
×
145
            $contents[] = self::createSecurityAdvisoriesContainer($result);
×
146
        }
147

148
        return $contents;
×
149
    }
150

151
    private static function createTableWithOutdatedPackages(Entity\Result\UpdateCheckResult $result): Dto\TeamsContent
×
152
    {
153
        $hasInsecureOutdatedPackages = $result->hasInsecureOutdatedPackages();
×
NEW
154
        $columns = [];
×
155
        $rowCells = [];
×
156
        $rowHeaders = [
×
NEW
157
            'Package' => 2,
×
NEW
158
            'Current version' => 1,
×
NEW
159
            'New version' => 1,
×
UNCOV
160
        ];
×
161

162
        if ($hasInsecureOutdatedPackages) {
×
NEW
163
            $rowHeaders['Security advisory'] = 1;
×
164
        }
165

NEW
166
        foreach ($rowHeaders as $rowHeader => $columnWidth) {
×
NEW
167
            $columns[] = new Dto\TeamsTableColumn($columnWidth);
×
168
            $rowCells[] = new Dto\TeamsTableCell(
×
169
                [
×
170
                    Dto\TeamsContent::textBlock(
×
171
                        text: $rowHeader,
×
172
                        wrap: true,
×
173
                    ),
×
174
                ],
×
175
            );
×
176
        }
177

178
        $rows = [
×
179
            new Dto\TeamsTableRow(
×
180
                cells: $rowCells,
×
181
                spacing: 'None',
×
182
                horizontalCellContentAlignment: 'Left',
×
183
                verticalCellContentAlignment: 'Top',
×
184
            ),
×
185
        ];
×
186

187
        // Add table rows
188
        foreach ($result->getOutdatedPackages() as $outdatedPackage) {
×
189
            $cells = [
×
190
                new Dto\TeamsTableCell(
×
191
                    [
×
192
                        Dto\TeamsContent::textBlock(
×
193
                            text: sprintf('[%s](%s)', $outdatedPackage->getName(), $outdatedPackage->getProviderLink()),
×
194
                            wrap: true,
×
195
                        ),
×
196
                    ],
×
197
                ),
×
198
                new Dto\TeamsTableCell(
×
199
                    [
×
200
                        Dto\TeamsContent::textBlock(
×
201
                            text: $outdatedPackage->getOutdatedVersion()->toString(),
×
202
                            wrap: true,
×
203
                        ),
×
204
                    ],
×
205
                ),
×
206
                new Dto\TeamsTableCell(
×
207
                    [
×
208
                        Dto\TeamsContent::textBlock(
×
209
                            text: $outdatedPackage->getNewVersion()->toString(),
×
210
                            wrap: true,
×
211
                            weight: 'Bolder',
×
212
                        ),
×
213
                    ],
×
214
                ),
×
215
            ];
×
216

217
            if ($outdatedPackage->isInsecure()) {
×
218
                $severityLevel = $outdatedPackage->getHighestSeverityLevel();
×
219
                $cells[] = new Dto\TeamsTableCell(
×
220
                    [
×
221
                        Dto\TeamsContent::textBlock(
×
222
                            text: sprintf(
×
223
                                '%s %s',
×
224
                                self::getEmojiForSeverityLevel($severityLevel),
×
225
                                $severityLevel->value,
×
226
                            ),
×
227
                            wrap: true,
×
228
                        ),
×
229
                    ],
×
230
                );
×
231
            } elseif ($hasInsecureOutdatedPackages) {
×
232
                $cells[] = new Dto\TeamsTableCell(
×
233
                    [
×
234
                        Dto\TeamsContent::textBlock(''),
×
235
                    ],
×
236
                );
×
237
            }
238

239
            $rows[] = new Dto\TeamsTableRow($cells);
×
240
        }
241

242
        return Dto\TeamsContent::table(
×
243
            columns: $columns,
×
244
            rows: $rows,
×
245
            firstRowAsHeader: true,
×
246
            gridStyle: 'default',
×
247
        );
×
248
    }
249

250
    private static function createSecurityAdvisoriesContainer(Entity\Result\UpdateCheckResult $result): Dto\TeamsContent
×
251
    {
252
        $items = [
×
253
            Dto\TeamsContent::textBlock(
×
254
                text: 'Security advisories',
×
255
                wrap: true,
×
256
                size: 'Large',
×
257
                weight: 'Bolder',
×
258
            ),
×
259
        ];
×
260

261
        foreach ($result->getInsecureOutdatedPackages() as $insecurePackage) {
×
262
            foreach ($insecurePackage->getSecurityAdvisories() as $securityAdvisory) {
×
263
                $facts = [
×
264
                    new Dto\TeamsFact('Package', $securityAdvisory->getPackageName()),
×
265
                    new Dto\TeamsFact('Reported at', $securityAdvisory->getReportedAt()->format('Y-m-d')),
×
266
                ];
×
267

268
                if (null !== $securityAdvisory->getCVE()) {
×
269
                    $facts[] = new Dto\TeamsFact('CVE', $securityAdvisory->getCVE());
×
270
                }
271

272
                $containerItems = [
×
273
                    Dto\TeamsContent::textBlock(
×
274
                        text: sprintf(
×
275
                            '%s %s',
×
276
                            self::getEmojiForSeverityLevel($securityAdvisory->getSeverity()),
×
277
                            $securityAdvisory->getSanitizedTitle(),
×
278
                        ),
×
279
                        wrap: true,
×
280
                        weight: 'Bolder',
×
281
                    ),
×
282
                    Dto\TeamsContent::factSet(
×
283
                        facts: $facts,
×
284
                        spacing: 'Small',
×
285
                    ),
×
286
                ];
×
287

288
                if (null !== $securityAdvisory->getLink()) {
×
289
                    $containerItems[] = Dto\TeamsContent::textBlock(
×
290
                        text: sprintf('[Read more](%s)', $securityAdvisory->getLink()),
×
291
                        wrap: true,
×
292
                        weight: 'Small',
×
293
                    );
×
294
                }
295

296
                $items[] = Dto\TeamsContent::container($containerItems);
×
297
            }
298
        }
299

300
        return Dto\TeamsContent::container(
×
301
            items: $items,
×
302
            isVisible: false,
×
303
            id: self::SECURITY_ADVISORIES_CONTAINER_ID,
×
304
            spacing: 'Medium',
×
305
        );
×
306
    }
307

308
    private static function createFallbackText(Entity\Result\UpdateCheckResult $result): string
×
309
    {
310
        $rootPackageName = $result->getRootPackage()?->getName();
×
311
        $numberOfOutdatedPackages = 0;
×
312
        $numberOfInsecurePackages = 0;
×
313
        $highestSeverityLevels = [];
×
314
        $addition = '';
×
315

316
        // Count outdated and insecure packages
317
        foreach ($result->getOutdatedPackages() as $outdatedPackage) {
×
318
            ++$numberOfOutdatedPackages;
×
319

320
            if ($outdatedPackage->isInsecure()) {
×
321
                ++$numberOfInsecurePackages;
×
322
                $highestSeverityLevels[] = $outdatedPackage->getHighestSeverityLevel();
×
323
            }
324
        }
325

326
        if ([] !== $highestSeverityLevels) {
×
327
            $highestSeverityLevel = Entity\Security\SeverityLevel::getHighestSeverityLevel(...$highestSeverityLevels);
×
328
            $addition = sprintf(
×
329
                ' %s marked as insecure with a highest severity of "%s".',
×
330
                $numberOfInsecurePackages === $numberOfOutdatedPackages
×
331
                    ? ($numberOfInsecurePackages > 1 ? 'They are' : 'It is')
×
332
                    : sprintf(
×
333
                        '%d of them %s',
×
334
                        $numberOfInsecurePackages,
×
335
                        $numberOfInsecurePackages > 1 ? 'are' : 'is',
×
336
                    ),
×
337
                $highestSeverityLevel->value,
×
338
            );
×
339
        }
340

341
        if (null !== $rootPackageName) {
×
342
            return sprintf(
×
343
                'Project "%s" has %d outdated package%s.%s',
×
344
                $rootPackageName,
×
345
                $numberOfOutdatedPackages,
×
346
                1 !== $numberOfOutdatedPackages ? 's' : '',
×
347
                $addition,
×
348
            );
×
349
        }
350

351
        return sprintf(
×
352
            '%d package%s are currently outdated.%s',
×
353
            $numberOfOutdatedPackages,
×
354
            1 !== $numberOfOutdatedPackages ? 's' : '',
×
355
            $addition,
×
356
        );
×
357
    }
358

359
    /**
360
     * @return list<Dto\TeamsAction>
361
     */
362
    private static function createCardActions(Entity\Result\UpdateCheckResult $result): array
×
363
    {
364
        if (!$result->hasInsecureOutdatedPackages()) {
×
365
            return [];
×
366
        }
367

368
        $actionId = 'showSecurityAdvisories';
×
369

370
        return [
×
371
            Dto\TeamsAction::toggleVisibility(
×
372
                $actionId,
×
373
                'Show security advisories',
×
374
                [
×
375
                    self::SECURITY_ADVISORIES_CONTAINER_ID,
×
376
                    $actionId,
×
377
                ],
×
378
            ),
×
379
        ];
×
380
    }
381

382
    private static function getEmojiForSeverityLevel(Entity\Security\SeverityLevel $severityLevel): string
×
383
    {
384
        return match ($severityLevel) {
×
385
            Entity\Security\SeverityLevel::Low => '⚪',
×
386
            Entity\Security\SeverityLevel::Medium => '🟡',
×
387
            Entity\Security\SeverityLevel::High => '🟠',
×
388
            Entity\Security\SeverityLevel::Critical => '🔴',
×
389
        };
×
390
    }
391

392
    /**
393
     * @return array{
394
     *     type: string,
395
     *     attachments: list<Dto\TeamsAttachment>,
396
     * }
397
     */
398
    public function jsonSerialize(): array
×
399
    {
400
        return [
×
401
            'type' => $this->type,
×
402
            'attachments' => $this->attachments,
×
403
        ];
×
404
    }
405
}
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