Co-authored-by: Camille Moussu <cmoussu@linagora.com> Co-authored-by: Benoit TELLIER <btellier@linagora.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
addSharedCalendar,
|
addSharedCalendar,
|
||||||
getCalendar,
|
getCalendar,
|
||||||
getCalendars,
|
getCalendars,
|
||||||
|
getSecretLink,
|
||||||
postCalendar,
|
postCalendar,
|
||||||
proppatchCalendar,
|
proppatchCalendar,
|
||||||
removeCalendar,
|
removeCalendar,
|
||||||
@@ -114,6 +115,40 @@ describe("Calendar API", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("get secret link ", async () => {
|
||||||
|
const calLink = "/calendars/calId.json";
|
||||||
|
(api.get as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue("link"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const noreset = await getSecretLink(calLink, false);
|
||||||
|
|
||||||
|
expect(api.get).toHaveBeenCalledWith(
|
||||||
|
`calendar/api${calLink}/secret-link?shouldResetLink=false`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("get secret link ", async () => {
|
||||||
|
const calLink = "/calendars/calId.json";
|
||||||
|
(api.get as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue("link"),
|
||||||
|
});
|
||||||
|
const reset = await getSecretLink(calLink, true);
|
||||||
|
|
||||||
|
expect(api.get).toHaveBeenCalledWith(
|
||||||
|
`calendar/api${calLink}/secret-link?shouldResetLink=true`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("When adding a sharedCal with #default as a name a new name is sent to the back", async () => {
|
it("When adding a sharedCal with #default as a name a new name is sent to the back", async () => {
|
||||||
const mockApiPost = jest.spyOn(api, "post");
|
const mockApiPost = jest.spyOn(api, "post");
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import CalendarPopover from "../../../src/components/Calendar/CalendarModal";
|
|||||||
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
||||||
import { Calendars } from "../../../src/features/Calendars/CalendarTypes";
|
import { Calendars } from "../../../src/features/Calendars/CalendarTypes";
|
||||||
|
import { getSecretLink } from "../../../src/features/Calendars/CalendarApi";
|
||||||
|
|
||||||
|
jest.mock("../../../src/features/Calendars/CalendarApi", () => ({
|
||||||
|
getSecretLink: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe("CalendarPopover", () => {
|
describe("CalendarPopover", () => {
|
||||||
const mockOnClose = jest.fn();
|
const mockOnClose = jest.fn();
|
||||||
@@ -308,6 +313,9 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
|||||||
Object.assign(navigator, {
|
Object.assign(navigator, {
|
||||||
clipboard: { writeText: jest.fn() },
|
clipboard: { writeText: jest.fn() },
|
||||||
});
|
});
|
||||||
|
(getSecretLink as jest.Mock).mockResolvedValue({
|
||||||
|
secretLink: "https://example.org/secret/initial",
|
||||||
|
});
|
||||||
|
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
<CalendarPopover
|
<CalendarPopover
|
||||||
@@ -326,7 +334,7 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
|||||||
expect(input).toHaveValue("https://cal.example.org/calendars/user1/cal1");
|
expect(input).toHaveValue("https://cal.example.org/calendars/user1/cal1");
|
||||||
|
|
||||||
// Click copy button (find button containing ContentCopyIcon)
|
// Click copy button (find button containing ContentCopyIcon)
|
||||||
const copyIcon = screen.getByTestId("ContentCopyIcon");
|
const copyIcon = screen.getAllByTestId("ContentCopyIcon")[0];
|
||||||
const copyButton = copyIcon.closest("button");
|
const copyButton = copyIcon.closest("button");
|
||||||
if (copyButton) {
|
if (copyButton) {
|
||||||
fireEvent.click(copyButton);
|
fireEvent.click(copyButton);
|
||||||
@@ -433,4 +441,46 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
|||||||
expect(importButton).toBeDisabled();
|
expect(importButton).toBeDisabled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fetches and resets the secret link", async () => {
|
||||||
|
(window as any).CALENDAR_BASE_URL = "https://cal.example.org";
|
||||||
|
|
||||||
|
(getSecretLink as jest.Mock)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
secretLink: "https://example.org/secret/initial",
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
secretLink: "https://example.org/secret/new",
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<CalendarPopover
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calendar={existingCalendar}
|
||||||
|
/>,
|
||||||
|
{ user: baseUser }
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: /Access/i }));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(
|
||||||
|
screen.getByDisplayValue("https://example.org/secret/initial")
|
||||||
|
).toBeInTheDocument()
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /reset/i }));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(
|
||||||
|
screen.getByDisplayValue("https://example.org/secret/new")
|
||||||
|
).toBeInTheDocument()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getSecretLink).toHaveBeenCalledWith(
|
||||||
|
existingCalendar.link.replace(".json", ""),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,39 +1,102 @@
|
|||||||
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||||
import { Box, IconButton, TextField } from "@mui/material";
|
import {
|
||||||
import { useState } from "react";
|
Box,
|
||||||
|
IconButton,
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
InputAdornment,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { getSecretLink } from "../../features/Calendars/CalendarApi";
|
||||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||||
|
import { FieldWithLabel } from "../Event/components/FieldWithLabel";
|
||||||
import { SnackbarAlert } from "../Loading/SnackBarAlert";
|
import { SnackbarAlert } from "../Loading/SnackBarAlert";
|
||||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||||
|
|
||||||
export function AccessTab({ calendar }: { calendar: Calendars }) {
|
export function AccessTab({ calendar }: { calendar: Calendars }) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const calDAVLink = `${(window as any).CALENDAR_BASE_URL}${calendar.link.replace(".json", "")}`;
|
const calDAVLink = `${(window as any).CALENDAR_BASE_URL}${calendar.link.replace(".json", "")}`;
|
||||||
|
|
||||||
|
const [secretLink, setSecretLink] = useState("");
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleCopy = () => {
|
useEffect(() => {
|
||||||
navigator.clipboard.writeText(calDAVLink);
|
async function fetchSecret() {
|
||||||
|
const existing = await getSecretLink(
|
||||||
|
calendar.link.replace(".json", ""),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
setSecretLink(existing.secretLink);
|
||||||
|
}
|
||||||
|
fetchSecret();
|
||||||
|
}, [calendar.link]);
|
||||||
|
|
||||||
|
const handleCopy = (content: string) => {
|
||||||
|
navigator.clipboard.writeText(content);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetSecretLink = async () => {
|
||||||
|
const newSecret = await getSecretLink(
|
||||||
|
calendar.link.replace(".json", ""),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
setSecretLink(newSecret.secretLink);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box mt={2}>
|
<FieldWithLabel label={t("calendar.caldav_access")} isExpanded={false}>
|
||||||
<TextField
|
<Box mt={2}>
|
||||||
disabled
|
<TextField
|
||||||
fullWidth
|
disabled
|
||||||
label={t("calendar.caldav_access")}
|
fullWidth
|
||||||
value={calDAVLink}
|
label={t("calendar.caldav_access")}
|
||||||
slotProps={{
|
value={calDAVLink}
|
||||||
input: {
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<IconButton onClick={handleCopy} edge="end">
|
<InputAdornment position="end">
|
||||||
<ContentCopyIcon />
|
<IconButton onClick={() => handleCopy(calDAVLink)} edge="end">
|
||||||
</IconButton>
|
<ContentCopyIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
},
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
</FieldWithLabel>
|
||||||
|
|
||||||
|
<FieldWithLabel label={t("calendar.secretUrl")} isExpanded={false}>
|
||||||
|
<Box mt={3} display="flex" alignItems="center" gap={1}>
|
||||||
|
<TextField
|
||||||
|
disabled
|
||||||
|
fullWidth
|
||||||
|
label={t("calendar.secretUrl")}
|
||||||
|
value={secretLink}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={() => handleCopy(secretLink)} edge="end">
|
||||||
|
<ContentCopyIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button variant="outlined" onClick={handleResetSecretLink}>
|
||||||
|
{t("actions.reset")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</FieldWithLabel>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ color: "text.secondary", mt: 1, lineHeight: 1.5 }}
|
||||||
|
>
|
||||||
|
{t("calendar.secretUrlDesc")}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<SnackbarAlert
|
<SnackbarAlert
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
open={open}
|
open={open}
|
||||||
|
|||||||
@@ -128,3 +128,14 @@ export async function updateAclCalendar(calLink: string, request: string) {
|
|||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSecretLink(calLink: string, reset: boolean) {
|
||||||
|
const response = await api
|
||||||
|
.get(`calendar/api${calLink}/secret-link?shouldResetLink=${reset}`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
return response as { secretLink: string };
|
||||||
|
}
|
||||||
|
|||||||
+4
-1
@@ -5,6 +5,8 @@
|
|||||||
"delegated": "Delegated Calendars",
|
"delegated": "Delegated Calendars",
|
||||||
"other": "Other Calendars",
|
"other": "Other Calendars",
|
||||||
"caldav_access": "CalDAV access",
|
"caldav_access": "CalDAV access",
|
||||||
|
"secretUrl": "Secret URL",
|
||||||
|
"secretUrlDesc": "The secret address can be used to view all calendar events even without access permissions. You can reset your current secret address and get a new one.",
|
||||||
"import_file_description": "Import events from an ICS file to one of your calendars.",
|
"import_file_description": "Import events from an ICS file to one of your calendars.",
|
||||||
"ics_feed_url": "ICS feed URL",
|
"ics_feed_url": "ICS feed URL",
|
||||||
"import_to": "Import to",
|
"import_to": "Import to",
|
||||||
@@ -28,7 +30,8 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
"add": "Add"
|
"add": "Add",
|
||||||
|
"reset": "Reset"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|||||||
+4
-1
@@ -5,6 +5,8 @@
|
|||||||
"delegated": "Calendriers délégués",
|
"delegated": "Calendriers délégués",
|
||||||
"other": "Autres calendriers",
|
"other": "Autres calendriers",
|
||||||
"caldav_access": "Accès CalDAV",
|
"caldav_access": "Accès CalDAV",
|
||||||
|
"secretUrl": "URL secrète",
|
||||||
|
"secretUrlDesc": "L'adresse secrète peut être utilisée pour visualiser tous vos calendiers même sans permissions. Vous pouvez la réinitialiser pour en obtenir une nouvelle.",
|
||||||
"import_file_description": "Importer des événements depuis un fichier ICS vers l'un de vos calendriers.",
|
"import_file_description": "Importer des événements depuis un fichier ICS vers l'un de vos calendriers.",
|
||||||
"ics_feed_url": "URL du flux ICS",
|
"ics_feed_url": "URL du flux ICS",
|
||||||
"import_to": "Importer vers",
|
"import_to": "Importer vers",
|
||||||
@@ -28,7 +30,8 @@
|
|||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
"import": "Importer",
|
"import": "Importer",
|
||||||
"add": "Ajouter"
|
"add": "Ajouter",
|
||||||
|
"reset": "Réinitialiser"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
|
|||||||
Reference in New Issue
Block a user