diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml new file mode 100644 index 00000000000..3a0fa5f8b34 --- /dev/null +++ b/.github/workflows/studio-tests.yml @@ -0,0 +1,35 @@ +# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Studio Unit Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + paths: + - 'studio/**' +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x, 14.x, 16.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - name: Install deps + run: npm i + working-directory: ./studio + - name: Run tests + run: npm test + working-directory: ./studio diff --git a/studio/components/interfaces/Settings/Logs/LogTable.tsx b/studio/components/interfaces/Settings/Logs/LogTable.tsx index 9709dc6965d..824627828c8 100644 --- a/studio/components/interfaces/Settings/Logs/LogTable.tsx +++ b/studio/components/interfaces/Settings/Logs/LogTable.tsx @@ -5,6 +5,7 @@ import DataGrid from '@supabase/react-data-grid' import LogSelection from './LogSelection' import { LogData } from './Logs.types' +import { isNil } from 'lodash' interface Props { isCustomQuery: boolean @@ -50,12 +51,13 @@ const LogTable = ({ isCustomQuery, data }: Props) => { }, {}) }, [JSON.stringify(data)]) + const strLogMap = JSON.stringify(logMap) useEffect(() => { - if (!data) return + if (isNil(data)) return if (focusedLog && !(focusedLog.id in logMap)) { setFocusedLog(null) } - }, [Object.keys(logMap)]) + }, [strLogMap]) if (!data) return null @@ -64,7 +66,7 @@ const LogTable = ({ isCustomQuery, data }: Props) => { const logDataRows = useMemo(() => { return Object.values(logMap).sort((a, b) => a.timestamp - b.timestamp) - }, [JSON.stringify(Object.keys(logMap))]) + }, [strLogMap]) return (
= ({}) => { : []), , -
- ui.toggleTheme()} - /> +
+ Theme +
, ]} diff --git a/studio/hooks/misc/useStore.tsx b/studio/hooks/misc/useStore.tsx index e0eb5fb3542..4060949ce31 100644 --- a/studio/hooks/misc/useStore.tsx +++ b/studio/hooks/misc/useStore.tsx @@ -1,6 +1,6 @@ import { useMonaco } from '@monaco-editor/react' import { autorun } from 'mobx' -import { createContext, FC, useContext, useEffect } from 'react' +import { createContext, FC, useContext, useEffect, useCallback } from 'react' import toast from 'react-hot-toast' import { IRootStore } from 'stores' @@ -33,8 +33,14 @@ export const StoreProvider: FC = ({ children, rootStore }) => { } }, [theme, monaco]) + const matchMediaEvent = useCallback(() => { + ui.themeOption === 'system' && + ui.setTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') + }, []) + useEffect(() => { ui.load() + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', matchMediaEvent) autorun(() => { if (ui.notification) { @@ -71,6 +77,10 @@ export const StoreProvider: FC = ({ children, rootStore }) => { } } }) + return () => + window + .matchMedia('(prefers-color-scheme: dark)') + .removeEventListener('change', matchMediaEvent) }, []) return {children} diff --git a/studio/pages/account/me.tsx b/studio/pages/account/me.tsx index 02b537af4b7..3a3190a7cc5 100644 --- a/studio/pages/account/me.tsx +++ b/studio/pages/account/me.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React from 'react' import { observer } from 'mobx-react-lite' import { IconMoon, IconSun, Select, Typography } from '@supabase/ui' @@ -77,15 +77,9 @@ const ProfileCard = observer(() => { ) }) -function ThemeSettings() { - const [theme, setTheme] = useState('') +const ThemeSettings = observer(() => { const { ui } = useStore() - useEffect(() => { - const localStorageTheme = window.localStorage.getItem('theme') - if (localStorageTheme) setTheme(localStorageTheme) - }, []) - return ( ) -} +}) diff --git a/studio/pages/project/[ref]/settings/logs/[type].tsx b/studio/pages/project/[ref]/settings/logs/[type].tsx index 4fc35bd1aa7..e9e66a30cdb 100644 --- a/studio/pages/project/[ref]/settings/logs/[type].tsx +++ b/studio/pages/project/[ref]/settings/logs/[type].tsx @@ -131,7 +131,6 @@ export const LogPage: NextPage = () => { setLatestRefresh(new Date().toISOString()) setParams({ ...params, timestamp_start: '' }) setSize(1) - mutate() } const handleModeToggle = () => { diff --git a/studio/stores/UiStore.ts b/studio/stores/UiStore.ts index e2f46376a38..728a0a99171 100644 --- a/studio/stores/UiStore.ts +++ b/studio/stores/UiStore.ts @@ -7,6 +7,7 @@ import Telemetry from 'lib/telemetry' export interface IUiStore { language: 'en_US' theme: 'dark' | 'light' + themeOption: 'dark' | 'light' | 'system' isDarkTheme: boolean selectedProject?: Project @@ -15,8 +16,8 @@ export interface IUiStore { profile?: User load: () => void - toggleTheme: () => void setTheme: (theme: 'dark' | 'light') => void + onThemeOptionChange: (themeOption: 'dark' | 'light' | 'system') => void setProjectRef: (ref?: string) => void setOrganizationSlug: (slug?: string) => void setNotification: (notification: Notification) => string @@ -26,6 +27,7 @@ export default class UiStore implements IUiStore { rootStore: IRootStore language: 'en_US' = 'en_US' theme: 'dark' | 'light' = 'dark' + themeOption: 'dark' | 'light' | 'system' = 'dark' selectedProjectRef?: string selectedOrganizationSlug?: string @@ -73,24 +75,39 @@ export default class UiStore implements IUiStore { load() { if (typeof window === 'undefined') return - const savedTheme = (window.localStorage.getItem('theme') ?? 'dark') as 'dark' | 'light' - this.setTheme(savedTheme) - } - - toggleTheme() { - if (this.theme === 'dark') { - this.setTheme('light') - } else { - this.setTheme('dark') + const localStorageThemeOption = window.localStorage.getItem('theme') + if (localStorageThemeOption === 'system') { + this.themeOption = localStorageThemeOption + return this.setTheme( + window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + ) } + if (localStorageThemeOption === 'light') { + this.themeOption = localStorageThemeOption + return this.setTheme('light') + } + window.localStorage.setItem('theme', 'dark') + this.themeOption = 'dark' + this.setTheme('dark') } setTheme(theme: 'dark' | 'light') { this.theme = theme - window.localStorage.setItem('theme', theme) document.body.className = theme } + onThemeOptionChange(themeOption: 'dark' | 'light' | 'system') { + this.themeOption = themeOption + if (themeOption === 'system') { + window.localStorage.setItem('theme', 'system') + return this.setTheme( + window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + ) + } + window.localStorage.setItem('theme', themeOption) + this.setTheme(themeOption) + } + setProjectRef(ref?: string) { this.selectedProjectRef = ref } diff --git a/studio/tests/pages/projects/logs.test.js b/studio/tests/pages/projects/logs.test.js index a8b8a50e4d7..0468288cedf 100644 --- a/studio/tests/pages/projects/logs.test.js +++ b/studio/tests/pages/projects/logs.test.js @@ -100,7 +100,7 @@ test('Refreshpage', async () => { await waitFor(() => screen.getByText(/my_key/)) // simulate refresh - await waitFor(() => userEvent.click(screen.getByText(/Refresh/))) + userEvent.click(screen.getByText(/Refresh/)) // when log line unmounts and it was focused, should close focus panel await waitFor(() => screen.queryByText(/my_key/) === null, { timeout: 1000 }) await waitFor(() => screen.queryByText(/happened/) === null, { timeout: 1000 }) diff --git a/web/docs/guides/auth/auth-azure.mdx b/web/docs/guides/auth/auth-azure.mdx new file mode 100644 index 00000000000..6b5b84c9ce6 --- /dev/null +++ b/web/docs/guides/auth/auth-azure.mdx @@ -0,0 +1,92 @@ +--- +id: auth-azure +title: 'Login with Azure' +description: Add Azure OAuth to your Supabase project +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +To enable Azure Auth for your project, you need to set up an Azure OAuth application and add the application credentials to your Supabase Dashboard. + +## Overview + +Azure OAuth consists of six broad steps: + +- Create an application under Azure Active Directory. +- Obtain a `Application (client) ID` with “Sign In with Azure” capabilities. This will be used as the `client id`. +- Create a `Secret ID` with “Sign In with Azure” capabilities. The value of the secret will be used as the `client secret`. +- Whitelist the callback url of your application. + +## Steps + +### Access your Azure Developer account + +- Go to [portal.azure.com](https://portal.azure.com/#home). +- Login and select "Azure Active Directory" under the list of Azure Services. + +### Register an application + +- Under Azure Active Directory, select "App registrations" in the side panel. +- Select "New registration". +- Choose a name and select your preferred option for the supported account types. +- Specify the "Redirect URI". +- The redirect / callback URI should look like this: `https://.supabase.co/auth/v1/callback` +- Click "Register" at the bottom of the form. + +![Register an application.](/img/guides/azure-register-app.png) + +### Obtain a Client ID + +This will serve as the `client_id` when you make API calls to authenticate the user. + +- Once your app has been registered, the client id can be found under the [list of app registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) under the column titled "Application (client) ID". + +![Obtain the client id](/img/guides/azure-client-id.png) + +### Obtain a Secret ID + +This will serve as the `client_secret` when you make API calls to authenticate the user. + +- Click on the name of the app registered above. +- Under "Essentials", click on "Client credentials". +- Navigate to the "Client secrets" tab and select "New client secret". +- Enter a description and choose your preferred expiry for the secret. +- Once the secret is generated, save the value (not the secret ID). + +![Obtain the client secret](/img/guides/azure-client-secret.png) + +### Add login code to your client app + +The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers). + +```js +const { user, session, error } = await supabase.auth.signIn({ + provider: 'Azure', +}) +``` + +Add a function which you can call from a button, link, or UI element. + +```js +async function signInWithAzure() { + const { user, session, error } = await supabase.auth.signIn({ + provider: 'Azure', + }, { + scopes: 'email', + }) +} +``` + +To log out: + +```js +async function signout() { + const { error } = await supabase.auth.signOut() +} +``` + +## Resources + +- [Azure Developer Account](https://portal.azure.com). +- [Github Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573). diff --git a/web/sidebars.js b/web/sidebars.js index c62b08083eb..2d7b417faad 100755 --- a/web/sidebars.js +++ b/web/sidebars.js @@ -148,6 +148,7 @@ module.exports = { 'guides/auth/auth-email', 'guides/auth/auth-magic-link', 'guides/auth/auth-apple', + 'guides/auth/auth-azure', 'guides/auth/auth-bitbucket', 'guides/auth/auth-discord', 'guides/auth/auth-facebook', diff --git a/web/src/data/authProviders.js b/web/src/data/authProviders.js index 0dce4ab7758..13770e5a00d 100644 --- a/web/src/data/authProviders.js +++ b/web/src/data/authProviders.js @@ -29,10 +29,10 @@ const authProviders = [ { name: 'Azure', // logo: '/img/libraries/dart-icon.svg', - href: 'https://github.com/supabase/gotrue/pull/54#issuecomment-757043573', + href: '/docs/guides/auth/auth-azure', official: false, supporter: 'TBD', - platform: false, + platform: true, selfHosted: true, }, { diff --git a/web/static/img/guides/azure-client-id.png b/web/static/img/guides/azure-client-id.png new file mode 100644 index 00000000000..f5a10632f4a Binary files /dev/null and b/web/static/img/guides/azure-client-id.png differ diff --git a/web/static/img/guides/azure-client-secret.png b/web/static/img/guides/azure-client-secret.png new file mode 100644 index 00000000000..3057ca3ad2c Binary files /dev/null and b/web/static/img/guides/azure-client-secret.png differ diff --git a/web/static/img/guides/azure-register-app.png b/web/static/img/guides/azure-register-app.png new file mode 100644 index 00000000000..d6550aa70f3 Binary files /dev/null and b/web/static/img/guides/azure-register-app.png differ