Skip to content

Commit

Permalink
Merged PR 2660: collapsable data quality dashboard section in user pr…
Browse files Browse the repository at this point in the history
…ofile

collapsable data quality dashboard section in user profile
  • Loading branch information
Aleksy Lisowski authored and piotrczarnas committed May 17, 2024
2 parents 046364c + b9c9ab9 commit 31ea9d2
Showing 1 changed file with 175 additions and 83 deletions.
258 changes: 175 additions & 83 deletions dqops/src/main/frontend/src/components/UserProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {
Accordion,
AccordionBody,
AccordionHeader,
IconButton,
Popover,
PopoverContent,
Expand All @@ -10,7 +13,11 @@ import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { DqoUserProfileModel } from '../../api';
import { useActionDispatch } from '../../hooks/useActionDispatch';
import { setLicenseFree, setUserProfile, toggleProfile } from '../../redux/actions/job.actions';
import {
setLicenseFree,
setUserProfile,
toggleProfile
} from '../../redux/actions/job.actions';
import { IRootState } from '../../redux/reducers';
import { EnviromentApiClient, UsersApi } from '../../services/apiClient';
import Button from '../Button';
Expand All @@ -24,11 +31,14 @@ interface UserProfile {
}

export default function UserProfile({ name, email }: UserProfile) {
const { isProfileOpen, userProfile } = useSelector((state: IRootState) => state.job || {});
const [apiKey, setApiKey] = useState("");
const [copied, setCopied] = useState(false)
const [open, setOpen] = useState(false)
const [passwordChangedMessage, setPasswordChangedMessage] = useState("")
const { isProfileOpen, userProfile } = useSelector(
(state: IRootState) => state.job || {}
);
const [apiKey, setApiKey] = useState('');
const [copied, setCopied] = useState(false);
const [open, setOpen] = useState(false);
const [dashboardOpen, setDashboardOpen] = useState(false);
const [passwordChangedMessage, setPasswordChangedMessage] = useState('');
const dispatch = useActionDispatch();

const toggleOpen = () => {
Expand All @@ -39,12 +49,11 @@ export default function UserProfile({ name, email }: UserProfile) {
dispatch(setLicenseFree(true));
};


const fetchUserProfile = async () => {
try {
const res: AxiosResponse<DqoUserProfileModel> =
await EnviromentApiClient.getUserProfile();
dispatch(setUserProfile(res.data))
dispatch(setUserProfile(res.data));
} catch (err) {
console.error(err);
}
Expand All @@ -59,33 +68,70 @@ export default function UserProfile({ name, email }: UserProfile) {
const sampleData = moment(userProfile?.trial_period_expires_at);
const dayDiff = sampleData.diff(today, 'days');

const generateApiKey =async () => {
await EnviromentApiClient.issueApiKey().then((res) => setApiKey(res.data))
}
const generateApiKey = async () => {
await EnviromentApiClient.issueApiKey().then((res) => setApiKey(res.data));
};

const copyToClipboard = () => {
navigator.clipboard.writeText(apiKey)
setCopied(true)
}
navigator.clipboard.writeText(apiKey);
setCopied(true);
};

const copyWhole = (e: any) => {
e.target.select();
}
};

const changePrincipalPassword = async (password: string) => {
await UsersApi.changeCallerPassword(password)
.then(() => setPasswordChangedMessage("Password has been successfully changed"))
.then(() => setOpen(false))
.catch((err) => console.error(err))
}
.then(() =>
setPasswordChangedMessage('Password has been successfully changed')
)
.then(() => setOpen(false))
.catch((err) => console.error(err));
};
const profileItems = [
{ label: 'Data quality dashboards:', value: userProfile?.data_quality_data_warehouse_enabled ? 'enabled' : 'disabled' },
{ label: 'Users:', value: userProfile?.users_limit ? formatNumberWithSegments(userProfile.users_limit) : '-' },
{ label: 'Monitored connections:', value: userProfile?.connections_limit ? formatNumberWithSegments(userProfile.connections_limit) : '-' },
{ label: 'Total monitored tables:', value: userProfile?.tables_limit ? formatNumberWithSegments(userProfile.tables_limit) : '-' },
{ label: 'Tables per connection:', value: userProfile?.connection_tables_limit ? formatNumberWithSegments(userProfile.connection_tables_limit) : '-' },
{ label: 'Concurrent jobs:', value: userProfile?.jobs_limit ? formatNumberWithSegments(userProfile.jobs_limit) : '-' },
{ label: 'Months in data quality warehouse:', value: userProfile?.months_limit ? formatNumberWithSegments(userProfile.months_limit) : '-' }
{
label: 'Data quality dashboards:',
value: userProfile?.data_quality_data_warehouse_enabled
? 'enabled'
: 'disabled'
},
{
label: 'Users:',
value: userProfile?.users_limit
? formatNumberWithSegments(userProfile.users_limit)
: '-'
},
{
label: 'Monitored connections:',
value: userProfile?.connections_limit
? formatNumberWithSegments(userProfile.connections_limit)
: '-'
},
{
label: 'Total monitored tables:',
value: userProfile?.tables_limit
? formatNumberWithSegments(userProfile.tables_limit)
: '-'
},
{
label: 'Tables per connection:',
value: userProfile?.connection_tables_limit
? formatNumberWithSegments(userProfile.connection_tables_limit)
: '-'
},
{
label: 'Concurrent jobs:',
value: userProfile?.jobs_limit
? formatNumberWithSegments(userProfile.jobs_limit)
: '-'
},
{
label: 'Months in data quality warehouse:',
value: userProfile?.months_limit
? formatNumberWithSegments(userProfile.months_limit)
: '-'
}
];
return (
<Popover open={isProfileOpen} handler={toggleOpen} placement="top-end">
Expand All @@ -100,75 +146,121 @@ export default function UserProfile({ name, email }: UserProfile) {
</div>
</IconButton>
</PopoverHandler>
<PopoverContent className="bg-white h-112 w-70 rounded-md border border-gray-400 flex-col justify-center items-center z-50 text-black text-sm">
<PopoverContent className="bg-white h-auto w-70 rounded-md border border-gray-400 flex-col justify-center items-center z-50 text-black text-sm">
<div className="flex justify-between items-center h-12 ">
<div className="ml-1 flex items-center justify-center gap-x-2">
{' '}
<div className="font-bold">User:</div>
<div className='break-all'>
{userProfile?.user ? userProfile.user : '-'}{' '}
<div className="break-all">
{userProfile?.user ? userProfile.user : '-'}{' '}
</div>
</div>
</div>
<div className="h-15">
<div className="ml-1 flex items-center gap-x-2 my-2">
{' '}
<div className="font-bold">Subscription plan:</div>
{userProfile?.license_type ? userProfile.license_type : '-'}{' '}
</div>
{userProfile?.trial_period_expires_at && (
<div className="ml-1 flex items-center gap-x-2 my-2">
{' '}
<div className="font-bold" style={{ whiteSpace: 'nowrap' }}>
{dayDiff} days left

<Accordion open={dashboardOpen}>
<AccordionHeader
onClick={() => setDashboardOpen((open) => !open)}
className=" !p-1 !m-0 !text-black"
>
<div className="text-sm">Data quality dashboards</div>
</AccordionHeader>
<AccordionBody>
<div className="h-15">
<div className="ml-1 flex items-center gap-x-2 my-2">
{' '}
<div className="font-bold">Subscription plan:</div>
{userProfile?.license_type
? userProfile.license_type
: '-'}{' '}
</div>
{userProfile?.trial_period_expires_at && (
<div className="ml-1 flex items-center gap-x-2 my-2">
{' '}
<div className="font-bold" style={{ whiteSpace: 'nowrap' }}>
{dayDiff} days left
</div>
<div
className="h-1 bg-gray-100"
style={{ width: '100%', position: 'relative' }}
>
<div
className="h-1 absolute bg-teal-500"
style={{
width: `${(dayDiff / 14) * 100}%`
}}
></div>
</div>
</div>
)}
</div>

<div className="font-bold h-8 ml-1">Account limits:</div>
{profileItems.map((item, index) => (
<div
className="h-1 bg-gray-100"
style={{ width: '100%', position: 'relative' }}
className="flex justify-between items-center pb-0.5"
key={index}
>
<div
className="h-1 absolute bg-teal-500"
style={{
width: `${(dayDiff / 14) * 100}%`
}}
></div>
<div className="ml-1">{item.label}</div>
<div className="mr-1">{item.value}</div>
</div>
</div>
)}
</div>
))}
</AccordionBody>
</Accordion>

<div className="font-bold h-8 ml-1">Account limits:</div>
{profileItems.map((item, index) => (
<div className="flex justify-between items-center pb-0.5" key={index}>
<div className="ml-1">{item.label}</div>
<div className="mr-1">{item.value}</div>
<div className="my-2 ml-1">
{apiKey.length !== 0 ? (
<div className="flex items-center">
<TextArea
label="User API Key:"
value={apiKey}
className="select-all text-sm"
onClick={copyWhole}
/>
<SvgIcon
name={copied ? 'done' : 'copytext'}
className="cursor-pointer mt-8 ml-5"
onClick={() => copyToClipboard()}
/>
</div>
))}
<div className='my-2 ml-1'>{apiKey.length!==0 ?
<div className='flex items-center' ><TextArea label='User API Key:' value={apiKey} className='select-all text-sm' onClick={copyWhole}/>
<SvgIcon name={copied ? 'done' : 'copytext' } className='cursor-pointer mt-8 ml-5' onClick={() => copyToClipboard()}/>
</div>
: <Button label='Generate API Key' color='primary' variant='outlined' onClick={generateApiKey}/>}
) : (
<Button
label="Generate API Key"
color="primary"
variant="outlined"
onClick={generateApiKey}
/>
)}
</div>
<div className="w-full flex flex-col h-30 text-black mt-4">
{userProfile.can_manage_account === true &&
<a
href="https://cloud.dqops.com/account"
target="_blank"
rel="noreferrer"
className="block text-teal-500 text-sm underline mb-3 ml-1"
>
Manage account
</a>
}
{userProfile.can_change_own_password === true &&
<>
<div className='text-teal-500 mb-3 text-sm cursor-pointer underline ml-1' onClick={() => setOpen(true)}>Change password</div>
<div className='text-green-500 pt-2 text-sm ml-1'>{passwordChangedMessage}</div>
</>
}
<div className="w-full flex flex-col h-auto text-black mt-4">
{userProfile.can_manage_account === true && (
<a
href="https://cloud.dqops.com/account"
target="_blank"
rel="noreferrer"
className="block text-teal-500 text-sm underline mb-3 ml-1"
>
Manage account
</a>
)}
{userProfile.can_change_own_password === true && (
<>
<div
className="text-teal-500 text-sm cursor-pointer underline ml-1"
onClick={() => setOpen(true)}
>
Change password
</div>
<div className="text-green-500 pt-2 text-sm ml-1">
{passwordChangedMessage}
</div>
</>
)}
</div>
<ChangePrincipalPasswordDialog open = {open} onClose={() => setOpen(false)} handleSubmit={changePrincipalPassword}/>
<ChangePrincipalPasswordDialog
open={open}
onClose={() => setOpen(false)}
handleSubmit={changePrincipalPassword}
/>
</PopoverContent>
</Popover>
);
Expand All @@ -177,10 +269,10 @@ export default function UserProfile({ name, email }: UserProfile) {
function formatNumberWithSegments(num: number): string {
const numStr = num.toString();
const segments = [];

for (let i = numStr.length; i > 0; i -= 3) {
segments.unshift(numStr.slice(Math.max(0, i - 3), i));
segments.unshift(numStr.slice(Math.max(0, i - 3), i));
}

return segments.join(' ');
}
}

0 comments on commit 31ea9d2

Please sign in to comment.