Fix some sonar smells in frontend code

This commit is contained in:
Aurora Lahtela 2023-04-08 09:13:38 +03:00
parent 58eae50428
commit f43d8f89fb
21 changed files with 109 additions and 70 deletions

View File

@ -49,7 +49,6 @@ const Accordion = ({headers, slices, open, style}) => {
}
const width = headers.length;
return (
<table className={"table accordion-striped" + (nightModeEnabled ? " table-dark" : '')} id="tableAccordion"
style={style}>

View File

@ -48,7 +48,8 @@ const QueryPath = () => {
return (
<aside id={"result-path"} className={"alert shadow " + (hasResults ? "alert-success" : "alert-warning")}>
{path.map((step, i) => <p key={i} style={{marginBottom: 0, marginLeft: i * 0.7 + "rem"}}>
{path.map((step, i) => <p key={step.kind + step.size}
style={{marginBottom: 0, marginLeft: i * 0.7 + "rem"}}>
<FontAwesomeIcon
icon={faFilter}/> '{getReadableFilterName(step.kind)}' matched {step.size} players
</p>)}

View File

@ -1,11 +1,11 @@
import React, {useEffect, useState} from 'react';
import {Transition} from 'react-transition-group';
const reduceAnimations = window.matchMedia(`(prefers-reduced-motion: reduce)`) === true;
const defaultDuration = 250;
const LoadIn = ({children, duration}) => {
if (!duration) duration = defaultDuration;
const reduceAnimations = window.matchMedia(`(prefers-reduced-motion: reduce)`).matches;
const defaultStyle = reduceAnimations ? {
transition: `opacity ${duration}ms ease-in-out`,

View File

@ -22,8 +22,8 @@ const ProjectionDropDown = ({projection, setProjection}) => {
<Dropdown.Menu>
<h6 className="dropdown-header">{t('html.label.geoProjection.dropdown')}</h6>
{projectionOptions.map((option, i) => (
<Dropdown.Item key={i} onClick={() => setProjection(option)}>
{projectionOptions.map(option => (
<Dropdown.Item key={option} onClick={() => setProjection(option)}>
{t(option)}
</Dropdown.Item>
))}

View File

@ -23,8 +23,8 @@ const SortDropDown = ({sortBy, sortReversed, setSortBy}) => {
<Dropdown.Menu>
<h6 className="dropdown-header">{t('html.label.sortBy')}</h6>
{sortOptions.map((option, i) => (
<Dropdown.Item key={i} onClick={() => setSortBy(option)}>
{sortOptions.map(option => (
<Dropdown.Item key={option} onClick={() => setSortBy(option)}>
{t(option.label)}
</Dropdown.Item>
))}

View File

@ -27,7 +27,7 @@ const NicknamesCard = ({player}) => {
</tr>
</thead>
<tbody>
{player.nicknames.map((nickname, i) => (<tr key={'nick-' + i}>
{player.nicknames.map(nickname => (<tr key={JSON.stringify(nickname)}>
<td dangerouslySetInnerHTML={{__html: nickname.nickname}}/>
<td>{nickname.server}</td>
<td>{nickname.date}</td>

View File

@ -51,8 +51,8 @@ const FilterDropdown = ({filterOptions, filters, setFilters}) => {
<Dropdown.Menu popperConfig={{strategy: "absolute"}}>
<h6 className="dropdown-header">{t('html.query.filters.add')}</h6>
<Scrollable>
{filterOptions.map((option, i) => (
<Dropdown.Item key={i} onClick={() => addFilter(option)}>
{filterOptions.map(option => (
<Dropdown.Item key={option} onClick={() => addFilter(option)}>
{getReadableFilterName(option)}
</Dropdown.Item>
))}

View File

@ -29,7 +29,7 @@ const FilterList = ({filters, setFilters, setAsInvalid, setAsValid}) => {
return (
<ul id={"filters"} className={"filters"}>
{filters.map((filter, i) => <li key={i} className={"filter"}>
{filters.map((filter, i) => <li key={JSON.stringify(filter)} className={"filter"}>
<Filter filter={filter} index={i}
setFilterOptions={newOptions => updateFilterOptions(i, newOptions)}
removeFilter={() => removeFilter(i)}

View File

@ -23,28 +23,36 @@ const PlayerbaseTrendsCard = ({data}) => {
headers={[t('html.label.thirtyDaysAgo'), t('html.label.now'), t('html.label.trend')]}>
<TableRow icon={faUsers} color="black" text={t('html.label.totalPlayers')}
values={[data.total_players_then, data.total_players_now,
<BigTrend trend={data.total_players_trend}/>]}/>
<BigTrend key={JSON.stringify(data.total_players_trend)}
trend={data.total_players_trend}/>]}/>
<TableRow icon={faUsers} color="lime" text={t('html.label.regularPlayers')}
values={[data.regular_players_then, data.regular_players_now,
<BigTrend trend={data.regular_players_trend}/>]}/>
<BigTrend key={JSON.stringify(data.regular_players_trend)}
trend={data.regular_players_trend}/>]}/>
<TableRow icon={faClock} color="green"
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perPlayer')}
values={[data.playtime_avg_then, data.playtime_avg_now,
<BigTrend trend={data.playtime_avg_trend}/>]}/>
<BigTrend key={JSON.stringify(data.total_players_trend)}
trend={data.playtime_avg_trend}/>]}/>
<TableRow icon={faClock} color="gray" text={t('html.label.afk') + ' ' + t('html.label.perPlayer')}
values={[data.afk_then, data.afk_now, <BigTrend trend={data.afk_trend}/>]}/>
values={[data.afk_then, data.afk_now,
<BigTrend key={JSON.stringify(data.afk_trend)}
trend={data.afk_trend}/>]}/>
<TableRow icon={faClock} color="green"
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perRegularPlayer')}
values={[data.regular_playtime_avg_then, data.regular_playtime_avg_now,
<BigTrend trend={data.regular_playtime_avg_trend}/>]}/>
<BigTrend key={JSON.stringify(data.regular_playtime_avg_trend)}
trend={data.regular_playtime_avg_trend}/>]}/>
<TableRow icon={faClock} color="teal"
text={t('html.label.averageSessionLength') + ' ' + t('html.label.perRegularPlayer')}
values={[data.regular_session_avg_then, data.regular_session_avg_now,
<BigTrend trend={data.regular_session_avg_trend}/>]}/>
<BigTrend key={JSON.stringify(data.regular_session_avg_trend)}
trend={data.regular_session_avg_trend}/>]}/>
<TableRow icon={faClock} color="gray"
text={t('html.label.afk') + ' ' + t('html.label.perRegularPlayer')}
values={[data.regular_afk_avg_then, data.regular_afk_avg_now,
<BigTrend trend={data.regular_afk_avg_trend}/>]}/>
<BigTrend key={JSON.stringify(data.regular_afk_avg_trend)}
trend={data.regular_afk_avg_trend}/>]}/>
</ComparisonTable>
</Card>
)

View File

@ -22,30 +22,43 @@ const ServerWeekComparisonCard = ({data}) => {
<ComparisonTable comparisonHeader={t('html.label.comparing7days')}
headers={[data.start + ' - ' + data.midpoint, data.midpoint + ' - ' + data.end, t('html.label.trend')]}>
<TableRow icon={faUsers} color="blue" text={t('html.label.uniquePlayers')}
values={[data.unique_before, data.unique_after, <BigTrend trend={data.unique_trend}/>]}/>
values={[data.unique_before, data.unique_after,
<BigTrend key={JSON.stringify(data.unique_trend)}
trend={data.unique_trend}/>]}/>
<TableRow icon={faUsers} color="light-green" text={t('html.label.newPlayers')}
values={[data.new_before, data.new_after, <BigTrend trend={data.new_trend}/>]}/>
values={[data.new_before, data.new_after,
<BigTrend key={JSON.stringify(data.new_trend)}
trend={data.new_trend}/>]}/>
<TableRow icon={faUsers} color="lime" text={t('html.label.regularPlayers')}
values={[data.regular_before, data.regular_after, <BigTrend trend={data.regular_trend}/>]}/>
values={[data.regular_before, data.regular_after,
<BigTrend key={JSON.stringify(data.regular_trend)}
trend={data.regular_trend}/>]}/>
<TableRow icon={faClock} color="green"
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perPlayer')}
values={[data.average_playtime_before, data.average_playtime_after,
<BigTrend trend={data.average_playtime_trend}/>]}/>
<BigTrend key={JSON.stringify(data.average_playtime_trend)}
trend={data.average_playtime_trend}/>]}/>
<TableRow icon={faClock} color="teal"
text={t('html.label.averageSessionLength')}
values={[data.session_length_average_before, data.session_length_average_after,
<BigTrend trend={data.session_length_average_trend}/>]}/>
<BigTrend key={JSON.stringify(data.session_length_average_trend)}
trend={data.session_length_average_trend}/>]}/>
<TableRow icon={faCalendarCheck} color="teal" text={t('html.label.sessions')}
values={[data.sessions_before, data.sessions_after,
<BigTrend trend={data.sessions_trend}/>]}/>
<BigTrend key={JSON.stringify(data.sessions_trend)}
trend={data.sessions_trend}/>]}/>
<TableRow icon={faCrosshairs} color="red" text={t('html.label.playerKills')}
values={[data.player_kills_before, data.player_kills_after,
<BigTrend trend={data.player_kills_trend}/>]}/>
<BigTrend key={JSON.stringify(data.player_kills_trend)}
trend={data.player_kills_trend}/>]}/>
<TableRow icon={faCrosshairs} color="green" text={t('html.label.mobKills')}
values={[data.mob_kills_before, data.mob_kills_after,
<BigTrend trend={data.mob_kills_trend}/>]}/>
<BigTrend key={JSON.stringify(data.mob_kills_trend)}
trend={data.mob_kills_trend}/>]}/>
<TableRow icon={faSkull} color="black" text={t('html.label.deaths')}
values={[data.deaths_before, data.deaths_after, <BigTrend trend={data.deaths_trend}/>]}/>
values={[data.deaths_before, data.deaths_after,
<BigTrend key={JSON.stringify(data.deaths_trend)}
trend={data.deaths_trend}/>]}/>
</ComparisonTable>
</Card>
)

View File

@ -96,7 +96,8 @@ const ExtensionCard = ({extension}) => {
</Card.Header>
<ul className="nav nav-tabs tab-nav-right" role="tablist">
{extension.onlyGenericTab ? '' :
extension.tabs.map((tab, i) => <li key={i} role="presentation" className="nav-item col-black">
extension.tabs.map((tab, i) => <li key={JSON.stringify(tab)} role="presentation"
className="nav-item col-black">
<button className={"nav-link col-black"
+ (openTabIndex === i ? ' active' : '')} onClick={() => toggleTabIndex(i)}>
<ExtensionIcon icon={tab.tabInformation.icon}/> {tab.tabInformation.tabName}

View File

@ -13,7 +13,7 @@ const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
onChange={handleChange}>
{options.map((option, i) => {
return (
<option key={i} value={selectedIndexes.includes(i)}
<option key={JSON.stringify(option)} value={selectedIndexes.includes(i)}
selected={selectedIndexes.includes(i)}>{option}</option>
)
})}

View File

@ -58,7 +58,7 @@ const getContributionIcon = (type) => {
const Contributor = ({contributor}) => {
const icons = contributor.contributed.map(
(type, i) => <Fa key={i} icon={["fa", getContributionIcon(type)]}/>);
type => <Fa key={"" + type} icon={["fa", getContributionIcon(type)]}/>);
return (
<li className="contributor">{contributor.name} {icons} </li>
)
@ -78,7 +78,7 @@ const Contributions = () => {
<p>In addition following <span className="col-theme">awesome people</span> have
contributed:</p>
<ul className="row contributors">
{contributors.map((contributor, i) => <Contributor key={i} contributor={contributor}/>)}
{contributors.map((contributor, i) => <Contributor key={contributor.name} contributor={contributor}/>)}
<li>{t('html.modal.info.contributors.bugreporters')}</li>
</ul>
<small>

View File

@ -22,8 +22,8 @@ const LanguageSelector = () => {
className="form-select form-select-sm"
id="langSelector"
defaultValue={localeService.clientLocale}>
{languages.map((lang, i) =>
<option key={i} value={lang.name}>{lang.displayName}</option>)}
{languages.map(lang =>
<option key={lang.name} value={lang.name}>{lang.displayName}</option>)}
</select>
)
}

View File

@ -150,8 +150,8 @@ const SidebarCollapse = ({item, open, setOpen, collapseSidebar}) => {
<div className="bg-white py-2 collapse-inner rounded">
{item.contents
.filter(content => content !== undefined)
.map((content, i) =>
<Item key={i}
.map(content =>
<Item key={JSON.stringify(content)}
inner
active={false}
item={content}

View File

@ -30,7 +30,7 @@ const GroupTable = ({groups, colors}) => {
<table className={"table mb-0" + (nightModeEnabled ? " table-dark" : '')}>
<tbody>
{groups.length ? groups.map((group, i) =>
<GroupRow key={i}
<GroupRow key={group.name}
group={group}
color={getColor(i)}/>) :
<tr>

View File

@ -1,4 +1,4 @@
import {createContext, useCallback, useContext, useEffect, useState} from "react";
import {createContext, useCallback, useContext, useEffect, useMemo, useState} from "react";
import {fetchWhoAmI} from "../service/authenticationService";
const AuthenticationContext = createContext({});
@ -35,7 +35,18 @@ export const AuthenticationContextProvider = ({children}) => {
updateLoginDetails();
}, [updateLoginDetails]);
const sharedState = {
const sharedState = useMemo(() => {
return {
authLoaded,
authRequired,
loggedIn,
user,
loginError,
hasPermission,
hasPermissionOtherThan,
updateLoginDetails
}
}, [
authLoaded,
authRequired,
loggedIn,
@ -44,7 +55,7 @@ export const AuthenticationContextProvider = ({children}) => {
hasPermission,
hasPermissionOtherThan,
updateLoginDetails
}
])
return (<AuthenticationContext.Provider value={sharedState}>
{children}
</AuthenticationContext.Provider>

View File

@ -1,4 +1,4 @@
import {createContext, useContext, useEffect, useState} from "react";
import {createContext, useContext, useEffect, useMemo, useState} from "react";
import {useDataRequest} from "./dataFetchHook";
import {fetchExtensionData} from "../service/serverService";
@ -15,7 +15,9 @@ export const ServerExtensionContextProvider = ({identifier, children}) => {
setExtensionDataLoadingError(loadingError);
}, [data, loadingError, setExtensionData, setExtensionDataLoadingError])
const sharedState = {extensionData, extensionDataLoadingError}
const sharedState = useMemo(() => {
return {extensionData, extensionDataLoadingError};
}, [extensionData, extensionDataLoadingError]);
return (<ServerExtensionContext.Provider value={sharedState}>
{children}
</ServerExtensionContext.Provider>

View File

@ -2187,7 +2187,7 @@ a.text-dark:hover, a.text-dark:focus {
border: 0;
font-weight: 900;
content: '\f105';
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 5 Free', sans-serif;
}
.sidebar .nav-item.dropdown.show .dropdown-toggle::after,
@ -2471,7 +2471,7 @@ a.text-dark:hover, a.text-dark:focus {
.sidebar #sidebarToggle::after {
font-weight: 900;
content: '\f104';
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 5 Free', sans-serif;
margin-right: 0.1rem;
}
@ -2490,7 +2490,7 @@ a.text-dark:hover, a.text-dark:focus {
.sidebar.toggled #sidebarToggle::after {
content: '\f105';
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 5 Free', sans-serif;
margin-left: 0.25rem;
}
@ -2611,7 +2611,7 @@ a.text-dark:hover, a.text-dark:focus {
border: 0;
font-weight: 900;
content: '\f107';
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 5 Free', sans-serif;
}
.sidebar .nav-item .nav-link[data-bs-toggle="collapse"].collapsed::after {
@ -2892,7 +2892,7 @@ a.text-dark:hover, a.text-dark:focus {
line-height: 51px;
font-weight: 900;
content: '\f107';
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 5 Free', sans-serif;
color: #d1d3e2;
}

View File

@ -146,6 +146,32 @@ const LoginPage = () => {
}
}, [setRedirectTo, setSuccessMessage, t])
const redirectAfterLogin = () => {
if (redirectTo && !redirectTo.startsWith('http') && !redirectTo.startsWith('file') && !redirectTo.startsWith('javascript')) {
// Normalize the URL so that it can't redirect to different domain.
try {
const redirectUrl = new URL(
redirectTo.substring(redirectTo.indexOf('/')) + (window.location.hash ? window.location.hash : ''),
window.location.protocol + '//' + window.location.host
);
if (redirectUrl.pathname.includes("//")) {
// Invalid redirect URL, something fishy might be going on, redirect to /
navigate('/');
} else {
navigate(
redirectUrl.pathname + redirectUrl.search + redirectUrl.hash
);
}
} catch (e) {
console.warn(e);
// Invalid redirect URL, something fishy might be going on, redirect to /
navigate('/');
}
} else {
navigate('/');
}
};
const login = async (username, password) => {
if (!username || username.length < 1) {
return setFailMessage(t('html.register.error.noUsername'));
@ -168,29 +194,7 @@ const LoginPage = () => {
}
} else if (data && data.success) {
await updateLoginDetails();
if (redirectTo && !redirectTo.startsWith('http') && !redirectTo.startsWith('file') && !redirectTo.startsWith('javascript')) {
// Normalize the URL so that it can't redirect to different domain.
try {
const redirectUrl = new URL(
redirectTo.substring(redirectTo.indexOf('/')) + (window.location.hash ? window.location.hash : ''),
window.location.protocol + '//' + window.location.host
);
if (redirectUrl.pathname.includes("//")) {
// Invalid redirect URL, something fishy might be going on, redirect to /
navigate('/');
} else {
navigate(
redirectUrl.pathname + redirectUrl.search + redirectUrl.hash
);
}
} catch (e) {
console.warn(e);
// Invalid redirect URL, something fishy might be going on, redirect to /
navigate('/');
}
} else {
navigate('/');
}
redirectAfterLogin();
} else {
setFailMessage(t('html.login.failed') + data ? data.error : t('generic.noData'));
}

View File

@ -34,7 +34,7 @@ const ConnectionsCard = ({player}) => {
</tr>
</thead>
<tbody>
{player.connections.map((connection, i) => (<tr key={'connection-' + i}>
{player.connections.map((connection, i) => (<tr key={JSON.stringify(connection)}>
<td>{connection.geolocation}</td>
<td>{connection.date}</td>
</tr>))}