Skip to content

Commit

Permalink
Merge pull request #10141 from CitizenLabDotCo/master
Browse files Browse the repository at this point in the history
Release 2025-01-22 (1)
  • Loading branch information
jinjagit authored Jan 22, 2025
2 parents a9a471f + b0e9768 commit 5cddc26
Show file tree
Hide file tree
Showing 34 changed files with 450 additions and 238 deletions.
2 changes: 1 addition & 1 deletion back/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ gem 'awesome_nested_set', '~> 3.6.0'
gem 'axlsx', '3.0.0.pre' # write xlsx files
gem 'rubyXL', '~> 3.4.27' # read xlsx files. Has issues with writing files https://github.com/CitizenLabDotCo/citizenlab/pull/7834
gem 'counter_culture', '~> 3.5'
gem 'groupdate', '~> 4.1'
gem 'groupdate', '~> 6.5'
gem 'icalendar', '~> 2.10'
gem 'interactor'
gem 'interactor-rails'
Expand Down
8 changes: 4 additions & 4 deletions back/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -684,8 +684,8 @@ GEM
graphql (2.4.4)
base64
fiber-storage
groupdate (4.3.0)
activesupport (>= 5)
groupdate (6.5.1)
activesupport (>= 7)
grpc (1.63.0-aarch64-linux)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
Expand Down Expand Up @@ -721,7 +721,7 @@ GEM
httpclient (2.8.3)
httpi (3.0.1)
rack
i18n (1.14.6)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
icalendar (2.10.1)
ice_cube (~> 0.16)
Expand Down Expand Up @@ -1283,7 +1283,7 @@ DEPENDENCIES
frontend!
google-cloud-document_ai (~> 1.4)
google_tag_manager!
groupdate (~> 4.1)
groupdate (~> 6.5)
icalendar (~> 2.10)
ice_cube (~> 0.16)
id_auth0!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ def calculate_lock_name(site_id)
def matomo_site_id
app_config = AppConfiguration.instance
site_id = app_config.settings.dig('matomo', 'tenant_site_id')
default_site_id = ENV.fetch('DEFAULT_MATOMO_TENANT_SITE_ID')
lifecycle = app_config.settings.dig('core', 'lifecycle_stage')

