Merge master.

This commit is contained in:
Mike Cao 2024-02-29 17:35:54 -08:00
commit 9f90412b25
11 changed files with 827 additions and 21 deletions

1
.gitignore vendored
View File

@ -35,6 +35,7 @@ yarn-error.log*
# local env files # local env files
.env .env
.env.* .env.*
*.env.*
*.dev.yml *.dev.yml

7
cypress.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
});

View File

@ -0,0 +1,51 @@
---
version: '3'
services:
umami:
build: ../
#image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- '3000:3000'
environment:
DATABASE_URL: postgresql://umami:umami@db:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: replace-me-with-a-random-string
depends_on:
db:
condition: service_healthy
restart: always
healthcheck:
test: ['CMD-SHELL', 'curl http://localhost:3000/api/heartbeat']
interval: 5s
timeout: 5s
retries: 5
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: umami
volumes:
- umami-db-data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 5
cypress:
image: 'cypress/included:13.6.0'
depends_on:
- umami
- db
environment:
- CYPRESS_baseUrl=http://umami:3000
- CYPRESS_umami_user=admin
- CYPRESS_umami_password=umami
volumes:
- ../tsconfig.json:/tsconfig.json
- ../cypress.config.ts:/cypress.config.ts
- ./:/cypress
- ../node_modules/:/node_modules
volumes:
umami-db-data:

18
cypress/e2e/login.cy.ts Normal file
View File

@ -0,0 +1,18 @@
describe('Login test', () => {
it(
'logs user in with correct credentials and logs user out',
{
defaultCommandTimeout: 10000,
},
() => {
cy.visit('/login');
cy.dataCy('input-username').type(Cypress.env('umami_user'));
cy.dataCy('input-password').type(Cypress.env('umami_password'));
cy.dataCy('button-submit').click();
cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
cy.dataCy('button-profile').click();
cy.dataCy('item-logout').click();
cy.url().should('eq', Cypress.config().baseUrl + '/login');
},
);
});

5
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="cypress" />
Cypress.Commands.add('dataCy', value => {
return cy.get(`[data-cy=${value}]`);
});

11
cypress/support/index.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable {
/**
* Custom command to select DOM element by data-cy attribute.
* @example cy.dataCy('greeting')
*/
dataCy(value: string): Chainable<JQuery<HTMLElement>>;
}
}

8
cypress/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"]
},
"include": ["**/*.ts", "../cypress.config.ts"]
}

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "2.10.1", "version": "2.11.0",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.", "description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Umami Software, Inc. <hello@umami.is>", "author": "Umami Software, Inc. <hello@umami.is>",
"license": "MIT", "license": "MIT",
@ -42,7 +42,9 @@
"change-password": "node scripts/change-password.js", "change-password": "node scripts/change-password.js",
"lint": "next lint --quiet", "lint": "next lint --quiet",
"prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install", "prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install",
"postbuild": "node scripts/postbuild.js" "postbuild": "node scripts/postbuild.js",
"cypress-open": "cypress open cypress run",
"cypress-run": "cypress run cypress run"
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,jsx,ts,tsx}": [ "**/*.{js,jsx,ts,tsx}": [
@ -133,6 +135,7 @@
"@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3", "@typescript-eslint/parser": "^6.7.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cypress": "^13.6.6",
"esbuild": "^0.17.17", "esbuild": "^0.17.17",
"eslint": "^8.33.0", "eslint": "^8.33.0",
"eslint-config-next": "^14.0.4", "eslint-config-next": "^14.0.4",

View File

@ -42,17 +42,30 @@ export function LoginForm() {
<div className={styles.title}>umami</div> <div className={styles.title}>umami</div>
<Form className={styles.form} onSubmit={handleSubmit} error={getMessage(error)}> <Form className={styles.form} onSubmit={handleSubmit} error={getMessage(error)}>
<FormRow label={formatMessage(labels.username)}> <FormRow label={formatMessage(labels.username)}>
<FormInput name="username" rules={{ required: formatMessage(labels.required) }}> <FormInput
data-cy="input-username"
name="username"
rules={{ required: formatMessage(labels.required) }}
>
<TextField autoComplete="off" /> <TextField autoComplete="off" />
</FormInput> </FormInput>
</FormRow> </FormRow>
<FormRow label={formatMessage(labels.password)}> <FormRow label={formatMessage(labels.password)}>
<FormInput name="password" rules={{ required: formatMessage(labels.required) }}> <FormInput
data-cy="input-password"
name="password"
rules={{ required: formatMessage(labels.required) }}
>
<PasswordField /> <PasswordField />
</FormInput> </FormInput>
</FormRow> </FormRow>
<FormButtons> <FormButtons>
<SubmitButton className={styles.button} variant="primary" disabled={isPending}> <SubmitButton
data-cy="button-submit"
className={styles.button}
variant="primary"
disabled={isPending}
>
{formatMessage(labels.login)} {formatMessage(labels.login)}
</SubmitButton> </SubmitButton>
</FormButtons> </FormButtons>

View File

@ -25,7 +25,7 @@ export function ProfileButton() {
return ( return (
<PopupTrigger> <PopupTrigger>
<Button variant="quiet"> <Button data-cy="button-profile" variant="quiet">
<Icon> <Icon>
<Icons.Profile /> <Icons.Profile />
</Icon> </Icon>
@ -41,7 +41,7 @@ export function ProfileButton() {
<Text>{formatMessage(labels.profile)}</Text> <Text>{formatMessage(labels.profile)}</Text>
</Item> </Item>
{!cloudMode && ( {!cloudMode && (
<Item key="logout" className={styles.item}> <Item data-cy="item-logout" key="logout" className={styles.item}>
<Icon> <Icon>
<Icons.Logout /> <Icons.Logout />
</Icon> </Icon>

717
yarn.lock

File diff suppressed because it is too large Load Diff