mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-11 18:11:59 +00:00
* feat(#11596): add support for custom logo config in settings * fix: edit custom logo config * chore: update application config provider * fix: unit test * fate: create separate brand logo component * Add Accessible Logo Config From ConfigResource * update custom logo api * minor change * fix: styling and use brand logo component * fix(#11503) Custom Login Logo does not change after adding new custom logo * restored delete files * doc: update custom logo config doc * update the icon * test: add unit test for brand logo * test: add unit test for custom logo config setting page * test: add unit test for custom logo config form * test: fix cy test * fix: url input validation * test: add cypress test * test: update cy test to check for validation * update the doc * test: fix cypress test * chore: make height and width required in BrandLogo component * chore: update component name --------- Co-authored-by: mohitdeuex <mohit.y@deuexsolutions.com>
This commit is contained in:
parent
c40c5dc478
commit
4df64ef6fe
@ -24,13 +24,16 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.openmetadata.api.configuration.ApplicationConfiguration;
|
||||
import org.openmetadata.api.configuration.LogoConfiguration;
|
||||
import org.openmetadata.catalog.security.client.SamlSSOClientConfig;
|
||||
import org.openmetadata.catalog.type.IdentityProviderConfig;
|
||||
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
|
||||
import org.openmetadata.schema.api.security.AuthorizerConfiguration;
|
||||
import org.openmetadata.schema.settings.SettingsType;
|
||||
import org.openmetadata.service.OpenMetadataApplicationConfig;
|
||||
import org.openmetadata.service.clients.pipeline.PipelineServiceAPIClientConfig;
|
||||
import org.openmetadata.service.resources.Collection;
|
||||
import org.openmetadata.service.resources.settings.SettingsCache;
|
||||
import org.openmetadata.service.security.jwt.JWKSResponse;
|
||||
import org.openmetadata.service.security.jwt.JWTTokenGenerator;
|
||||
|
||||
@ -84,6 +87,24 @@ public class ConfigResource {
|
||||
return authenticationConfiguration;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path(("/customLogoConfiguration"))
|
||||
@Operation(
|
||||
operationId = "getCustomLogoConfiguration",
|
||||
summary = "Get Custom Logo configuration",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "Logo Configuration",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = AuthenticationConfiguration.class)))
|
||||
})
|
||||
public LogoConfiguration getCustomLogoConfig() {
|
||||
return SettingsCache.getInstance().getSetting(SettingsType.CUSTOM_LOGO_CONFIGURATION, LogoConfiguration.class);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path(("/authorizer"))
|
||||
@Operation(
|
||||
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { interceptURL, verifyResponseStatusCode } from '../../common/common';
|
||||
|
||||
const config = {
|
||||
logo: 'https://custom-logo.png',
|
||||
monogram: 'https://custom-monogram.png',
|
||||
logoError: 'Logo URL is not valid url',
|
||||
monogramError: 'Monogram URL is not valid url',
|
||||
};
|
||||
|
||||
describe('Custom Logo Config', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
|
||||
cy.get('[data-testid="appbar-item-settings"]')
|
||||
.should('exist')
|
||||
.and('be.visible')
|
||||
.click();
|
||||
|
||||
interceptURL(
|
||||
'GET',
|
||||
'api/v1/system/settings/customLogoConfiguration',
|
||||
'customLogoConfiguration'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="global-setting-left-panel"]')
|
||||
.contains('Custom Logo')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.and('exist')
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@customLogoConfiguration', 200);
|
||||
});
|
||||
|
||||
it('Should have default config', () => {
|
||||
cy.get('[data-testid="sub-heading"]')
|
||||
.should('be.visible')
|
||||
.contains('Configure The Application Logo and Monogram.');
|
||||
cy.get('[data-testid="logo-url"]').should('be.visible').contains('--');
|
||||
cy.get('[data-testid="monogram-url"]').should('be.visible').contains('--');
|
||||
cy.get('[data-testid="edit-button"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('Should update the config', () => {
|
||||
interceptURL(
|
||||
'GET',
|
||||
'api/v1/system/settings/customLogoConfiguration',
|
||||
'customLogoConfiguration'
|
||||
);
|
||||
cy.get('[data-testid="edit-button"]').should('be.visible').click();
|
||||
verifyResponseStatusCode('@customLogoConfiguration', 200);
|
||||
|
||||
cy.get('[data-testid="customLogoUrlPath"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.clear()
|
||||
.type('incorrect url');
|
||||
|
||||
// validation should work
|
||||
cy.get('[role="alert"]').should('contain', config.logoError);
|
||||
|
||||
cy.get('[data-testid="customLogoUrlPath"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.clear()
|
||||
.type(config.logo);
|
||||
|
||||
cy.get('[data-testid="customMonogramUrlPath"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.clear()
|
||||
.type('incorrect url');
|
||||
|
||||
// validation should work
|
||||
cy.get('[role="alert"]').should('contain', config.monogramError);
|
||||
|
||||
cy.get('[data-testid="customMonogramUrlPath"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.clear()
|
||||
.type(config.monogram);
|
||||
|
||||
interceptURL('PUT', 'api/v1/system/settings', 'updatedConfig');
|
||||
|
||||
interceptURL(
|
||||
'GET',
|
||||
'api/v1/system/settings/customLogoConfiguration',
|
||||
'updatedCustomLogoConfiguration'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="save-button"]').click();
|
||||
|
||||
verifyResponseStatusCode('@updatedConfig', 200);
|
||||
verifyResponseStatusCode('@updatedCustomLogoConfiguration', 200);
|
||||
|
||||
cy.get('[data-testid="logo-url"]')
|
||||
.should('be.visible')
|
||||
.contains(config.logo);
|
||||
cy.get('[data-testid="monogram-url"]')
|
||||
.should('be.visible')
|
||||
.contains(config.monogram);
|
||||
});
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
# Custom Logo Configuration
|
||||
|
||||
To change the Logo for the application, we need to update logo at two locations.
|
||||
|
||||
$$note
|
||||
It might take a few minutes to reflect changes.
|
||||
$$
|
||||
|
||||
Following configuration is needed to allow OpenMetadata to update logo.
|
||||
|
||||
$$section
|
||||
|
||||
### Logo URL $(id="customLogoUrlPath")
|
||||
|
||||
URL path for the login page logo.
|
||||
$$
|
||||
|
||||
$$note
|
||||
Logo aspect ratio should be 5:2 and Recommended size should be 150 x 60 px
|
||||
$$
|
||||
|
||||
$$section
|
||||
|
||||
### Monogram URL $(id="customMonogramUrlPath")
|
||||
|
||||
URL path for the navbar logo.
|
||||
$$
|
||||
|
||||
$$note
|
||||
Monogram aspect ratio should be 1:1 and Recommended size should be 30 x 30 px
|
||||
$$
|
@ -1,8 +1,8 @@
|
||||
# Email Configuration
|
||||
|
||||
Openmetadata is able to send Emails on a various steps like Signup, Forgot Password , Reset Password, Change Event updates.
|
||||
<br/>
|
||||
Following configuration is needed to allow Openmetadata to send Emails.
|
||||
OpenMetadata is able to send Emails on a various steps like SignUp, Forgot Password , Reset Password, Change Event updates.
|
||||
|
||||
Following configuration is needed to allow OpenMetadata to send Emails.
|
||||
|
||||
$$section
|
||||
|
||||
@ -29,7 +29,7 @@ $$section
|
||||
|
||||
### OpenMetadata URL $(id="openMetadataUrl")
|
||||
|
||||
Url of the Openmetadata Server, in case of Docker or K8s this needs to be the external Url used to access the UI.
|
||||
Url of the OpenMetadata Server, in case of Docker or K8s this needs to be the external Url used to access the UI.
|
||||
$$
|
||||
|
||||
$$section
|
||||
@ -45,12 +45,11 @@ $$section
|
||||
|
||||
Port of the SMTP Server, this depends on the transportation strategy below.
|
||||
Following is the mapping between port and the transportation strategy.
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
**SMTP:**- If SMTP port is 25 use this
|
||||
<br/>
|
||||
|
||||
**SMTPS:**- If SMTP port is 465 use this
|
||||
<br/>
|
||||
|
||||
**SMTP_TLS:**- If SMTP port is 587 use this
|
||||
$$
|
||||
|
||||
@ -58,9 +57,9 @@ $$section
|
||||
|
||||
### Emailing entity $(id="emailingEntity")
|
||||
|
||||
This defines the entity that's sending Email. By default, it's `Openmetadata`.
|
||||
<br/>
|
||||
If your company name is `JohnDoe` setting it up will update subject line, content line so that mails have `JohnDoe` inplace of `Openmetadata`.
|
||||
This defines the entity that's sending Email. By default, it's `OpenMetadata`.
|
||||
|
||||
If your company name is `JohnDoe` setting it up will update subject line, content line so that mails have `JohnDoe` inplace of `OpenMetadata`.
|
||||
|
||||
$$
|
||||
|
||||
@ -76,9 +75,9 @@ $$section
|
||||
### Support URL $(id="supportUrl")
|
||||
|
||||
A support Url link is created in the mails to allow the users to reach in case of issues.
|
||||
<br/>
|
||||
|
||||
If you have your internal channels / groups this can be updated here.
|
||||
<br/>
|
||||
|
||||
Default: `https://slack.open-metadata.org`.
|
||||
|
||||
$$
|
||||
@ -87,6 +86,6 @@ $$section
|
||||
|
||||
### Transportation strategy $(id="transportationStrategy")
|
||||
|
||||
Possible values: `SMTP`, `SMTPS`, `SMTP_TLS`. <br/> Depends as per the `port` above.
|
||||
Possible values: `SMTP`, `SMTPS`, `SMTP_TLS`. Depends as per the `port` above.
|
||||
|
||||
$$
|
||||
|
@ -36,8 +36,8 @@ const App: FunctionComponent = () => {
|
||||
<Router>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ErrorBoundry>
|
||||
<AuthProvider childComponentType={AppRouter}>
|
||||
<ApplicationConfigProvider>
|
||||
<ApplicationConfigProvider>
|
||||
<AuthProvider childComponentType={AppRouter}>
|
||||
<HelmetProvider>
|
||||
<WebAnalyticsProvider>
|
||||
<PermissionProvider>
|
||||
@ -50,8 +50,8 @@ const App: FunctionComponent = () => {
|
||||
</PermissionProvider>
|
||||
</WebAnalyticsProvider>
|
||||
</HelmetProvider>
|
||||
</ApplicationConfigProvider>
|
||||
</AuthProvider>
|
||||
</AuthProvider>
|
||||
</ApplicationConfigProvider>
|
||||
</ErrorBoundry>
|
||||
</I18nextProvider>
|
||||
</Router>
|
||||
|
@ -0,0 +1,21 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1032_4367)">
|
||||
<path d="M7.1997 8.06164C7.04228 8.19582 6.93792 8.37732 6.90472 8.5747C6.87151 8.77208 6.91155 8.97288 7.0179 9.14229C7.12424 9.3117 7.29018 9.43905 7.48696 9.50226C7.68374 9.56546 7.89894 9.56053 8.09526 9.48834C8.29158 9.41614 8.45664 9.28121 8.56183 9.10696C8.66702 8.9327 8.7057 8.7301 8.67118 8.53428C8.63665 8.33845 8.56434 8.17149 8.40605 8.04455" stroke="currentColor" stroke-width="0.7"/>
|
||||
<path d="M13.9062 6.89062C13.8333 5.46875 12.725 2.4625 8.875 1.8125" stroke="currentColor" stroke-width="0.7"/>
|
||||
<rect x="13.25" y="6.6875" width="1.75" height="1.625" fill="currentColor" stroke="currentColor" stroke-width="0.7"/>
|
||||
<rect x="1" y="6.6875" width="1.75" height="1.625" stroke="currentColor" stroke-width="0.7"/>
|
||||
<path d="M2.75 1.8125H13.0312" stroke="currentColor" stroke-width="0.7"/>
|
||||
<ellipse cx="2.75" cy="1.8125" rx="0.875" ry="0.8125" fill="currentColor" stroke="currentColor" stroke-width="0.7"/>
|
||||
<ellipse cx="13.6875" cy="1.8125" rx="0.875" ry="0.8125" fill="currentColor" stroke="currentColor" stroke-width="0.7"/>
|
||||
<path d="M5.375 15.625V13.5938H10.1875V15.625" stroke="currentColor" stroke-width="0.7" stroke-linejoin="round"/>
|
||||
<rect x="4.5875" y="12.025" width="6.3875" height="1.91875" fill="currentColor" stroke="currentColor" stroke-width="0.7" stroke-linejoin="round"/>
|
||||
<path d="M1.875 6.89062C1.94792 5.40104 3.05625 2.3 6.90625 1.8125" stroke="currentColor" stroke-width="0.7" stroke-linejoin="round"/>
|
||||
<rect x="7.125" y="1" width="1.75" height="1.625" fill="currentColor" stroke="currentColor" stroke-width="0.7"/>
|
||||
<path d="M7.34375 8.10938V3.84375L4.5 9.9375L5.8125 12.1719H9.75L11.0625 9.9375L8.4375 3.84375V8.10938" stroke="currentColor" stroke-width="0.7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1032_4367">
|
||||
<rect width="16" height="16" fill="currentColor"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -12,26 +12,18 @@
|
||||
*/
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getApplicationConfig } from 'rest/miscAPI';
|
||||
import { getCustomLogoConfig } from 'rest/settingConfigAPI';
|
||||
import ApplicationConfigProvider, {
|
||||
useApplicationConfigProvider,
|
||||
} from './ApplicationConfigProvider';
|
||||
|
||||
const mockApplicationConfig = {
|
||||
logoConfig: {
|
||||
customLogoUrlPath: 'https://customlink.source',
|
||||
|
||||
customMonogramUrlPath: 'https://customlink.source',
|
||||
},
|
||||
loginConfig: {
|
||||
maxLoginFailAttempts: 3,
|
||||
accessBlockTime: 600,
|
||||
jwtTokenExpiryTime: 3600,
|
||||
},
|
||||
customLogoUrlPath: 'https://customlink.source',
|
||||
customMonogramUrlPath: 'https://customlink.source',
|
||||
};
|
||||
|
||||
jest.mock('rest/miscAPI', () => ({
|
||||
getApplicationConfig: jest
|
||||
jest.mock('rest/settingConfigAPI', () => ({
|
||||
getCustomLogoConfig: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockApplicationConfig)),
|
||||
}));
|
||||
@ -55,9 +47,9 @@ describe('ApplicationConfigProvider', () => {
|
||||
|
||||
it('fetch the application config on mount and set in the context', async () => {
|
||||
function TestComponent() {
|
||||
const { logoConfig } = useApplicationConfigProvider();
|
||||
const { customLogoUrlPath } = useApplicationConfigProvider();
|
||||
|
||||
return <div>{logoConfig?.customLogoUrlPath}</div>;
|
||||
return <div>{customLogoUrlPath}</div>;
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
@ -72,6 +64,6 @@ describe('ApplicationConfigProvider', () => {
|
||||
await screen.findByText('https://customlink.source')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(getApplicationConfig).toHaveBeenCalledTimes(1);
|
||||
expect(getCustomLogoConfig).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -10,7 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ApplicationConfiguration } from 'generated/configuration/applicationConfiguration';
|
||||
import { LogoConfiguration } from 'generated/configuration/applicationConfiguration';
|
||||
import React, {
|
||||
createContext,
|
||||
FC,
|
||||
@ -19,10 +19,10 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { getApplicationConfig } from 'rest/miscAPI';
|
||||
import { getCustomLogoConfig } from 'rest/settingConfigAPI';
|
||||
|
||||
export const ApplicationConfigContext = createContext<ApplicationConfiguration>(
|
||||
{} as ApplicationConfiguration
|
||||
export const ApplicationConfigContext = createContext<LogoConfiguration>(
|
||||
{} as LogoConfiguration
|
||||
);
|
||||
|
||||
export const useApplicationConfigProvider = () =>
|
||||
@ -35,14 +35,17 @@ interface ApplicationConfigProviderProps {
|
||||
const ApplicationConfigProvider: FC<ApplicationConfigProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [applicationConfig, setApplicationConfig] =
|
||||
useState<ApplicationConfiguration>({} as ApplicationConfiguration);
|
||||
const [applicationConfig, setApplicationConfig] = useState<LogoConfiguration>(
|
||||
{} as LogoConfiguration
|
||||
);
|
||||
|
||||
const fetchApplicationConfig = async () => {
|
||||
try {
|
||||
const response = await getApplicationConfig();
|
||||
const data = await getCustomLogoConfig();
|
||||
|
||||
setApplicationConfig(response);
|
||||
setApplicationConfig({
|
||||
...data,
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import BrandImage from './BrandImage';
|
||||
|
||||
jest.mock(
|
||||
'components/ApplicationConfigProvider/ApplicationConfigProvider',
|
||||
() => ({
|
||||
useApplicationConfigProvider: jest.fn().mockImplementation(() => ({
|
||||
customLogoUrlPath: 'https://custom-logo.png',
|
||||
customMonogramUrlPath: 'https://custom-monogram.png',
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
describe('Test Brand Logo', () => {
|
||||
it('Should render the brand logo with default props value', () => {
|
||||
render(<BrandImage height="auto" width={152} />);
|
||||
|
||||
const image = screen.getByTestId('brand-logo-image');
|
||||
|
||||
expect(image).toBeInTheDocument();
|
||||
expect(image).toHaveAttribute('alt', 'OpenMetadata Logo');
|
||||
expect(image).toHaveAttribute('height', 'auto');
|
||||
expect(image).toHaveAttribute('width', '152');
|
||||
});
|
||||
|
||||
it('Should render the brand logo with passed props value', () => {
|
||||
render(
|
||||
<BrandImage
|
||||
alt="brand-monogram"
|
||||
className="m-auto"
|
||||
dataTestId="brand-monogram"
|
||||
height={30}
|
||||
width={30}
|
||||
/>
|
||||
);
|
||||
|
||||
const image = screen.getByTestId('brand-monogram');
|
||||
|
||||
expect(image).toBeInTheDocument();
|
||||
expect(image).toHaveAttribute('alt', 'brand-monogram');
|
||||
expect(image).toHaveAttribute('height', '30');
|
||||
expect(image).toHaveAttribute('width', '30');
|
||||
expect(image).toHaveClass('m-auto');
|
||||
});
|
||||
|
||||
it('Should render the brand logo based on custom logo config', () => {
|
||||
render(
|
||||
<BrandImage
|
||||
alt="brand-monogram"
|
||||
className="m-auto"
|
||||
dataTestId="brand-monogram"
|
||||
height="auto"
|
||||
width={152}
|
||||
/>
|
||||
);
|
||||
|
||||
const image = screen.getByTestId('brand-monogram');
|
||||
|
||||
expect(image).toBeInTheDocument();
|
||||
expect(image).toHaveAttribute('alt', 'brand-monogram');
|
||||
expect(image).toHaveAttribute('height', 'auto');
|
||||
expect(image).toHaveAttribute('width', '152');
|
||||
expect(image).toHaveAttribute('src', 'https://custom-logo.png');
|
||||
expect(image).toHaveClass('m-auto');
|
||||
});
|
||||
|
||||
it('Should render the monogram if isMonoGram is true', () => {
|
||||
render(
|
||||
<BrandImage
|
||||
isMonoGram
|
||||
alt="brand-monogram"
|
||||
dataTestId="brand-monogram"
|
||||
height={30}
|
||||
width={30}
|
||||
/>
|
||||
);
|
||||
|
||||
const image = screen.getByTestId('brand-monogram');
|
||||
|
||||
expect(image).toBeInTheDocument();
|
||||
expect(image).toHaveAttribute('alt', 'brand-monogram');
|
||||
expect(image).toHaveAttribute('height', '30');
|
||||
expect(image).toHaveAttribute('width', '30');
|
||||
expect(image).toHaveAttribute('src', 'https://custom-monogram.png');
|
||||
});
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import MonoGram from 'assets/svg/logo-monogram.svg';
|
||||
import Logo from 'assets/svg/logo.svg';
|
||||
import { useApplicationConfigProvider } from 'components/ApplicationConfigProvider/ApplicationConfigProvider';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
interface BrandImageProps {
|
||||
dataTestId?: string;
|
||||
className?: string;
|
||||
alt?: string;
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
isMonoGram?: boolean;
|
||||
}
|
||||
|
||||
const BrandImage: FC<BrandImageProps> = ({
|
||||
dataTestId,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
className,
|
||||
isMonoGram = false,
|
||||
}) => {
|
||||
const { customLogoUrlPath = '', customMonogramUrlPath = '' } =
|
||||
useApplicationConfigProvider();
|
||||
|
||||
const logoSource = isMonoGram
|
||||
? customMonogramUrlPath || MonoGram
|
||||
: customLogoUrlPath || Logo;
|
||||
|
||||
return (
|
||||
<img
|
||||
alt={alt ?? 'OpenMetadata Logo'}
|
||||
className={className}
|
||||
data-testid={dataTestId ?? 'brand-logo-image'}
|
||||
height={height}
|
||||
id="brand-image"
|
||||
src={logoSource}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrandImage;
|
@ -22,7 +22,7 @@ import {
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { ReactComponent as DropDownIcon } from 'assets/svg/DropDown.svg';
|
||||
import { useApplicationConfigProvider } from 'components/ApplicationConfigProvider/ApplicationConfigProvider';
|
||||
import BrandImage from 'components/common/BrandImage/BrandImage';
|
||||
import { useGlobalSearchProvider } from 'components/GlobalSearchProvider/GlobalSearchProvider';
|
||||
import WhatsNewAlert from 'components/Modals/WhatsNewModal/WhatsNewAlert/WhatsNewAlert.component';
|
||||
import { CookieStorage } from 'cookie-storage';
|
||||
@ -95,7 +95,6 @@ const NavBar = ({
|
||||
handleOnClick,
|
||||
handleClear,
|
||||
}: NavBarProps) => {
|
||||
const { logoConfig } = useApplicationConfigProvider();
|
||||
const { searchCriteria, updateSearchCriteria } = useGlobalSearchProvider();
|
||||
|
||||
// get current user details
|
||||
@ -361,22 +360,18 @@ const NavBar = ({
|
||||
[AppState]
|
||||
);
|
||||
|
||||
const brandLogoUrl = useMemo(() => {
|
||||
return logoConfig?.customMonogramUrlPath || Logo;
|
||||
}, [logoConfig]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tw-h-16 tw-py-3 tw-border-b-2 tw-border-separator tw-bg-white">
|
||||
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap tw-px-6">
|
||||
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap">
|
||||
<Link className="tw-flex-shrink-0" id="openmetadata_logo" to="/">
|
||||
<img
|
||||
<BrandImage
|
||||
isMonoGram
|
||||
alt="OpenMetadata Logo"
|
||||
className="vertical-middle"
|
||||
data-testid="image"
|
||||
dataTestId="image"
|
||||
height={30}
|
||||
src={brandLogoUrl}
|
||||
width={30}
|
||||
/>
|
||||
</Link>
|
||||
|
@ -196,6 +196,9 @@ const EditEmailConfigPage = withSuspenseFallback(
|
||||
() => import('pages/EditEmailConfigPage/EditEmailConfigPage.component')
|
||||
)
|
||||
);
|
||||
const EditCustomLogoConfigPage = withSuspenseFallback(
|
||||
React.lazy(() => import('pages/EditCustomLogoConfig/EditCustomLogoConfig'))
|
||||
);
|
||||
|
||||
const AddRulePage = withSuspenseFallback(
|
||||
React.lazy(() => import('pages/PoliciesPage/PoliciesDetailPage/AddRulePage'))
|
||||
@ -548,6 +551,12 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
hasPermission={false}
|
||||
path={ROUTES.SETTINGS_EDIT_EMAIL_CONFIG}
|
||||
/>
|
||||
<AdminProtectedRoute
|
||||
exact
|
||||
component={EditCustomLogoConfigPage}
|
||||
hasPermission={false}
|
||||
path={ROUTES.SETTINGS_EDIT_CUSTOM_LOGO_CONFIG}
|
||||
/>
|
||||
<Route exact component={EditRulePage} path={ROUTES.EDIT_POLICY_RULE} />
|
||||
|
||||
<Route exact component={GlobalSettingPage} path={ROUTES.SETTINGS} />
|
||||
|
@ -116,6 +116,12 @@ const EmailConfigSettingsPage = withSuspenseFallback(
|
||||
import('pages/EmailConfigSettingsPage/EmailConfigSettingsPage.component')
|
||||
)
|
||||
);
|
||||
const CustomLogoConfigSettingsPage = withSuspenseFallback(
|
||||
React.lazy(
|
||||
() =>
|
||||
import('pages/CustomLogoConfigSettingsPage/CustomLogoConfigSettingsPage')
|
||||
)
|
||||
);
|
||||
|
||||
const GlobalSettingRouter = () => {
|
||||
const { permissions } = usePermissionProvider();
|
||||
@ -248,6 +254,15 @@ const GlobalSettingRouter = () => {
|
||||
GlobalSettingOptions.EMAIL
|
||||
)}
|
||||
/>
|
||||
<AdminProtectedRoute
|
||||
exact
|
||||
component={CustomLogoConfigSettingsPage}
|
||||
hasPermission={false}
|
||||
path={getSettingPath(
|
||||
GlobalSettingsMenuCategory.OPEN_METADATA,
|
||||
GlobalSettingOptions.CUSTOM_LOGO
|
||||
)}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
|
@ -53,6 +53,7 @@ export enum GlobalSettingOptions {
|
||||
DATA_INSIGHT_REPORT_ALERT = 'dataInsightReport',
|
||||
ADD_DATA_INSIGHT_REPORT_ALERT = 'add-data-insight-report',
|
||||
EDIT_DATA_INSIGHT_REPORT_ALERT = 'edit-data-insight-report',
|
||||
CUSTOM_LOGO = 'customLogo',
|
||||
}
|
||||
|
||||
export const GLOBAL_SETTING_PERMISSION_RESOURCES = [
|
||||
|
@ -292,6 +292,8 @@ export const ROUTES = {
|
||||
|
||||
CONTAINER_DETAILS: `/container/${PLACEHOLDER_ROUTE_ENTITY_FQN}`,
|
||||
CONTAINER_DETAILS_WITH_TAB: `/container/${PLACEHOLDER_ROUTE_ENTITY_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
|
||||
|
||||
SETTINGS_EDIT_CUSTOM_LOGO_CONFIG: `/settings/OpenMetadata/customLogo/edit-custom-logo-configuration`,
|
||||
};
|
||||
|
||||
export const SOCKET_EVENTS = {
|
||||
|
@ -70,3 +70,6 @@ export const TAGS_DOCS =
|
||||
'https://docs.open-metadata.org/main-concepts/metadata-standard/schemas/api/tags';
|
||||
|
||||
export const AIRFLOW_DOCS = 'https://docs.open-metadata.org/deployment/airflow';
|
||||
|
||||
export const CUSTOM_LOGO_DOCS =
|
||||
'https://docs.open-metadata.org/how-to-guides/how-to-add-custom-logo';
|
||||
|
@ -185,4 +185,5 @@ export const addDBTIngestionGuide = [
|
||||
];
|
||||
|
||||
export const EMAIL_CONFIG_SERVICE_CATEGORY = 'EmailConfiguration';
|
||||
export const CUSTOM_LOGO_CONFIG_SERVICE_CATEGORY = 'CustomLogoConfiguration';
|
||||
export const OPEN_METADATA = 'OpenMetadata';
|
||||
|
@ -162,6 +162,8 @@
|
||||
"criteria": "Criteria",
|
||||
"custom-attribute-plural": "Custom Attributes",
|
||||
"custom-entity": "Custom entity",
|
||||
"custom-logo": "Custom Logo",
|
||||
"custom-logo-configuration": "Custom Logo Configuration",
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "Custom property",
|
||||
"custom-property-plural": "Custom Properties",
|
||||
@ -474,6 +476,7 @@
|
||||
"log-plural": "Logs",
|
||||
"logged-in-user-lowercase": "logged-in user",
|
||||
"login": "Login",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Logout",
|
||||
"major": "Major",
|
||||
"manage-entity": "Manage {{entity}}",
|
||||
@ -521,6 +524,7 @@
|
||||
"model-plural": "Models",
|
||||
"model-store": "Model Store",
|
||||
"monday": "Monday",
|
||||
"monogram-url": "Monogram URL",
|
||||
"month": "Month",
|
||||
"more": "More",
|
||||
"more-help": "More Help",
|
||||
@ -1029,6 +1033,9 @@
|
||||
"create-or-update-email-account-for-bot": "Changing the account email will update or create a new bot user.",
|
||||
"created-this-task-lowercase": "created this task",
|
||||
"custom-classification-name-dbt-tags": "Custom OpenMetadata Classification name for dbt tags ",
|
||||
"custom-logo-configuration-message": "Configure The Application Logo and Monogram.",
|
||||
"custom-logo-url-path-message": "URL path for the login page logo.",
|
||||
"custom-monogram-url-path-message": "URL path for the navbar logo.",
|
||||
"data-asset-has-been-action-type": "Data Asset has been {{actionType}}",
|
||||
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",
|
||||
"data-insight-alert-trigger-description": "Trigger for real time or schedule it for daily, weekly or monthly.",
|
||||
@ -1089,6 +1096,7 @@
|
||||
"entity-does-not-have-followers": "{{entityName}} doesn't have any followers yet",
|
||||
"entity-ingestion-added-successfully": "{{entity}} Ingestion Added Successfully",
|
||||
"entity-is-not-valid": "{{entity}} is not valid",
|
||||
"entity-is-not-valid-url": "{{entity}} is not valid url",
|
||||
"entity-maximum-size": "{{entity}} can be a maximum of {{max}} characters",
|
||||
"entity-not-contain-whitespace": "{{entity}} should not contain white space",
|
||||
"entity-owned-by-name": "This entity is owned by {{entityOwner}}",
|
||||
|
@ -162,6 +162,8 @@
|
||||
"criteria": "Criterio",
|
||||
"custom-attribute-plural": "Atributos personalizados",
|
||||
"custom-entity": "Custom entity",
|
||||
"custom-logo": "Custom Logo",
|
||||
"custom-logo-configuration": "Custom Logo Configuration",
|
||||
"custom-oidc": "OIDC personalizado",
|
||||
"custom-property": "Propiedad personalizada",
|
||||
"custom-property-plural": "Propiedades personalizadas",
|
||||
@ -474,6 +476,7 @@
|
||||
"log-plural": "Registros",
|
||||
"logged-in-user-lowercase": "usuario conectado",
|
||||
"login": "Iniciar sesión",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Cerrar sesión",
|
||||
"major": "Principal",
|
||||
"manage-entity": "Administrar {{entity}}",
|
||||
@ -521,6 +524,7 @@
|
||||
"model-plural": "Models",
|
||||
"model-store": "Almacenamiento de Modelos",
|
||||
"monday": "Lunes",
|
||||
"monogram-url": "Monogram URL",
|
||||
"month": "Mes",
|
||||
"more": "Más",
|
||||
"more-help": "Más Ayuda",
|
||||
@ -1029,6 +1033,9 @@
|
||||
"create-or-update-email-account-for-bot": "Cambiar el correo electrónico de la cuenta actualizará o creará un nuevo bot.",
|
||||
"created-this-task-lowercase": "creó esta tarea",
|
||||
"custom-classification-name-dbt-tags": "Nombre personalizado de clasificación de OpenMetadata para tags de dbt",
|
||||
"custom-logo-configuration-message": "Configure The Application Logo and Monogram.",
|
||||
"custom-logo-url-path-message": "URL path for the login page logo.",
|
||||
"custom-monogram-url-path-message": "URL path for the navbar logo.",
|
||||
"data-asset-has-been-action-type": "El activo de datos ha sido {{actionType}}",
|
||||
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",
|
||||
"data-insight-alert-trigger-description": "Trigger for real time or schedule it for daily, weekly or monthly.",
|
||||
@ -1089,6 +1096,7 @@
|
||||
"entity-does-not-have-followers": "{{entityName}} aún no tiene seguidores",
|
||||
"entity-ingestion-added-successfully": "{{entity}} Ingestión agregada exitosamente",
|
||||
"entity-is-not-valid": "{{entity}} no es válido",
|
||||
"entity-is-not-valid-url": "{{entity}} is not valid url",
|
||||
"entity-maximum-size": "{{entity}} can be a maximum of {{max}} characters",
|
||||
"entity-not-contain-whitespace": "{{entity}} no debe contener espacios en blanco",
|
||||
"entity-owned-by-name": "Esta entidad es propiedad de {{entityOwner}}",
|
||||
|
@ -162,6 +162,8 @@
|
||||
"criteria": "Critères",
|
||||
"custom-attribute-plural": "Propriétés Personalisées",
|
||||
"custom-entity": "Entité Personalisées",
|
||||
"custom-logo": "Custom Logo",
|
||||
"custom-logo-configuration": "Custom Logo Configuration",
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "Propriétés Personalisées",
|
||||
"custom-property-plural": "Propriétés Personalisées",
|
||||
@ -474,6 +476,7 @@
|
||||
"log-plural": "Journal",
|
||||
"logged-in-user-lowercase": "Utilisateur Connecté",
|
||||
"login": "Se Connecter",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Se Déconnecter",
|
||||
"major": "Major",
|
||||
"manage-entity": "Gérer {{entity}}",
|
||||
@ -521,6 +524,7 @@
|
||||
"model-plural": "Modèles",
|
||||
"model-store": "Magasin de Modèles",
|
||||
"monday": "Lundi",
|
||||
"monogram-url": "Monogram URL",
|
||||
"month": "Mois",
|
||||
"more": "Plus",
|
||||
"more-help": "Plus d'Aide",
|
||||
@ -1029,6 +1033,9 @@
|
||||
"create-or-update-email-account-for-bot": "Changer l'email créera un nouveau ou mettra à jour l'agent numérique",
|
||||
"created-this-task-lowercase": "a créé cette tâche",
|
||||
"custom-classification-name-dbt-tags": "Nom personnalisé de la classification OpenMetadata pour les tags dbt ",
|
||||
"custom-logo-configuration-message": "Configure The Application Logo and Monogram.",
|
||||
"custom-logo-url-path-message": "URL path for the login page logo.",
|
||||
"custom-monogram-url-path-message": "URL path for the navbar logo.",
|
||||
"data-asset-has-been-action-type": "l'actif de donnée a été {{actionType}}",
|
||||
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",
|
||||
"data-insight-alert-trigger-description": "Trigger for real time or schedule it for daily, weekly or monthly.",
|
||||
@ -1089,6 +1096,7 @@
|
||||
"entity-does-not-have-followers": "{{entityName}} n'a pas de followers pour l'instant",
|
||||
"entity-ingestion-added-successfully": "{{entity}} Ingestion ajouté avec succès",
|
||||
"entity-is-not-valid": "{{entity}} n'est pas valide",
|
||||
"entity-is-not-valid-url": "{{entity}} is not valid url",
|
||||
"entity-maximum-size": "{{entity}} peut avoir un nombre maximum de characters de {{max}}",
|
||||
"entity-not-contain-whitespace": "{{entity}} ne doit pas contenir d'espace",
|
||||
"entity-owned-by-name": "Cette Resource appartient à {{entityOwner}}",
|
||||
|
@ -162,6 +162,8 @@
|
||||
"criteria": "クライテリア",
|
||||
"custom-attribute-plural": "カスタム属性",
|
||||
"custom-entity": "Custom entity",
|
||||
"custom-logo": "Custom Logo",
|
||||
"custom-logo-configuration": "Custom Logo Configuration",
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "カスタムプロパティ",
|
||||
"custom-property-plural": "カスタムプロパティ",
|
||||
@ -474,6 +476,7 @@
|
||||
"log-plural": "ログ",
|
||||
"logged-in-user-lowercase": "logged-in user",
|
||||
"login": "ログイン",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "ログアウト",
|
||||
"major": "メジャー",
|
||||
"manage-entity": "{{entity}}の管理",
|
||||
@ -521,6 +524,7 @@
|
||||
"model-plural": "Models",
|
||||
"model-store": "モデルストア",
|
||||
"monday": "月曜日",
|
||||
"monogram-url": "Monogram URL",
|
||||
"month": "月",
|
||||
"more": "More",
|
||||
"more-help": "より詳細なヘルプ",
|
||||
@ -1029,6 +1033,9 @@
|
||||
"create-or-update-email-account-for-bot": "Changing the account email will update or create a new bot user.",
|
||||
"created-this-task-lowercase": "このタスクを作成する",
|
||||
"custom-classification-name-dbt-tags": "Custom OpenMetadata Classification name for dbt tags ",
|
||||
"custom-logo-configuration-message": "Configure The Application Logo and Monogram.",
|
||||
"custom-logo-url-path-message": "URL path for the login page logo.",
|
||||
"custom-monogram-url-path-message": "URL path for the navbar logo.",
|
||||
"data-asset-has-been-action-type": "データアセットが{{actionType}}されました",
|
||||
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",
|
||||
"data-insight-alert-trigger-description": "Trigger for real time or schedule it for daily, weekly or monthly.",
|
||||
@ -1089,6 +1096,7 @@
|
||||
"entity-does-not-have-followers": "{{entityName}}はフォロワーがいません",
|
||||
"entity-ingestion-added-successfully": "{{entity}}から抽出した情報は正常に追加されました",
|
||||
"entity-is-not-valid": "{{entity}}は正しくありません",
|
||||
"entity-is-not-valid-url": "{{entity}} is not valid url",
|
||||
"entity-maximum-size": "{{entity}} can be a maximum of {{max}} characters",
|
||||
"entity-not-contain-whitespace": "{{entity}}は空白を含んではいけません",
|
||||
"entity-owned-by-name": "このエンティティは{{entityOwner}}が所有しています",
|
||||
|
@ -162,6 +162,8 @@
|
||||
"criteria": "Critério",
|
||||
"custom-attribute-plural": "Atributos personalizados",
|
||||
"custom-entity": "Custom entity",
|
||||
"custom-logo": "Custom Logo",
|
||||
"custom-logo-configuration": "Custom Logo Configuration",
|
||||
"custom-oidc": "OIDC customizado",
|
||||
"custom-property": "Propriedade customizada",
|
||||
"custom-property-plural": "Propriedades customizadas",
|
||||
@ -474,6 +476,7 @@
|
||||
"log-plural": "Logs",
|
||||
"logged-in-user-lowercase": "conectar com usuário",
|
||||
"login": "Entrar",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Sair",
|
||||
"major": "Principal",
|
||||
"manage-entity": "Gerenciar {{numberOfDays}}",
|
||||
@ -521,6 +524,7 @@
|
||||
"model-plural": "Models",
|
||||
"model-store": "Estoque modelo",
|
||||
"monday": "Segunda-feira",
|
||||
"monogram-url": "Monogram URL",
|
||||
"month": "Mês",
|
||||
"more": "Mais",
|
||||
"more-help": "Mais ajuda",
|
||||
@ -1029,6 +1033,9 @@
|
||||
"create-or-update-email-account-for-bot": "Alterar o e-mail da conta atualizará ou criará um novo usuário de bot.",
|
||||
"created-this-task-lowercase": "criou esta tarefa",
|
||||
"custom-classification-name-dbt-tags": "Nome personalizado da Classificação OpenMetadata para tags dbt",
|
||||
"custom-logo-configuration-message": "Configure The Application Logo and Monogram.",
|
||||
"custom-logo-url-path-message": "URL path for the login page logo.",
|
||||
"custom-monogram-url-path-message": "URL path for the navbar logo.",
|
||||
"data-asset-has-been-action-type": "O ativo de dados foi {{actionType}}",
|
||||
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",
|
||||
"data-insight-alert-trigger-description": "Trigger for real time or schedule it for daily, weekly or monthly.",
|
||||
@ -1089,6 +1096,7 @@
|
||||
"entity-does-not-have-followers": "{{entityName}} ainda não tem seguidores",
|
||||
"entity-ingestion-added-successfully": "{{entity}} Ingestão Adicionada com Sucesso",
|
||||
"entity-is-not-valid": "{{entity}} não é válido",
|
||||
"entity-is-not-valid-url": "{{entity}} is not valid url",
|
||||
"entity-maximum-size": "{{entity}} can be a maximum of {{max}} characters",
|
||||
"entity-not-contain-whitespace": "{{entity}} não deve conter espaço em branco",
|
||||
"entity-owned-by-name": "Esta entidade é de propriedade de {{entityOwner}}",
|
||||
|
@ -162,6 +162,8 @@
|
||||
"criteria": "标准",
|
||||
"custom-attribute-plural": "自定义属性",
|
||||
"custom-entity": "自定义条目",
|
||||
"custom-logo": "Custom Logo",
|
||||
"custom-logo-configuration": "Custom Logo Configuration",
|
||||
"custom-oidc": "自定义 OIDC",
|
||||
"custom-property": "自定义属性",
|
||||
"custom-property-plural": "自定义属性",
|
||||
@ -474,6 +476,7 @@
|
||||
"log-plural": "日志",
|
||||
"logged-in-user-lowercase": "已登录用户",
|
||||
"login": "登录",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "退出",
|
||||
"major": "主要",
|
||||
"manage-entity": "管理{{entity}}",
|
||||
@ -521,6 +524,7 @@
|
||||
"model-plural": "模型",
|
||||
"model-store": "模型 Store",
|
||||
"monday": "星期一",
|
||||
"monogram-url": "Monogram URL",
|
||||
"month": "月",
|
||||
"more": "更多",
|
||||
"more-help": "更多帮助",
|
||||
@ -1029,6 +1033,9 @@
|
||||
"create-or-update-email-account-for-bot": "更改帐号电子邮箱将更新或创建一个新的机器人用户",
|
||||
"created-this-task-lowercase": "创建了此任务",
|
||||
"custom-classification-name-dbt-tags": "dbt 标签的自定义 OpenMetadata 分类名称",
|
||||
"custom-logo-configuration-message": "Configure The Application Logo and Monogram.",
|
||||
"custom-logo-url-path-message": "URL path for the login page logo.",
|
||||
"custom-monogram-url-path-message": "URL path for the navbar logo.",
|
||||
"data-asset-has-been-action-type": "数据资产已{{actionType}}",
|
||||
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",
|
||||
"data-insight-alert-trigger-description": "Trigger for real time or schedule it for daily, weekly or monthly.",
|
||||
@ -1089,6 +1096,7 @@
|
||||
"entity-does-not-have-followers": "{{entityName}}尚无任何关注者",
|
||||
"entity-ingestion-added-successfully": "{{entity}}提取成功添加",
|
||||
"entity-is-not-valid": "{{entity}}无效",
|
||||
"entity-is-not-valid-url": "{{entity}} is not valid url",
|
||||
"entity-maximum-size": "{{entity}}最多只能包含{{max}}个字符",
|
||||
"entity-not-contain-whitespace": "{{entity}}不应包含空格",
|
||||
"entity-owned-by-name": "此实体归{{entityOwner}}所有",
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
waitForElementToBeRemoved,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { getSettingsConfigFromConfigType } from 'rest/settingConfigAPI';
|
||||
import CustomLogoConfigSettingsPage from './CustomLogoConfigSettingsPage';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock('rest/settingConfigAPI', () => ({
|
||||
getSettingsConfigFromConfigType: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
config_value: {
|
||||
customLogoUrlPath: 'https://custom-logo.png',
|
||||
customMonogramUrlPath: 'https://custom-monogram.png',
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('Test Custom Logo Config Page', () => {
|
||||
it('Should render the config details', async () => {
|
||||
render(<CustomLogoConfigSettingsPage />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
// page header
|
||||
expect(screen.getByText('label.custom-logo')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('message.custom-logo-configuration-message')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('edit-button')).toBeInTheDocument();
|
||||
|
||||
// card header
|
||||
expect(
|
||||
screen.getByText('label.custom-logo-configuration')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// logo
|
||||
expect(screen.getByText('label.logo-url')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('logo-url-info')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('logo-url')).toHaveTextContent(
|
||||
'https://custom-logo.png'
|
||||
);
|
||||
|
||||
// monogram
|
||||
expect(screen.getByText('label.monogram-url')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('monogram-url-info')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('monogram-url')).toHaveTextContent(
|
||||
'https://custom-monogram.png'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should render the error placeholder if api fails', async () => {
|
||||
(getSettingsConfigFromConfigType as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.reject()
|
||||
);
|
||||
render(<CustomLogoConfigSettingsPage />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
expect(
|
||||
screen.getByTestId('create-error-placeholder-label.custom-logo')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('add-placeholder-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Edit button should work', async () => {
|
||||
render(<CustomLogoConfigSettingsPage />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
const editButton = screen.getByTestId('edit-button');
|
||||
|
||||
expect(editButton).toBeInTheDocument();
|
||||
|
||||
userEvent.click(editButton);
|
||||
|
||||
expect(mockPush).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import Icon from '@ant-design/icons/lib/components/Icon';
|
||||
import { Button, Card, Col, Row, Tooltip, Typography } from 'antd';
|
||||
import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import PageHeader from 'components/header/PageHeader.component';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { GRAYED_OUT_COLOR, ROUTES } from 'constants/constants';
|
||||
import { CUSTOM_LOGO_DOCS } from 'constants/docs.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
import { LogoConfiguration } from 'generated/configuration/applicationConfiguration';
|
||||
import { SettingType } from 'generated/settings/settings';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getSettingsConfigFromConfigType } from 'rest/settingConfigAPI';
|
||||
import { showErrorToast } from 'utils/ToastUtils';
|
||||
|
||||
const CustomLogoConfigSettingsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [config, setConfig] = useState<LogoConfiguration>();
|
||||
|
||||
const fetchCustomLogoConfig = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const { data } = await getSettingsConfigFromConfigType(
|
||||
SettingType.CustomLogoConfiguration
|
||||
);
|
||||
|
||||
setConfig(data.config_value as LogoConfiguration);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const handleEditClick = () => {
|
||||
history.push(ROUTES.SETTINGS_EDIT_CUSTOM_LOGO_CONFIG);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCustomLogoConfig();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (isUndefined(config)) {
|
||||
return (
|
||||
<ErrorPlaceHolder
|
||||
permission
|
||||
doc={CUSTOM_LOGO_DOCS}
|
||||
heading={t('label.custom-logo')}
|
||||
type={ERROR_PLACEHOLDER_TYPE.CREATE}
|
||||
onClick={handleEditClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row align="middle" gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Row align="middle" justify="space-between">
|
||||
<Col>
|
||||
<PageHeader
|
||||
data={{
|
||||
header: t('label.custom-logo'),
|
||||
subHeader: t('message.custom-logo-configuration-message'),
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button
|
||||
data-testid="edit-button"
|
||||
icon={<Icon component={IconEdit} size={12} />}
|
||||
onClick={handleEditClick}>
|
||||
{t('label.edit')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card>
|
||||
<>
|
||||
<Typography.Title level={5}>
|
||||
{t('label.custom-logo-configuration')}
|
||||
</Typography.Title>
|
||||
<Row align="middle" className="m-t-md" gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Row align="middle">
|
||||
<Col span={24}>
|
||||
<Typography.Text className="m-0 text-grey-muted">
|
||||
{t('label.logo-url')}
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={t('message.custom-logo-url-path-message')}
|
||||
trigger="hover">
|
||||
<InfoCircleOutlined
|
||||
className="m-x-xss"
|
||||
data-testid="logo-url-info"
|
||||
style={{ color: GRAYED_OUT_COLOR }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Text data-testid="logo-url">
|
||||
{isEmpty(config?.customLogoUrlPath)
|
||||
? '--'
|
||||
: config?.customLogoUrlPath}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Row align="middle">
|
||||
<Col span={24}>
|
||||
<Typography.Text className="m-0 text-grey-muted">
|
||||
{t('label.monogram-url')}
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={t('message.custom-monogram-url-path-message')}
|
||||
trigger="hover">
|
||||
<InfoCircleOutlined
|
||||
className="m-x-xss"
|
||||
data-testid="monogram-url-info"
|
||||
style={{ color: GRAYED_OUT_COLOR }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Text data-testid="monogram-url">
|
||||
{isEmpty(config?.customMonogramUrlPath)
|
||||
? '--'
|
||||
: config?.customMonogramUrlPath}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomLogoConfigSettingsPage;
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
screen,
|
||||
waitForElementToBeRemoved,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { updateSettingsConfig } from 'rest/settingConfigAPI';
|
||||
import EditCustomLogoConfig from './EditCustomLogoConfig';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
const mockGoBack = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
goBack: mockGoBack,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('rest/settingConfigAPI', () => ({
|
||||
getSettingsConfigFromConfigType: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
config_value: {
|
||||
customLogoUrlPath: 'https://custom-logo.png',
|
||||
customMonogramUrlPath: 'https://custom-monogram.png',
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
updateSettingsConfig: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
jest.mock('components/common/title-breadcrumb/title-breadcrumb.component', () =>
|
||||
jest.fn().mockImplementation(() => <div>BreadCrumb.component</div>)
|
||||
);
|
||||
jest.mock('components/common/ServiceDocPanel/ServiceDocPanel', () =>
|
||||
jest.fn().mockImplementation(() => <div>ServiceDocPanel.component</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/common/ResizablePanels/ResizablePanels', () =>
|
||||
jest.fn().mockImplementation(({ firstPanel, secondPanel }) => (
|
||||
<>
|
||||
<div>{firstPanel.children}</div>
|
||||
<div>{secondPanel.children}</div>
|
||||
</>
|
||||
))
|
||||
);
|
||||
|
||||
describe('Test Custom Logo Config Form', () => {
|
||||
it('Should render the child components', async () => {
|
||||
render(<EditCustomLogoConfig />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
// breadcrumb
|
||||
expect(screen.getByText('BreadCrumb.component')).toBeInTheDocument();
|
||||
|
||||
// service doc panel
|
||||
expect(screen.getByText('ServiceDocPanel.component')).toBeInTheDocument();
|
||||
|
||||
// form
|
||||
expect(screen.getByTestId('custom-logo-config-form')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render the form with default values', async () => {
|
||||
render(<EditCustomLogoConfig />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
// form
|
||||
expect(screen.getByTestId('custom-logo-config-form')).toBeInTheDocument();
|
||||
|
||||
// logo url input
|
||||
expect(screen.getByTestId('customLogoUrlPath')).toHaveValue(
|
||||
'https://custom-logo.png'
|
||||
);
|
||||
|
||||
// monogram url input
|
||||
expect(screen.getByTestId('customMonogramUrlPath')).toHaveValue(
|
||||
'https://custom-monogram.png'
|
||||
);
|
||||
});
|
||||
|
||||
it('Cancel button should work', async () => {
|
||||
render(<EditCustomLogoConfig />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
const cancelButton = screen.getByTestId('cancel-button');
|
||||
|
||||
userEvent.click(cancelButton);
|
||||
|
||||
expect(mockGoBack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Save button should work', async () => {
|
||||
render(<EditCustomLogoConfig />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
|
||||
|
||||
const logoInput = screen.getByTestId('customLogoUrlPath');
|
||||
const monogramInput = screen.getByTestId('customMonogramUrlPath');
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(logoInput, 'https://custom-logo-1.png');
|
||||
userEvent.type(monogramInput, 'https://custom-monogram-1.png');
|
||||
});
|
||||
|
||||
const saveButton = screen.getByTestId('save-button');
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(saveButton);
|
||||
});
|
||||
|
||||
expect(updateSettingsConfig).toHaveBeenCalledWith({
|
||||
config_type: 'customLogoConfiguration',
|
||||
config_value: {
|
||||
customLogoUrlPath: 'https://custom-logo-1.png',
|
||||
customMonogramUrlPath: 'https://custom-monogram-1.png',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Button, Card, Col, Form, Row } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import ResizablePanels from 'components/common/ResizablePanels/ResizablePanels';
|
||||
import ServiceDocPanel from 'components/common/ServiceDocPanel/ServiceDocPanel';
|
||||
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import PageContainerV1 from 'components/containers/PageContainerV1';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import {
|
||||
GlobalSettingOptions,
|
||||
GlobalSettingsMenuCategory,
|
||||
} from 'constants/GlobalSettings.constants';
|
||||
import {
|
||||
CUSTOM_LOGO_CONFIG_SERVICE_CATEGORY,
|
||||
OPEN_METADATA,
|
||||
} from 'constants/service-guide.constant';
|
||||
import { ServiceCategory } from 'enums/service.enum';
|
||||
import { LogoConfiguration } from 'generated/configuration/applicationConfiguration';
|
||||
import { Settings, SettingType } from 'generated/settings/settings';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
getSettingsConfigFromConfigType,
|
||||
updateSettingsConfig,
|
||||
} from 'rest/settingConfigAPI';
|
||||
import { FieldProp, FieldTypes, generateFormFields } from 'utils/formUtils';
|
||||
import { getSettingPath } from 'utils/RouterUtils';
|
||||
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
|
||||
|
||||
const EditCustomLogoConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const [form] = Form.useForm();
|
||||
const [activeField, setActiveField] = useState<string>('');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
|
||||
const fetchCustomLogoConfig = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const { data } = await getSettingsConfigFromConfigType(
|
||||
SettingType.CustomLogoConfiguration
|
||||
);
|
||||
|
||||
form.setFieldsValue({ ...(data.config_value ?? {}) });
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const breadcrumb = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: t('label.setting-plural'),
|
||||
url: getSettingPath(),
|
||||
},
|
||||
{
|
||||
name: t('label.custom-logo'),
|
||||
url: getSettingPath(
|
||||
GlobalSettingsMenuCategory.OPEN_METADATA,
|
||||
GlobalSettingOptions.CUSTOM_LOGO
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t('label.edit-entity', {
|
||||
entity: t('label.custom-logo-configuration'),
|
||||
}),
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const formFields: FieldProp[] = [
|
||||
{
|
||||
name: 'customLogoUrlPath',
|
||||
label: t('label.logo-url'),
|
||||
type: FieldTypes.TEXT,
|
||||
required: false,
|
||||
id: 'root/customLogoUrlPath',
|
||||
props: {
|
||||
'data-testid': 'customLogoUrlPath',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
type: 'url',
|
||||
message: t('message.entity-is-not-valid-url', {
|
||||
entity: t('label.logo-url'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customMonogramUrlPath',
|
||||
label: t('label.monogram-url'),
|
||||
type: FieldTypes.TEXT,
|
||||
required: false,
|
||||
id: 'root/customMonogramUrlPath',
|
||||
props: {
|
||||
'data-testid': 'customMonogramUrlPath',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
type: 'url',
|
||||
message: t('message.entity-is-not-valid-url', {
|
||||
entity: t('label.monogram-url'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const handleGoBack = () => history.goBack();
|
||||
|
||||
const handleSubmit = async (configValues: LogoConfiguration) => {
|
||||
try {
|
||||
setUpdating(true);
|
||||
const configData = {
|
||||
config_type: SettingType.CustomLogoConfiguration,
|
||||
config_value: configValues,
|
||||
};
|
||||
await updateSettingsConfig(configData as Settings);
|
||||
showSuccessToast(
|
||||
t('server.update-entity-success', {
|
||||
entity: t('label.custom-logo-configuration'),
|
||||
})
|
||||
);
|
||||
handleGoBack();
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const firstPanelChildren = (
|
||||
<div className="max-width-md w-9/10 service-form-container">
|
||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||
<Card className="p-lg m-t-md">
|
||||
<Form
|
||||
data-testid="custom-logo-config-form"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
onFocus={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setActiveField(e.target.id);
|
||||
}}>
|
||||
{generateFormFields(formFields)}
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button
|
||||
data-testid="cancel-button"
|
||||
type="link"
|
||||
onClick={handleGoBack}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button
|
||||
data-testid="save-button"
|
||||
htmlType="submit"
|
||||
loading={updating}
|
||||
type="primary">
|
||||
{t('label.save')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
const secondPanelChildren = (
|
||||
<ServiceDocPanel
|
||||
activeField={activeField}
|
||||
serviceName={CUSTOM_LOGO_CONFIG_SERVICE_CATEGORY}
|
||||
serviceType={OPEN_METADATA as ServiceCategory}
|
||||
/>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCustomLogoConfig();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainerV1>
|
||||
<ResizablePanels
|
||||
firstPanel={{ children: firstPanelChildren, minWidth: 700, flex: 0.7 }}
|
||||
pageTitle={t('label.edit-entity', { entity: t('label.service') })}
|
||||
secondPanel={{
|
||||
children: secondPanelChildren,
|
||||
className: 'service-doc-panel',
|
||||
minWidth: 60,
|
||||
overlay: {
|
||||
displayThreshold: 200,
|
||||
header: t('label.setup-guide'),
|
||||
rotation: 'counter-clockwise',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</PageContainerV1>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditCustomLogoConfig;
|
@ -41,7 +41,7 @@ import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
getSettingsConfigFromConfigType,
|
||||
updateSettingsConfig,
|
||||
} from 'rest/emailConfigAPI';
|
||||
} from 'rest/settingConfigAPI';
|
||||
import { getSettingPath } from 'utils/RouterUtils';
|
||||
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { isBoolean, isEmpty, isNumber, isUndefined } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getSettingsConfigFromConfigType } from 'rest/emailConfigAPI';
|
||||
import { getSettingsConfigFromConfigType } from 'rest/settingConfigAPI';
|
||||
import { getEmailConfigFieldLabels } from 'utils/EmailConfigUtils';
|
||||
import { showErrorToast } from 'utils/ToastUtils';
|
||||
import { ReactComponent as IconEdit } from '../../assets/svg/edit-new.svg';
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
import { Button, Card, Col, Divider, Form, Input, Row, Typography } from 'antd';
|
||||
import { useBasicAuth } from 'components/authentication/auth-provider/basic-auth.provider';
|
||||
import BrandImage from 'components/common/BrandImage/BrandImage';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@ -51,11 +52,7 @@ const ForgotPassword = () => {
|
||||
style={{ maxWidth: '430px' }}>
|
||||
<Row gutter={[16, 24]}>
|
||||
<Col className="text-center" span={24}>
|
||||
<SVGIcons
|
||||
alt={t('label.open-metadata-logo')}
|
||||
icon={Icons.LOGO}
|
||||
width="152"
|
||||
/>
|
||||
<BrandImage className="m-auto" height="auto" width={152} />
|
||||
</Col>
|
||||
<Col className="flex-center text-center mt-8" span={24}>
|
||||
<Typography.Text className="text-xl font-medium text-grey-muted">
|
||||
|
@ -35,11 +35,9 @@ jest.mock(
|
||||
'components/ApplicationConfigProvider/ApplicationConfigProvider',
|
||||
() => ({
|
||||
useApplicationConfigProvider: jest.fn().mockImplementation(() => ({
|
||||
logoConfig: {
|
||||
customLogoUrlPath: 'https://customlink.source',
|
||||
customLogoUrlPath: 'https://customlink.source',
|
||||
|
||||
customMonogramUrlPath: 'https://customlink.source',
|
||||
},
|
||||
customMonogramUrlPath: 'https://customlink.source',
|
||||
})),
|
||||
})
|
||||
);
|
||||
@ -142,10 +140,9 @@ describe('Test SigninPage Component', () => {
|
||||
});
|
||||
|
||||
const brandLogoImage = await screen.findByTestId('brand-logo-image');
|
||||
const logoImage = brandLogoImage.querySelector('img') as HTMLImageElement;
|
||||
|
||||
expect(brandLogoImage).toBeInTheDocument();
|
||||
|
||||
expect(logoImage.src).toEqual('https://customlink.source/');
|
||||
expect(brandLogoImage).toHaveAttribute('src', 'https://customlink.source');
|
||||
});
|
||||
});
|
||||
|
@ -11,21 +11,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Divider,
|
||||
Form,
|
||||
Image,
|
||||
Input,
|
||||
Row,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import Logo from 'assets/svg/logo.svg';
|
||||
import { Button, Col, Divider, Form, Input, Row, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { useApplicationConfigProvider } from 'components/ApplicationConfigProvider/ApplicationConfigProvider';
|
||||
import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider';
|
||||
import { useBasicAuth } from 'components/authentication/auth-provider/basic-auth.provider';
|
||||
import BrandImage from 'components/common/BrandImage/BrandImage';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import LoginButton from 'components/LoginButton/LoginButton';
|
||||
import jwtDecode, { JwtPayload } from 'jwt-decode';
|
||||
@ -42,7 +32,6 @@ import './login.style.less';
|
||||
import LoginCarousel from './LoginCarousel';
|
||||
|
||||
const SigninPage = () => {
|
||||
const { logoConfig } = useApplicationConfigProvider();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@ -77,10 +66,6 @@ const SigninPage = () => {
|
||||
return isAuthDisabled || isAuthenticated;
|
||||
}, [isAuthDisabled, isAuthenticated]);
|
||||
|
||||
const brandLogoUrl = useMemo(() => {
|
||||
return logoConfig?.customLogoUrlPath ?? Logo;
|
||||
}, [logoConfig]);
|
||||
|
||||
const isTokenExpired = () => {
|
||||
const token = localState.getOidcToken();
|
||||
if (token) {
|
||||
@ -216,14 +201,7 @@ const SigninPage = () => {
|
||||
className={classNames('mt-24 text-center flex-center flex-col', {
|
||||
'sso-container': !isAuthProviderBasic,
|
||||
})}>
|
||||
<Image
|
||||
alt="OpenMetadata Logo"
|
||||
data-testid="brand-logo-image"
|
||||
fallback={Logo}
|
||||
preview={false}
|
||||
src={brandLogoUrl}
|
||||
width={152}
|
||||
/>
|
||||
<BrandImage height="auto" width={152} />
|
||||
<Typography.Text className="mt-8 w-80 text-xl font-medium text-grey-muted">
|
||||
{t('message.om-description')}{' '}
|
||||
</Typography.Text>
|
||||
|
@ -14,13 +14,13 @@
|
||||
import { Alert, Button, Card, Col, Form, Input, Row, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useBasicAuth } from 'components/authentication/auth-provider/basic-auth.provider';
|
||||
import BrandImage from 'components/common/BrandImage/BrandImage';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants';
|
||||
import { passwordRegex } from '../../constants/regex.constants';
|
||||
import { PasswordResetRequest } from '../../generated/auth/passwordResetRequest';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import './reset-password.style.less';
|
||||
import { getUserNameAndToken } from './reset-password.utils';
|
||||
@ -97,7 +97,7 @@ const ResetPassword = () => {
|
||||
style={{ maxWidth: '450px' }}>
|
||||
<Row gutter={[16, 24]}>
|
||||
<Col className="text-center" span={24}>
|
||||
<SVGIcons alt="OpenMetadata Logo" icon={Icons.LOGO} width="152" />
|
||||
<BrandImage className="m-auto" height="auto" width={152} />
|
||||
</Col>
|
||||
|
||||
<Col className="mt-12 text-center" span={24}>
|
||||
|
@ -14,6 +14,7 @@
|
||||
import { Button, Col, Divider, Form, Input, Row, Typography } from 'antd';
|
||||
import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider';
|
||||
import { useBasicAuth } from 'components/authentication/auth-provider/basic-auth.provider';
|
||||
import BrandImage from 'components/common/BrandImage/BrandImage';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -22,7 +23,6 @@ import loginBG from '../../assets/img/login-bg.png';
|
||||
import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants';
|
||||
import { passwordRegex } from '../../constants/regex.constants';
|
||||
import { AuthTypes } from '../../enums/signin.enum';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import LoginCarousel from '../login/LoginCarousel';
|
||||
import './../login/login.style.less';
|
||||
|
||||
@ -70,7 +70,7 @@ const BasicSignUp = () => {
|
||||
<div className="d-flex bg-body-main flex-grow" data-testid="signin-page">
|
||||
<div className="w-5/12">
|
||||
<div className="mt-4 text-center flex-center flex-col">
|
||||
<SVGIcons alt="OpenMetadata Logo" icon={Icons.LOGO} width="152" />
|
||||
<BrandImage height="auto" width={152} />
|
||||
<Typography.Text className="mt-8 w-80 text-xl font-medium text-grey-muted">
|
||||
{t('message.om-description')}
|
||||
</Typography.Text>
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { LogoConfiguration } from 'generated/configuration/applicationConfiguration';
|
||||
import { Settings, SettingType } from 'generated/settings/settings';
|
||||
import axiosClient from 'rest';
|
||||
|
||||
@ -30,3 +31,11 @@ export const updateSettingsConfig = async (payload: Settings) => {
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export const getCustomLogoConfig = async () => {
|
||||
const response = await axiosClient.get<LogoConfiguration>(
|
||||
`system/config/customLogoConfiguration`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
@ -38,6 +38,7 @@ import { ReactComponent as TableIcon } from '../../src/assets/svg/table-grey.svg
|
||||
import { ReactComponent as TeamsIcon } from '../../src/assets/svg/teams-grey.svg';
|
||||
import { ReactComponent as TopicIcon } from '../../src/assets/svg/topic-grey.svg';
|
||||
import { ReactComponent as UsersIcon } from '../../src/assets/svg/user.svg';
|
||||
import { ReactComponent as CustomLogoIcon } from '../assets/svg/ic-custom-logo.svg';
|
||||
import { ReactComponent as StorageIcon } from '../assets/svg/ic-storage.svg';
|
||||
import { userPermissions } from '../utils/PermissionsUtils';
|
||||
|
||||
@ -301,6 +302,12 @@ export const getGlobalSettingsMenuWithPermission = (
|
||||
key: 'openMetadata.email',
|
||||
icon: <EmailSettingsIcon className="w-4 side-panel-icons" />,
|
||||
},
|
||||
{
|
||||
label: i18next.t('label.custom-logo'),
|
||||
isProtected: Boolean(isAdminUser),
|
||||
key: 'openMetadata.customLogo',
|
||||
icon: <CustomLogoIcon className="w-4 side-panel-icons" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user