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

laingsimon / courage_scores / 21874874321

10 Feb 2026 05:11PM UTC coverage: 95.344% (-0.4%) from 95.698%
21874874321

push

github

web-flow
v3.2.15 (#1973)

8336 of 9184 branches covered (90.77%)

Branch coverage included in aggregate %.

51 of 120 new or added lines in 5 files covered. (42.5%)

4 existing lines in 2 files now uncovered.

14908 of 15195 relevant lines covered (98.11%)

217.48 hits per line

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

8.0
/CourageScores/ClientApp/src/components/admin/Query.tsx
1
import { useAdmin } from './AdminContainer';
2✔
2
import { useState } from 'react';
2✔
3
import {
2✔
4
    BootstrapDropdown,
5
    IBootstrapDropdownItem,
6
} from '../common/BootstrapDropdown';
7
import { any, sortBy } from '../../helpers/collections';
2✔
8
import { LoadingSpinnerSmall } from '../common/LoadingSpinnerSmall';
2✔
9
import { useLocation, useNavigate } from 'react-router';
2✔
10
import { useDependencies } from '../common/IocContainer';
2✔
11
import { QueryRequestDto } from '../../interfaces/models/dtos/Query/QueryRequestDto';
12
import { useApp } from '../common/AppContainer';
2✔
13
import { QueryResponseDto } from '../../interfaces/models/dtos/Query/QueryResponseDto';
14
import { IClientActionResultDto } from '../common/IClientActionResultDto';
15
import { stateChanged } from '../../helpers/events';
2✔
16
import { renderDate } from '../../helpers/rendering';
2✔
17
import { isGuid } from '../../helpers/projection';
2✔
18

19
export function Query() {
2✔
20
    const { onError } = useApp();
×
21
    const { tables: containers } = useAdmin();
×
22
    const { queryApi } = useDependencies();
×
23
    const location = useLocation();
×
24
    const navigate = useNavigate();
×
25
    const [executing, setExecuting] = useState<boolean>(false);
×
26
    const [results, setResults] = useState<
×
27
        IClientActionResultDto<QueryResponseDto> | undefined
28
    >(undefined);
29
    const containerOptions: IBootstrapDropdownItem[] =
30
        containers?.sort(sortBy('name')).map((t) => ({
×
31
            label: t.name,
32
            value: t.name,
33
        })) ?? [];
34
    const search = new URLSearchParams(location.search);
×
35
    const container = search.get('container');
×
36
    const query = search.get('query');
×
37
    const max = search.get('max');
×
NEW
38
    const [tentativeQuery, setTentativeQuery] = useState<string>(query || '');
×
39

40
    async function changeParams(name: string, value: string) {
41
        const newQuery = new URLSearchParams(location.search);
×
42
        if (value) {
×
43
            newQuery.set(name, value);
×
44
        } else {
45
            newQuery.delete(name);
×
46
        }
47

NEW
48
        if (name === 'container' && value && !newQuery.get('query')) {
×
NEW
49
            const alias = value.substring(0, 1);
×
NEW
50
            const query = `select *\nfrom ${value} ${alias}`;
×
NEW
51
            newQuery.set('query', query);
×
NEW
52
            setTentativeQuery(query);
×
53
        }
54

NEW
55
        navigate(`/admin/query/?${newQuery}`);
×
56
    }
57

58
    async function onExecute() {
59
        if (executing) {
×
60
            return;
×
61
        }
62

63
        if (!container) {
×
64
            alert('Select a container first');
×
65
            return;
×
66
        }
NEW
67
        if (!tentativeQuery) {
×
68
            alert('Enter a query first');
×
69
            return;
×
70
        }
71

72
        setExecuting(true);
×
73

74
        try {
×
75
            const request: QueryRequestDto = {
×
76
                container: container,
77
                query: tentativeQuery,
78
                max: max ? Number.parseInt(max) : undefined,
×
79
            };
80

81
            const results = await queryApi.execute(request);
×
82
            setResults(results);
×
83
        } catch (e) {
84
            onError(e);
×
85
        } finally {
86
            setExecuting(false);
×
87
        }
88
    }
89

90
    function renderValue(value?: object) {
91
        if (value === null) {
×
92
            return `null`;
×
93
        }
94

95
        if (typeof value === 'object') {
×
96
            return <pre>{JSON.stringify(value, null, 2)}</pre>;
×
97
        }
98

NEW
99
        const stringValue = value as string | undefined;
×
100
        const isALink =
NEW
101
            stringValue?.startsWith &&
×
102
            (stringValue.startsWith('/') || stringValue.startsWith('http'));
NEW
103
        if (isALink) {
×
NEW
104
            return <a href={value}>{value}</a>;
×
105
        }
106

NEW
107
        if (
×
108
            stringValue?.match &&
×
109
            stringValue.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+Z)?$/)
110
        ) {
111
            // date and optional time
112

NEW
113
            const datePart = renderDate(stringValue?.substring(0, 10));
×
NEW
114
            const timePart = stringValue.substring(11);
×
NEW
115
            const isDateOnly = timePart.match(/^[0:.Z]+$/);
×
116

NEW
117
            return (
×
118
                <abbr title={stringValue}>
119
                    {datePart}
120
                    {isDateOnly ? '' : ' @ ' + timePart.substring(0, 5)}
×
121
                </abbr>
122
            );
123
        }
124

NEW
125
        if (stringValue?.match && isGuid(stringValue)) {
×
NEW
126
            const firstPart = stringValue!.substring(0, 4);
×
NEW
127
            const lastPart = stringValue!.substring(32);
×
128

NEW
129
            return (
×
130
                <abbr title={stringValue}>
131
                    {firstPart}-{lastPart}
132
                </abbr>
133
            );
134
        }
135

UNCOV
136
        return value;
×
137
    }
