mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 17:47:22 +00:00
chore: update telemetry link and add privacy link to feedback form (#15864)
* chore: update telemetry link and add privacy link to feedback form Bending the rules a little with 1 PR to resolve 2 related issues as they touch 2 of the same files. We have had feedback from RH legal on two things: - #15862: The telemetry infoLink is not a _privacy_ statement and shouldn't be called one. The proposed change is from 'Read our privacy statement' to 'For more information read our statement'. - #15861: The feedback form offers to collect your email address, so it should link to a separate url that is a privacy statement. This change: - Updates the infoLink to the proposed text. - Adds a privacyLink and privacyURL to product.json. - Adds the new privacy link to the feedback form (identical to telemetry link in WelcomePage). Used onMount instead of $derived in FeedbackForm because I do not want to trigger Svelte 5 migration as part of this issue. Fixes #15862. Fixes #15861. Signed-off-by: Tim deBoer <git@tdeboer.ca> * chore: change telemetry info and privacy to objects We had infoLink/infoURL and privacyLink/privacyURL. This changes them both to be simple objects with link/url properties. There is a bunch of change, but overall it is slightly simpler and better structured since info and privacy are still optional, but once you provide them the properties aren't. Signed-off-by: Tim deBoer <git@tdeboer.ca> --------- Signed-off-by: Tim deBoer <git@tdeboer.ca>
This commit is contained in:
parent
7dd6e28464
commit
9cdef69faa
9 changed files with 116 additions and 32 deletions
|
|
@ -18,6 +18,12 @@
|
|||
|
||||
export interface TelemetryMessages {
|
||||
acceptMessage: string;
|
||||
infoLink?: string;
|
||||
infoURL?: string;
|
||||
info?: {
|
||||
link: string;
|
||||
url: string;
|
||||
};
|
||||
privacy?: {
|
||||
link: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -399,8 +399,10 @@ describe('aggregateTrack', () => {
|
|||
expect(messages.acceptMessage).toBe(
|
||||
'Help improve Podman Desktop by allowing Red Hat to collect anonymous usage data.',
|
||||
);
|
||||
expect(messages.infoLink).toBe('Read our privacy statement');
|
||||
expect(messages.infoURL).toBe('https://developers.redhat.com/article/tool-data-collection');
|
||||
expect(messages.info?.link).toBe('For more information read our statement');
|
||||
expect(messages.info?.url).toBe('https://developers.redhat.com/article/tool-data-collection');
|
||||
expect(messages.privacy?.link).toBe('Read our privacy statement');
|
||||
expect(messages.privacy?.url).toBe('https://www.redhat.com/en/about/privacy-policy');
|
||||
});
|
||||
|
||||
test('should register telemetry preference correctly', async () => {
|
||||
|
|
@ -411,7 +413,7 @@ describe('aggregateTrack', () => {
|
|||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
'telemetry.enabled': expect.objectContaining({
|
||||
markdownDescription: `${messages.acceptMessage} [${messages.infoLink}](${messages.infoURL})`,
|
||||
markdownDescription: `${messages.acceptMessage} [${messages.info?.link}](${messages.info?.url})`,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
|
@ -420,13 +422,17 @@ describe('aggregateTrack', () => {
|
|||
|
||||
test('should return custom telemetry message', () => {
|
||||
vi.mocked(product).telemetry.acceptMessage = 'Accept message';
|
||||
vi.mocked(product).telemetry.infoLink = 'Privacy message';
|
||||
vi.mocked(product).telemetry.infoURL = 'privacy-url';
|
||||
vi.mocked(product).telemetry.info.link = 'Info message';
|
||||
vi.mocked(product).telemetry.info.url = 'info-url';
|
||||
vi.mocked(product).telemetry.privacy.link = 'Privacy message';
|
||||
vi.mocked(product).telemetry.privacy.url = 'privacy-url';
|
||||
|
||||
const messages = telemetry.getTelemetryMessages();
|
||||
expect(messages.acceptMessage).toBe('Accept message');
|
||||
expect(messages.infoLink).toBe('Privacy message');
|
||||
expect(messages.infoURL).toBe('privacy-url');
|
||||
expect(messages.info?.link).toBe('Info message');
|
||||
expect(messages.info?.url).toBe('info-url');
|
||||
expect(messages.privacy?.link).toBe('Privacy message');
|
||||
expect(messages.privacy?.url).toBe('privacy-url');
|
||||
});
|
||||
|
||||
test('preference should be formatted correctly when no link is provided', async () => {
|
||||
|
|
|
|||
|
|
@ -110,17 +110,16 @@ export class Telemetry {
|
|||
|
||||
async init(): Promise<void> {
|
||||
const telemetryMessages = this.getTelemetryMessages();
|
||||
const telemetryLink =
|
||||
telemetryMessages.infoLink && telemetryMessages.infoURL
|
||||
? ` [${telemetryMessages.infoLink}](${telemetryMessages.infoURL})`
|
||||
: '';
|
||||
const telemetryInfo = telemetryMessages.info
|
||||
? ` [${telemetryMessages.info.link}](${telemetryMessages.info.url})`
|
||||
: '';
|
||||
const telemetryConfigurationNode: IConfigurationNode = {
|
||||
id: 'preferences.telemetry',
|
||||
title: 'Telemetry',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[TelemetrySettings.SectionName + '.' + TelemetrySettings.Enabled]: {
|
||||
markdownDescription: `${telemetryMessages.acceptMessage}${telemetryLink}`,
|
||||
markdownDescription: `${telemetryMessages.acceptMessage}${telemetryInfo}`,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
|
@ -184,8 +183,18 @@ export class Telemetry {
|
|||
getTelemetryMessages(): TelemetryMessages {
|
||||
return {
|
||||
acceptMessage: product.telemetry.acceptMessage,
|
||||
infoLink: product.telemetry.infoLink,
|
||||
infoURL: product.telemetry.infoURL,
|
||||
info: product.telemetry.info
|
||||
? {
|
||||
link: product.telemetry.info?.link,
|
||||
url: product.telemetry.info?.url,
|
||||
}
|
||||
: undefined,
|
||||
privacy: product.telemetry.privacy
|
||||
? {
|
||||
link: product.telemetry.privacy?.link,
|
||||
url: product.telemetry.privacy?.url,
|
||||
}
|
||||
: undefined,
|
||||
} as TelemetryMessages;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
* Copyright (C) 2024-2026 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -18,8 +18,11 @@
|
|||
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { expect, test } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/svelte';
|
||||
import { tick } from 'svelte';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import type { TelemetryMessages } from '/@api/telemetry';
|
||||
|
||||
import FeedbackForm from './FeedbackForm.svelte';
|
||||
|
||||
|
|
@ -29,3 +32,34 @@ test('something', () => {
|
|||
expect(screen.getByLabelText('validation and buttons')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('validation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Expect privacy statement is missing from the UI when not provided', async () => {
|
||||
render(FeedbackForm);
|
||||
|
||||
await tick();
|
||||
|
||||
const privacyLink = screen.queryByRole('link');
|
||||
expect(privacyLink).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Expect privacy statement is included when it exists', async () => {
|
||||
const telem: TelemetryMessages = {
|
||||
acceptMessage: 'Help improve the product',
|
||||
privacy: {
|
||||
link: 'Click here',
|
||||
url: 'privacy-url',
|
||||
},
|
||||
};
|
||||
vi.mocked(window.getTelemetryMessages).mockResolvedValue(telem);
|
||||
|
||||
render(FeedbackForm);
|
||||
|
||||
await tick();
|
||||
|
||||
const privacyLink = screen.getByRole('link');
|
||||
expect(privacyLink).toBeInTheDocument();
|
||||
expect(privacyLink.textContent).toEqual(telem.privacy?.link);
|
||||
|
||||
await fireEvent.click(privacyLink);
|
||||
await vi.waitFor(() => expect(vi.mocked(window.openExternal)).toBeCalledWith(telem.privacy?.url));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { Link } from '@podman-desktop/ui-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import type { TelemetryMessages } from '/@api/telemetry';
|
||||
|
||||
let telemetryMessages: TelemetryMessages;
|
||||
|
||||
onMount(async () => {
|
||||
telemetryMessages = await window.getTelemetryMessages();
|
||||
});
|
||||
</script>
|
||||
<div>
|
||||
<div class="relative max-h-80 overflow-auto text-[var(--pd-modal-text)] px-10 pb-4" aria-label="content">
|
||||
<slot name="content" />
|
||||
|
||||
{#if telemetryMessages?.privacy}
|
||||
<div class="pt-6">
|
||||
<Link
|
||||
on:click={async (): Promise<void> => {
|
||||
await window.openExternal(telemetryMessages.privacy?.url ?? '');
|
||||
}}>{telemetryMessages?.privacy.link}</Link>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="px-5 py-5 mt-2 flex flex-row w-full space-x-5" aria-label="validation and buttons">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
* Copyright (C) 2024-2026 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -41,6 +41,7 @@ beforeAll(() => {
|
|||
writeText: vi.fn(),
|
||||
},
|
||||
},
|
||||
getTelemetryMessages: vi.fn(),
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,8 +97,10 @@ test('Expect that telemetry messages is visible', async () => {
|
|||
test('Expect that telemetry link opens url', async () => {
|
||||
const telem: TelemetryMessages = {
|
||||
acceptMessage: 'Help improve the product',
|
||||
infoLink: 'Click here',
|
||||
infoURL: 'privacy-url',
|
||||
info: {
|
||||
link: 'Click here',
|
||||
url: 'info-url',
|
||||
},
|
||||
};
|
||||
vi.mocked(window.getTelemetryMessages).mockResolvedValue(telem);
|
||||
|
||||
|
|
@ -106,23 +108,22 @@ test('Expect that telemetry link opens url', async () => {
|
|||
const accept = screen.getByText(telem.acceptMessage);
|
||||
expect(accept).toBeInTheDocument();
|
||||
|
||||
const infoLink = screen.getByText(telem.infoLink ?? '');
|
||||
const infoLink = screen.getByText(telem.info?.link ?? '');
|
||||
expect(infoLink).toBeInTheDocument();
|
||||
|
||||
await fireEvent.click(infoLink);
|
||||
await vi.waitFor(() => expect(vi.mocked(window.openExternal)).toBeCalledWith(telem.infoURL));
|
||||
await vi.waitFor(() => expect(vi.mocked(window.openExternal)).toBeCalledWith(telem.info?.url));
|
||||
});
|
||||
|
||||
test('Expect that telemetry link is missing when url is not provided', async () => {
|
||||
test('Expect that telemetry link is missing when info is not provided', async () => {
|
||||
const telem = {
|
||||
acceptMessage: 'Help improve the product',
|
||||
infoLink: 'Click here',
|
||||
} as TelemetryMessages;
|
||||
vi.mocked(window.getTelemetryMessages).mockResolvedValue(telem);
|
||||
|
||||
await waitRender({ showWelcome: true, showTelemetry: true });
|
||||
|
||||
const infoLink = screen.queryByText(telem.infoLink ?? '');
|
||||
const infoLink = screen.queryByRole('link');
|
||||
expect(infoLink).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -166,11 +166,11 @@ function startOnboardingQueue(): void {
|
|||
<div class="w-2/5 text-[var(--pd-content-card-text)]">
|
||||
{#if telemetryMessages}
|
||||
{telemetryMessages.acceptMessage}
|
||||
{#if telemetryMessages?.infoLink && telemetryMessages?.infoURL}
|
||||
{#if telemetryMessages?.info}
|
||||
<Link
|
||||
on:click={async (): Promise<void> => {
|
||||
await window.openExternal(telemetryMessages.infoURL ?? '');
|
||||
}}>{telemetryMessages?.infoLink}</Link>
|
||||
await window.openExternal(telemetryMessages.info?.url ?? '');
|
||||
}}>{telemetryMessages?.info.link}</Link>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
10
product.json
10
product.json
|
|
@ -16,8 +16,14 @@
|
|||
"telemetry": {
|
||||
"key": "Mhl7GXADk5M1vG6r9FXztbCqWRQY8XPy",
|
||||
"acceptMessage": "Help improve Podman Desktop by allowing Red Hat to collect anonymous usage data.",
|
||||
"infoLink": "Read our privacy statement",
|
||||
"infoURL": "https://developers.redhat.com/article/tool-data-collection"
|
||||
"info": {
|
||||
"link": "For more information read our statement",
|
||||
"url": "https://developers.redhat.com/article/tool-data-collection"
|
||||
},
|
||||
"privacy": {
|
||||
"link": "Read our privacy statement",
|
||||
"url": "https://www.redhat.com/en/about/privacy-policy"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"default": "https://registry.podman-desktop.io/api/extensions.json"
|
||||
|
|
|
|||
Loading…
Reference in a new issue