raise MatomoMisconfigurationError, <<~MSG if site_id.blank? || site_id == ENV['DEFAULT_MATOMO_TENANT_SITE_ID']
Matomo site (= #{site_id.inspect}) for tenant '#{app_config.id}' is misconfigured.
MSG
if (site_id.blank? || site_id == default_site_id) && %w[active trial].include?(lifecycle)
raise MatomoMisconfigurationError, <<~MSG
Matomo site (= #{site_id.inspect}) for tenant '#{app_config.id}' is misconfigured.
MSG
end

site_id
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,44 @@
end
end

it 'raises an error if the configured matomo site is the default one' do
stub_const('ENV', ENV.to_h.merge(
'DEFAULT_MATOMO_TENANT_SITE_ID' => AppConfiguration.instance.settings('matomo', 'tenant_site_id')
))
describe 'MatomoMisconfigurationError' do
before do
stub_const('ENV', ENV.to_h.merge(
'DEFAULT_MATOMO_TENANT_SITE_ID' => AppConfiguration.instance.settings('matomo', 'tenant_site_id')
))
end

context 'when tenant has active lifecycle' do
it 'raises an error if the configured matomo site is the default one' do
expect { described_class.perform_now(Tenant.current.id) }
.to raise_error(described_class::MatomoMisconfigurationError)
end
end

expect { described_class.perform_now(Tenant.current.id) }
.to raise_error(described_class::MatomoMisconfigurationError)
context 'when tenant has trial lifecycle' do
before do
AppConfiguration.instance.settings['core']['lifecycle_stage'] = 'trial'
AppConfiguration.instance.save!
end

it 'raises an error if the configured matomo site is the default one' do
expect { described_class.perform_now(Tenant.current.id) }
.to raise_error(described_class::MatomoMisconfigurationError)
end
end

context 'when tenant has demo lifecycle' do
before do
AppConfiguration.instance.settings['core']['lifecycle_stage'] = 'demo'
AppConfiguration.instance.save!(validate: false)
end

it 'does not raise an error if the configured matomo site is the default one' do
# We expect this error to be raised because the Matomo client is not configured.
# We shouldn't see the MatomoMisconfigurationError, which would be raised before this one.
expect { described_class.perform_now(Tenant.current.id) }
.to raise_error(Matomo::Client::MissingBaseUriError)
end
end
end
end
13 changes: 8 additions & 5 deletions front/app/component-library/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { MouseEvent, ButtonHTMLAttributes } from 'react';
import React, { MouseEvent, ButtonHTMLAttributes, forwardRef } from 'react';

import { isNil, get } from 'lodash-es';
import { darken, transparentize, opacify, rgba } from 'polished';
Expand Down Expand Up @@ -514,7 +514,6 @@ export interface Props extends ButtonContainerProps {
hiddenText?: string | JSX.Element;
icon?: IconProps['name'];
iconPos?: 'left' | 'right';
setSubmitButtonRef?: (value: any) => void;
text?: string | JSX.Element;
theme?: MainThemeProps | undefined;
type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
Expand All @@ -526,11 +525,13 @@ export interface Props extends ButtonContainerProps {
ariaExpanded?: boolean;
ariaPressed?: boolean;
ariaDescribedby?: string;
ariaControls?: string;
as?: React.ElementType;
tabIndex?: number;
}
export type Ref = HTMLButtonElement;

const Button = (props: Props) => {
const Button = forwardRef<Ref, Props>((props, ref) => {
const handleOnClick = (event: MouseEvent<HTMLDivElement>) => {
const { onClick, processing, disabled } = props;

Expand Down Expand Up @@ -594,6 +595,7 @@ const Button = (props: Props) => {
ariaExpanded,
ariaPressed,
ariaDescribedby,
ariaControls,
opacityDisabled,
className,
onClick: _onClick,
Expand Down Expand Up @@ -698,20 +700,21 @@ const Button = (props: Props) => {
aria-expanded={ariaExpanded}
aria-pressed={ariaPressed}
aria-describedby={ariaDescribedby}
aria-controls={ariaControls}
aria-disabled={disabled || processing}
ref={props.setSubmitButtonRef}
className={buttonClassNames}
form={form}
type={buttonType}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
as={as}
tabIndex={tabIndex}
ref={ref}
>
{childContent}
</StyledButton>
</Container>
);
};
});

export default Button;
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const IconTooltip: FC<Props> = memo<Props>(
placement={placement || 'right-end'}
theme={theme || ''}
maxWidth={maxTooltipWidth || 350}
useWrapper={false}
useContentWrapper={false}
content={
<ContentWrapper id={`tooltip-content-${uuid}`} tippytheme={theme}>
{content}
Expand Down
19 changes: 19 additions & 0 deletions front/app/component-library/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface InputProps {
onBlur?: (arg: FormEvent<HTMLInputElement>) => void;
setRef?: (arg: HTMLInputElement) => void | undefined;
onKeyDown?: (event: KeyboardEvent) => void;
onMultilinePaste?: (lines: string[]) => void;
autoFocus?: boolean;
min?: string;
max?: string;
Expand Down Expand Up @@ -154,6 +155,8 @@ class Input extends PureComponent<InputProps> {
autocomplete,
size = 'medium',
'data-testid': dataTestId,
onChange,
onMultilinePaste,
} = this.props;
const hasError = !isNil(this.props.error) && !isEmpty(this.props.error);
const optionalProps = isBoolean(spellCheck) ? { spellCheck } : null;
Expand Down Expand Up @@ -204,6 +207,22 @@ class Input extends PureComponent<InputProps> {
required={required}
autoComplete={autocomplete}
onKeyDown={onKeyDown}
onPaste={
onMultilinePaste
? (e) => {
e.preventDefault();
navigator.clipboard.readText().then((text) => {
const split = text.split('\n');

if (split.length === 1) {
onChange?.(split[0], this.props.locale);
} else {
onMultilinePaste(split);
}
});
}
: undefined
}
{...optionalProps}
/>

Expand Down
110 changes: 37 additions & 73 deletions front/app/component-library/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type TooltipProps = Omit<
'interactive' | 'plugins' | 'role'
> & {
width?: string;
useWrapper?: boolean;
useContentWrapper?: boolean;
};

const useActiveElement = () => {
Expand Down Expand Up @@ -64,63 +64,12 @@ const PLUGINS = [
},
];

const TippyComponent = ({
children,
theme,
width,
componentKey,
isFocused,
setIsFocused,
setKey,
tooltipId,
onHidden,
...rest
}: {
children: React.ReactNode;
theme: string;
width: string | undefined;
componentKey: number;
isFocused: boolean | undefined;
setIsFocused: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setKey: React.Dispatch<React.SetStateAction<number>>;
tooltipId: React.MutableRefObject<string>;
} & TooltipProps) => {
// This component sometimes crashes because of re-renders.
// This useCallback slightly improves the situation (i.e. it makes it
// slightly less likely for the component to crash).
// But in the end we just need to completely rewrite this whole component
// to fix the issue properly.
// https://www.notion.so/govocal/Fix-Tooltip-component-16f9663b7b2680a48aebdf2ace15d1f8
const handleOnHidden = useCallback(() => {
setIsFocused(undefined);
setKey((prev) => prev + 1);
}, [setIsFocused, setKey]);

return (
<Tippy
key={componentKey}
plugins={PLUGINS}
interactive={true}
role="tooltip"
visible={isFocused}
// Ensures tippy works with both keyboard and mouse
onHidden={onHidden ?? handleOnHidden}
theme={theme}
{...rest}
>
<Box as="span" id={tooltipId.current} w={width || 'fit-content'}>
{children}
</Box>
</Tippy>
);
};

const Tooltip = ({
children,
theme = 'light',
width,
// This prop is used to determine if the native Tippy component should be wrapped in a Box component
useWrapper = true,
// This prop is used to determine if the native Tippy component content should be wrapped in a Box component
useContentWrapper = true,
...rest
}: TooltipProps) => {
const tooltipId = useRef(
Expand All @@ -145,37 +94,52 @@ const Tooltip = ({
}
}, [activeElement, isFocused]);

if (useWrapper) {
// This component sometimes crashes because of re-renders.
// This useCallback slightly improves the situation (i.e. it makes it
// slightly less likely for the component to crash).
// But in the end we just need to completely rewrite this whole component
// to fix the issue properly.
// https://www.notion.so/govocal/Fix-Tooltip-component-16f9663b7b2680a48aebdf2ace15d1f8
const handleOnHidden = useCallback(() => {
setIsFocused(undefined);
setKey((prev) => prev + 1);
}, [setIsFocused, setKey]);

if (useContentWrapper) {
return (
<TippyComponent
componentKey={key}
<Tippy
key={key}
plugins={PLUGINS}
interactive={true}
role="tooltip"
visible={isFocused}
// Ensures tippy works with both keyboard and mouse
onHidden={handleOnHidden}
theme={theme}
width={width}
isFocused={isFocused}
setIsFocused={setIsFocused}
setKey={setKey}
tooltipId={tooltipId}
{...rest}
>
{children}
</TippyComponent>
<Box as="span" id={tooltipId.current} w={width || 'fit-content'}>
{children}
</Box>
</Tippy>
);
} else {
return (
// This option is used for more accessible tooltips when useWrapper is false
// This Box is used for more accessible tooltips when useContentWrapper is false
<Box as="span" id={tooltipId.current} w={width || 'fit-content'}>
<TippyComponent
componentKey={key}
<Tippy
key={key}
plugins={PLUGINS}
interactive={true}
role="tooltip"
visible={isFocused}
// Ensures tippy works with both keyboard and mouse
onHidden={handleOnHidden}
theme={theme}
width={width}
isFocused={isFocused}
setIsFocused={setIsFocused}
setKey={setKey}
tooltipId={tooltipId}
{...rest}
>
{children}
</TippyComponent>
</Tippy>
</Box>
);
}
Expand Down
4 changes: 2 additions & 2 deletions front/app/component-library/hooks/useInstanceId.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState } from 'react';

import { uuid4 } from '@sentry/utils';
import { v4 as uuidv4 } from 'uuid';

const useInstanceId = () => {
return useState(() => uuid4())[0];
return useState(() => uuidv4())[0];
};

export default useInstanceId;
4 changes: 2 additions & 2 deletions front/app/components/EsriMap/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import MapView from '@arcgis/core/views/MapView';
import WebMap from '@arcgis/core/WebMap';
import Popup from '@arcgis/core/widgets/Popup';
import { colors } from '@citizenlab/cl2-component-library';
import { uuid4 } from '@sentry/utils';
import { transparentize } from 'polished';
import { v4 as uuidv4 } from 'uuid';

import { IMapConfig } from 'api/map_config/types';
import { IMapLayerAttributes } from 'api/map_layers/types';
Expand Down Expand Up @@ -574,7 +574,7 @@ export const createEsriGeoJsonLayers = (

// create new geojson layer using the created url
const geoJsonLayer = new GeoJSONLayer({
id: `${uuid4()}`,
id: `${uuidv4()}`,
url,
customParameters: {
layerId: layer.id,
Expand Down
Loading

0 comments on commit 5cddc26

Please sign in to comment.