138

139
    function getHeadingLookup(rows?: object[]): string[] {
140
        const headings: { [key: string]: number } = {};
×
141

142
        if (!rows) {
×
143
            return [];
×
144
        }
145

146
        for (const row of rows) {
×
147
            const rowHeadings = Object.keys(row);
×
148
            for (let rowHeading of rowHeadings) {
×
149
                if (!headings[rowHeading]) {
×
150
                    headings[rowHeading] = 1;
×
151
                }
152
            }
153
        }
154

155
        return Object.keys(headings);
×
156
    }
157

158
    const headingLookup = getHeadingLookup(results?.result?.rows);
×
159

NEW
160
    try {
×
NEW
161
        return (
×
162
            <div className="content-background p-3">
163
                <div className="input-group mb-3">
164
                    <div className="d-flex pe-3 align-self-center fs-5">
165
                        Query
166
                    </div>
167
                    <BootstrapDropdown
168
                        onChange={(container) =>
NEW
169
                            changeParams('container', container!)
×
170
                        }
171
                        options={containerOptions}
172
                        value={container || ''}
×
173
                    />
174
                    <div className="input-group-prepend ms-1">
175
                        <span className="input-group-text">Limit</span>
176
                    </div>
177
                    <input
178
                        type="number"
179
                        className="form-control"
180
                        placeholder="Max"
181
                        value={max || ''}
×
182
                        min={1}
NEW
183
                        onChange={(e) => changeParams('max', e.target.value)}
×
184
                    />
185
                    <button
186
                        className="btn btn-primary ms-1"
187
                        onClick={onExecute}>
188
                        {executing ? <LoadingSpinnerSmall /> : null}
×
189
                        Execute
190
                    </button>
191
                </div>
192
                <div>
193
                    <textarea
194
                        className="width-100 font-monospace form-control"
195
                        name="query"
196
                        value={tentativeQuery}
197
                        rows={8}
198
                        onChange={stateChanged(setTentativeQuery)}
NEW
199
                        onBlur={() => changeParams('query', tentativeQuery)}
×
200
                    />
201
                </div>
202
                <div>
203
                    {!executing &&
×
204
                    results?.success === true &&
205
                    results.result ? (
206
                        <div className="overflow-x-auto">
207
                            <h5>
208
                                {results.result.rows.length} of{' '}
209
                                {results.result.rowCount} rows
210
                            </h5>
211

212
                            <table className="table table-sm table-striped">
213
                                <thead>
214
                                    <tr>
215
                                        <th>#</th>
216
                                        {headingLookup.map((heading, index) => (
NEW
217
                                            <th key={index}>{heading}</th>
×
218
                                        ))}
219
                                    </tr>
220
                                </thead>
221
                                <tbody>
222
                                    {results.result.rows.map(
223
                                        (row, rowIndex) => (
NEW
224
                                            <tr key={rowIndex}>
×
225
                                                <td>{rowIndex + 1}</td>
226
                                                {headingLookup.map(
227
                                                    (heading, index) => (
NEW
228
                                                        <td key={index}>
×
229
                                                            {renderValue(
230
                                                                row[heading],
231
                                                            )}
232
                                                        </td>
233
                                                    ),
234
                                                )}
235
                                            </tr>
236
                                        ),
237
                                    )}
238
                                </tbody>
239
                            </table>
240
                        </div>
241
                    ) : null}
242
                    {!executing && any(results?.errors) ? (
×
243
                        <div className="alert alert-danger">
244
                            {results!.errors?.map((e, i) => (
NEW
245
                                <li key={i}>{e}</li>
×
246
                            ))}
247
                        </div>
248
                    ) : null}
249
                    {!executing && any(results?.warnings) ? (
×
250
                        <div className="alert alert-warning">
251
                            {results!.warnings?.map((w, i) => (
NEW
252
                                <li key={i}>{w}</li>
×
253
                            ))}
254
                        </div>
255
                    ) : null}
256
                </div>
257
            </div>
258
        );
259
    } catch (e) {
NEW
260
        onError(e);
×
261
    }
262
}
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